using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Admin.Services;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;

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

[Trait("Category", "Unit")]
public sealed class UnsServiceMoveTests
{
    [Fact]
    public async Task LoadSnapshotAsync_ReturnsAllAreasAndLines_WithEquipmentCounts()
    {
        using var ctx = NewContext();
        Seed(ctx, draftId: 1, areas: new[] { "area-1", "area-2" },
            lines: new[] { ("line-a", "area-1"), ("line-b", "area-1"), ("line-c", "area-2") },
            equipmentLines: new[] { "line-a", "line-a", "line-b" });
        var svc = new UnsService(ctx);

        var snap = await svc.LoadSnapshotAsync(1, CancellationToken.None);

        snap.Areas.Count.ShouldBe(2);
        snap.Lines.Count.ShouldBe(3);
        snap.FindLine("line-a")!.EquipmentCount.ShouldBe(2);
        snap.FindLine("line-b")!.EquipmentCount.ShouldBe(1);
        snap.FindLine("line-c")!.EquipmentCount.ShouldBe(0);
    }

    [Fact]
    public async Task LoadSnapshotAsync_RevisionToken_IsStable_BetweenTwoReads()
    {
        using var ctx = NewContext();
        Seed(ctx, draftId: 1, areas: new[] { "area-1" }, lines: new[] { ("line-a", "area-1") });
        var svc = new UnsService(ctx);

        var first = await svc.LoadSnapshotAsync(1, CancellationToken.None);
        var second = await svc.LoadSnapshotAsync(1, CancellationToken.None);

        second.RevisionToken.Matches(first.RevisionToken).ShouldBeTrue();
    }

    [Fact]
    public async Task LoadSnapshotAsync_RevisionToken_Changes_When_LineAdded()
    {
        using var ctx = NewContext();
        Seed(ctx, draftId: 1, areas: new[] { "area-1" }, lines: new[] { ("line-a", "area-1") });
        var svc = new UnsService(ctx);

        var before = await svc.LoadSnapshotAsync(1, CancellationToken.None);
        await svc.AddLineAsync(1, "area-1", "new-line", null, CancellationToken.None);
        var after = await svc.LoadSnapshotAsync(1, CancellationToken.None);

        after.RevisionToken.Matches(before.RevisionToken).ShouldBeFalse();
    }

    [Fact]
    public async Task MoveLineAsync_WithMatchingToken_Reparents_Line()
    {
        using var ctx = NewContext();
        Seed(ctx, draftId: 1, areas: new[] { "area-1", "area-2" },
            lines: new[] { ("line-a", "area-1") });
        var svc = new UnsService(ctx);

        var snap = await svc.LoadSnapshotAsync(1, CancellationToken.None);
        await svc.MoveLineAsync(1, snap.RevisionToken, "line-a", "area-2", CancellationToken.None);

        var moved = await ctx.UnsLines.AsNoTracking().FirstAsync(l => l.UnsLineId == "line-a");
        moved.UnsAreaId.ShouldBe("area-2");
    }

    [Fact]
    public async Task MoveLineAsync_WithStaleToken_Throws_DraftRevisionConflict()
    {
        using var ctx = NewContext();
        Seed(ctx, draftId: 1, areas: new[] { "area-1", "area-2" },
            lines: new[] { ("line-a", "area-1") });
        var svc = new UnsService(ctx);

        // Simulate a peer operator's concurrent edit between our preview + commit.
        var stale = new DraftRevisionToken("0000000000000000");

        await Should.ThrowAsync<DraftRevisionConflictException>(() =>
            svc.MoveLineAsync(1, stale, "line-a", "area-2", CancellationToken.None));

        var row = await ctx.UnsLines.AsNoTracking().FirstAsync(l => l.UnsLineId == "line-a");
        row.UnsAreaId.ShouldBe("area-1");
    }

    private static void Seed(OtOpcUaConfigDbContext ctx, long draftId,
        IEnumerable<string> areas,
        IEnumerable<(string line, string area)> lines,
        IEnumerable<string>? equipmentLines = null)
    {
        foreach (var a in areas)
        {
            ctx.UnsAreas.Add(new UnsArea
            {
                GenerationId = draftId, UnsAreaId = a, ClusterId = "c1", Name = a,
            });
        }
        foreach (var (line, area) in lines)
        {
            ctx.UnsLines.Add(new UnsLine
            {
                GenerationId = draftId, UnsLineId = line, UnsAreaId = area, Name = line,
            });
        }
        foreach (var lineId in equipmentLines ?? [])
        {
            ctx.Equipment.Add(new Equipment
            {
                EquipmentRowId = Guid.NewGuid(), GenerationId = draftId,
                EquipmentId = $"EQ-{Guid.NewGuid():N}"[..15],
                EquipmentUuid = Guid.NewGuid(), DriverInstanceId = "drv",
                UnsLineId = lineId, Name = "x", MachineCode = "m",
            });
        }
        ctx.SaveChanges();
    }

    private static OtOpcUaConfigDbContext NewContext()
    {
        var opts = new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        return new OtOpcUaConfigDbContext(opts);
    }
}
