using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests;

/// <summary>
/// Tests for <see cref="ListValueNormalizer"/> — the idempotent startup normalizer that
/// rewrites already-persisted List attribute values from the old array-of-strings JSON
/// form (<c>["10","20"]</c>) to the new native-typed form (<c>[10,20]</c>).
/// </summary>
public class ListValueNormalizerTests : IDisposable
{
    private readonly ScadaBridgeDbContext _context;

    public ListValueNormalizerTests()
    {
        _context = SqliteTestHelper.CreateInMemoryContext();
    }

    public void Dispose()
    {
        _context.Database.CloseConnection();
        _context.Dispose();
    }

    // Seeds a Template parent (satisfies the TemplateAttribute -> Template FK) and returns its Id.
    private async Task<int> SeedTemplateAsync()
    {
        var template = new Template("T1");
        _context.Templates.Add(template);
        await _context.SaveChangesAsync();
        return template.Id;
    }

    // Seeds Site + Template + Instance (satisfies the InstanceAttributeOverride -> Instance FK)
    // and returns the Instance Id.
    private async Task<int> SeedInstanceAsync()
    {
        var site = new Site("Site1", "S-001");
        var template = new Template("T1");
        _context.Sites.Add(site);
        _context.Templates.Add(template);
        await _context.SaveChangesAsync();

        var instance = new Instance("Inst1") { SiteId = site.Id, TemplateId = template.Id };
        _context.Instances.Add(instance);
        await _context.SaveChangesAsync();
        return instance.Id;
    }

    [Fact]
    public async Task TemplateAttribute_OldStringForm_RewrittenToNative()
    {
        var templateId = await SeedTemplateAsync();
        _context.TemplateAttributes.Add(new TemplateAttribute("intList")
        {
            TemplateId = templateId,
            DataType = DataType.List,
            ElementDataType = DataType.Int32,
            Value = "[\"10\",\"20\"]",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);

        var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
        Assert.Equal("[10,20]", reloaded.Value);
    }

    [Fact]
    public async Task TemplateAttribute_AlreadyNative_IsNotRewritten()
    {
        var templateId = await SeedTemplateAsync();
        _context.TemplateAttributes.Add(new TemplateAttribute("intList")
        {
            TemplateId = templateId,
            DataType = DataType.List,
            ElementDataType = DataType.Int32,
            Value = "[10,20]",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);

        // No tracked entity should be marked Modified — idempotent no-op.
        var tracked = _context.ChangeTracker.Entries<TemplateAttribute>()
            .Where(e => e.State == EntityState.Modified)
            .ToList();
        Assert.Empty(tracked);

        var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
        Assert.Equal("[10,20]", reloaded.Value);
    }

    [Fact]
    public async Task TemplateAttribute_StringList_IsUnchanged()
    {
        var templateId = await SeedTemplateAsync();
        _context.TemplateAttributes.Add(new TemplateAttribute("stringList")
        {
            TemplateId = templateId,
            DataType = DataType.List,
            ElementDataType = DataType.String,
            Value = "[\"a\",\"b\"]",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);

        var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
        Assert.Equal("[\"a\",\"b\"]", reloaded.Value);
    }

    [Fact]
    public async Task TemplateAttribute_Malformed_IsSkipped_AndSiblingStillNormalized()
    {
        var templateId = await SeedTemplateAsync();
        _context.TemplateAttributes.Add(new TemplateAttribute("badList")
        {
            TemplateId = templateId,
            DataType = DataType.List,
            ElementDataType = DataType.Int32,
            Value = "[\"a\"",      // malformed JSON
        });
        _context.TemplateAttributes.Add(new TemplateAttribute("goodList")
        {
            TemplateId = templateId,
            DataType = DataType.List,
            ElementDataType = DataType.Int32,
            Value = "[\"5\"]",     // old form, valid
        });
        await _context.SaveChangesAsync();

        // Must NOT throw despite the malformed row.
        await ListValueNormalizer.NormalizeAsync(_context);

        var bad = await _context.TemplateAttributes.AsNoTracking()
            .SingleAsync(a => a.Name == "badList");
        var good = await _context.TemplateAttributes.AsNoTracking()
            .SingleAsync(a => a.Name == "goodList");

        Assert.Equal("[\"a\"", bad.Value);   // skipped, untouched
        Assert.Equal("[5]", good.Value);     // normalized
    }

    [Fact]
    public async Task TemplateAttribute_NonListRow_IsUnchanged()
    {
        var templateId = await SeedTemplateAsync();
        _context.TemplateAttributes.Add(new TemplateAttribute("scalar")
        {
            TemplateId = templateId,
            DataType = DataType.Int32,
            ElementDataType = null,
            Value = "42",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);

        var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
        Assert.Equal("42", reloaded.Value);
    }

    [Fact]
    public async Task InstanceAttributeOverride_OldStringForm_RewrittenToNative()
    {
        var instanceId = await SeedInstanceAsync();
        _context.InstanceAttributeOverrides.Add(new InstanceAttributeOverride("intList")
        {
            InstanceId = instanceId,
            ElementDataType = DataType.Int32,
            OverrideValue = "[\"5\"]",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);

        var reloaded = await _context.InstanceAttributeOverrides.AsNoTracking().SingleAsync();
        Assert.Equal("[5]", reloaded.OverrideValue);
    }

    [Fact]
    public async Task InstanceAttributeOverride_NullElementType_IsUntouched()
    {
        var instanceId = await SeedInstanceAsync();
        _context.InstanceAttributeOverrides.Add(new InstanceAttributeOverride("scalar")
        {
            InstanceId = instanceId,
            ElementDataType = null,
            OverrideValue = "42",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);

        var reloaded = await _context.InstanceAttributeOverrides.AsNoTracking().SingleAsync();
        Assert.Equal("42", reloaded.OverrideValue);
    }

    [Fact]
    public async Task NormalizeAsync_IsIdempotent_SecondRunChangesNothing()
    {
        var templateId = await SeedTemplateAsync();
        _context.TemplateAttributes.Add(new TemplateAttribute("intList")
        {
            TemplateId = templateId,
            DataType = DataType.List,
            ElementDataType = DataType.Int32,
            Value = "[\"10\",\"20\"]",
        });
        await _context.SaveChangesAsync();

        await ListValueNormalizer.NormalizeAsync(_context);
        var afterFirst = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
        Assert.Equal("[10,20]", afterFirst.Value);

        await ListValueNormalizer.NormalizeAsync(_context);
        var afterSecond = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
        Assert.Equal("[10,20]", afterSecond.Value);

        var tracked = _context.ChangeTracker.Entries<TemplateAttribute>()
            .Where(e => e.State == EntityState.Modified)
            .ToList();
        Assert.Empty(tracked);
    }
}
