using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Server.OpcUa;

namespace ZB.MOM.WW.OtOpcUa.Server.Tests;

[Trait("Category", "Unit")]
public sealed class EquipmentNamespaceContentLoaderTests : IDisposable
{
    private const string DriverId = "galaxy-prod";
    private const string OtherDriverId = "galaxy-dev";
    private const long Gen = 5;

    private readonly OtOpcUaConfigDbContext _db;
    private readonly EquipmentNamespaceContentLoader _loader;

    public EquipmentNamespaceContentLoaderTests()
    {
        var options = new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
            .UseInMemoryDatabase($"eq-content-loader-{Guid.NewGuid():N}")
            .Options;
        _db = new OtOpcUaConfigDbContext(options);
        _loader = new EquipmentNamespaceContentLoader(_db);
    }

    public void Dispose() => _db.Dispose();

    [Fact]
    public async Task Returns_Null_When_Driver_Has_No_Equipment_At_Generation()
    {
        var result = await _loader.LoadAsync(DriverId, Gen, CancellationToken.None);
        result.ShouldBeNull();
    }

    [Fact]
    public async Task Loads_Areas_Lines_Equipment_Tags_For_Driver_At_Generation()
    {
        SeedBaseline();

        var result = await _loader.LoadAsync(DriverId, Gen, CancellationToken.None);

        result.ShouldNotBeNull();
        result!.Areas.ShouldHaveSingleItem().UnsAreaId.ShouldBe("area-1");
        result.Lines.ShouldHaveSingleItem().UnsLineId.ShouldBe("line-a");
        result.Equipment.Count.ShouldBe(2);
        result.Equipment.ShouldContain(e => e.EquipmentId == "eq-oven-3");
        result.Equipment.ShouldContain(e => e.EquipmentId == "eq-press-7");
        result.Tags.Count.ShouldBe(2);
        result.Tags.ShouldContain(t => t.TagId == "tag-temp");
        result.Tags.ShouldContain(t => t.TagId == "tag-press");
    }

    [Fact]
    public async Task Skips_Other_Drivers_Equipment()
    {
        SeedBaseline();

        // Equipment + Tag owned by a different driver at the same generation — must not leak.
        _db.Equipment.Add(new Equipment
        {
            EquipmentRowId = Guid.NewGuid(), GenerationId = Gen,
            EquipmentId = "eq-other", EquipmentUuid = Guid.NewGuid(),
            DriverInstanceId = OtherDriverId, UnsLineId = "line-a", Name = "other-eq",
            MachineCode = "MC-other",
        });
        _db.Tags.Add(new Tag
        {
            TagRowId = Guid.NewGuid(), GenerationId = Gen, TagId = "tag-other",
            DriverInstanceId = OtherDriverId, EquipmentId = "eq-other",
            Name = "OtherTag", DataType = "Int32",
            AccessLevel = TagAccessLevel.ReadWrite, TagConfig = "plcaddr-other",
        });
        await _db.SaveChangesAsync();

        var result = await _loader.LoadAsync(DriverId, Gen, CancellationToken.None);

        result.ShouldNotBeNull();
        result!.Equipment.ShouldNotContain(e => e.EquipmentId == "eq-other");
        result.Tags.ShouldNotContain(t => t.TagId == "tag-other");
    }

    [Fact]
    public async Task Skips_Other_Generations()
    {
        SeedBaseline();

        // Same driver, different generation — must not leak in. Walker consumes one sealed
        // generation per bootstrap per decision #148.
        _db.Equipment.Add(new Equipment
        {
            EquipmentRowId = Guid.NewGuid(), GenerationId = 99,
            EquipmentId = "eq-futuristic", EquipmentUuid = Guid.NewGuid(),
            DriverInstanceId = DriverId, UnsLineId = "line-a", Name = "futuristic",
            MachineCode = "MC-fut",
        });
        await _db.SaveChangesAsync();

        var result = await _loader.LoadAsync(DriverId, Gen, CancellationToken.None);

        result.ShouldNotBeNull();
        result!.Equipment.ShouldNotContain(e => e.EquipmentId == "eq-futuristic");
    }

    [Fact]
    public async Task Skips_Disabled_Equipment()
    {
        SeedBaseline();

        _db.Equipment.Add(new Equipment
        {
            EquipmentRowId = Guid.NewGuid(), GenerationId = Gen,
            EquipmentId = "eq-disabled", EquipmentUuid = Guid.NewGuid(),
            DriverInstanceId = DriverId, UnsLineId = "line-a", Name = "disabled-eq",
            MachineCode = "MC-dis", Enabled = false,
        });
        await _db.SaveChangesAsync();

        var result = await _loader.LoadAsync(DriverId, Gen, CancellationToken.None);

        result.ShouldNotBeNull();
        result!.Equipment.ShouldNotContain(e => e.EquipmentId == "eq-disabled");
    }

    private void SeedBaseline()
    {
        _db.UnsAreas.Add(new UnsArea
        {
            UnsAreaRowId = Guid.NewGuid(), UnsAreaId = "area-1", ClusterId = "c-warsaw",
            Name = "warsaw", GenerationId = Gen,
        });
        _db.UnsLines.Add(new UnsLine
        {
            UnsLineRowId = Guid.NewGuid(), UnsLineId = "line-a", UnsAreaId = "area-1",
            Name = "line-a", GenerationId = Gen,
        });
        _db.Equipment.AddRange(
            new Equipment
            {
                EquipmentRowId = Guid.NewGuid(), GenerationId = Gen,
                EquipmentId = "eq-oven-3", EquipmentUuid = Guid.NewGuid(),
                DriverInstanceId = DriverId, UnsLineId = "line-a", Name = "oven-3",
                MachineCode = "MC-oven-3",
            },
            new Equipment
            {
                EquipmentRowId = Guid.NewGuid(), GenerationId = Gen,
                EquipmentId = "eq-press-7", EquipmentUuid = Guid.NewGuid(),
                DriverInstanceId = DriverId, UnsLineId = "line-a", Name = "press-7",
                MachineCode = "MC-press-7",
            });
        _db.Tags.AddRange(
            new Tag
            {
                TagRowId = Guid.NewGuid(), GenerationId = Gen, TagId = "tag-temp",
                DriverInstanceId = DriverId, EquipmentId = "eq-oven-3",
                Name = "Temperature", DataType = "Int32",
                AccessLevel = TagAccessLevel.ReadWrite, TagConfig = "plcaddr-temperature",
            },
            new Tag
            {
                TagRowId = Guid.NewGuid(), GenerationId = Gen, TagId = "tag-press",
                DriverInstanceId = DriverId, EquipmentId = "eq-press-7",
                Name = "PressTemp", DataType = "Int32",
                AccessLevel = TagAccessLevel.ReadWrite, TagConfig = "plcaddr-press-temp",
            });
        _db.SaveChanges();
    }
}
