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

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

/// <summary>
///     Verifies <see cref="DraftSnapshotFactory.FromConfigDbAsync"/> materialises a
///     <see cref="DraftSnapshot"/> from the live config DB whose Tag/VirtualTag rows feed the
///     equipment-signal collision rule — the one rule wired into the deploy gate (Task 3).
/// </summary>
[Trait("Category", "Unit")]
public sealed class DraftSnapshotFactoryTests : IDisposable
{
    private readonly OtOpcUaConfigDbContext _db;

    /// <summary>Initializes a new instance with an isolated in-memory config DB.</summary>
    public DraftSnapshotFactoryTests()
    {
        var options = new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
            .UseInMemoryDatabase($"draft-snapshot-{Guid.NewGuid():N}")
            .Options;
        _db = new OtOpcUaConfigDbContext(options);
    }

    /// <summary>Disposes the database context.</summary>
    public void Dispose() => _db.Dispose();

    /// <summary>Seeds an Equipment plus a raw Tag and a VirtualTag; the snapshot must carry both
    /// signal collections. v3: Tags are raw-only, so the equipment-signal collision rule now operates
    /// on VirtualTags — two VirtualTags sharing (EquipmentId, Name) must surface the collision.</summary>
    [Fact]
    public async Task FromConfigDb_populates_Tags_and_VirtualTags_and_surfaces_collision()
    {
        SeedEquipment("eq-1");
        _db.Tags.Add(BuildTag(name: "speed"));
        _db.VirtualTags.Add(BuildVirtualTag(equipmentId: "eq-1", name: "speed", suffix: "a"));
        _db.VirtualTags.Add(BuildVirtualTag(equipmentId: "eq-1", name: "speed", suffix: "b"));
        await _db.SaveChangesAsync();

        var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);

        snapshot.Tags.Count.ShouldBe(1);
        snapshot.VirtualTags.Count.ShouldBe(2);
        DraftValidator.Validate(snapshot).ShouldContain(e => e.Code == "UnsEffectiveNameCollision");
    }

    /// <summary>Distinct VirtualTag names under the same equipment do not collide, so the snapshot
    /// validates clean of the collision code.</summary>
    [Fact]
    public async Task FromConfigDb_no_collision_when_names_differ()
    {
        SeedEquipment("eq-1");
        _db.Tags.Add(BuildTag(name: "speed"));
        _db.VirtualTags.Add(BuildVirtualTag(equipmentId: "eq-1", name: "temperature"));
        await _db.SaveChangesAsync();

        var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);

        snapshot.Tags.Count.ShouldBe(1);
        snapshot.VirtualTags.Count.ShouldBe(1);
        DraftValidator.Validate(snapshot).ShouldNotContain(e => e.Code == "UnsEffectiveNameCollision");
    }

    /// <summary>Seeds one active and one released reservation for the same equipment context;
    /// only the active row (ReleasedAt == null) should appear in the snapshot.</summary>
    [Fact]
    public async Task FromConfigDb_ActiveReservations_excludes_released_rows()
    {
        var equipmentUuid = Guid.NewGuid();

        _db.ExternalIdReservations.Add(new ExternalIdReservation
        {
            ReservationId    = Guid.NewGuid(),
            Kind             = ReservationKind.ZTag,
            Value            = "ZT-001",
            EquipmentUuid    = equipmentUuid,
            ClusterId        = "cluster-a",
            FirstPublishedBy = "test",
            ReleasedAt       = null,         // active
        });
        _db.ExternalIdReservations.Add(new ExternalIdReservation
        {
            ReservationId    = Guid.NewGuid(),
            Kind             = ReservationKind.ZTag,
            Value            = "ZT-002",
            EquipmentUuid    = equipmentUuid,
            ClusterId        = "cluster-a",
            FirstPublishedBy = "test",
            ReleasedAt       = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc),  // released
            ReleasedBy       = "test",
            ReleaseReason    = "retired",
        });
        await _db.SaveChangesAsync();

        var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);

        snapshot.ActiveReservations.Count.ShouldBe(1);
        snapshot.ActiveReservations[0].Value.ShouldBe("ZT-001");
        snapshot.ActiveReservations[0].ReleasedAt.ShouldBeNull();
    }

    private void SeedEquipment(string equipmentId)
    {
        var uuid = Guid.NewGuid();
        _db.Equipment.Add(new Equipment
        {
            EquipmentUuid = uuid,
            EquipmentId = equipmentId,
            Name = "eq",
            UnsLineId = "line-a",
            MachineCode = "m",
        });
    }

    // v3: Tags are raw-only (DeviceId FK, no EquipmentId/DriverInstanceId).
    private static Tag BuildTag(string name) => new()
    {
        TagId = $"tag-{name}",
        DeviceId = "dev-1",
        Name = name,
        DataType = "Float",
        AccessLevel = TagAccessLevel.Read,
        TagConfig = "{}",
    };

    private static VirtualTag BuildVirtualTag(string equipmentId, string name, string suffix = "") => new()
    {
        VirtualTagId = $"vtag-{name}{suffix}",
        EquipmentId = equipmentId,
        Name = name,
        DataType = "Float",
        ScriptId = "s-1",
    };
}
