using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;

/// <summary>
/// v3 Batch-1 migration of the former equipment-bound Tag CRUD tests. Per-equipment tag authoring
/// was RETIRED: <see cref="Tag"/> is now Raw-only (no <c>EquipmentId</c>/<c>DriverInstanceId</c>/
/// <c>FolderPath</c>), the <c>Namespace</c>/<c>NamespaceKind</c> entities were deleted, and tag
/// authoring moved to the Raw tree (<c>/raw</c>) in Batch 2. So <see cref="UnsTreeService"/>'s
/// <c>CreateTagAsync</c> / <c>UpdateTagAsync</c> now return a uniform Batch-1 refusal, and
/// <c>LoadTagsForEquipmentAsync</c> / <c>LoadTagDriversForEquipmentAsync</c> return empty. The tests
/// below assert that stub contract (preserving the intent of the old CRUD-guard / driver-loader
/// tests) plus the still-live <c>DeleteTagAsync</c> path (which continues to remove a raw Tag row).
/// </summary>
/// <remarks>
/// The former TagConfig-JSON-validity, decision-#122 driver-cluster guard, Equipment-kind-namespace
/// guard, and duplicate-id/name guards moved with the authoring surface to the Raw tree — re-covered
/// under the Batch-2 Raw-tree service tests.
/// </remarks>
[Trait("Category", "Unit")]
public sealed class UnsTreeServiceTagTests
{
    /// <summary>The Batch-1 refusal message the retired per-equipment tag authoring now returns.</summary>
    private const string TagAuthoringMovedError = "Tag authoring moved to the Raw tree (/raw) in v3 Batch 2.";

    private static (UnsTreeService Service, string DbName) Fresh()
    {
        var dbName = $"uns-tag-{Guid.NewGuid():N}";
        return (new UnsTreeService(UnsTreeTestDb.Factory(dbName)), dbName);
    }

    /// <summary>Seeds an area→line→equipment path in <paramref name="cluster"/> under equipment <c>EQ-1</c>.</summary>
    private static void SeedHierarchy(string dbName, string cluster = "MAIN")
    {
        using var db = UnsTreeTestDb.CreateNamed(dbName);
        db.ServerClusters.Add(new ServerCluster
        {
            ClusterId = cluster,
            Name = cluster,
            Enterprise = "zb",
            Site = "s",
            RedundancyMode = RedundancyMode.None,
            CreatedBy = "test",
        });
        db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-1", ClusterId = cluster, Name = "a" });
        db.UnsLines.Add(new UnsLine { UnsLineId = "LINE-1", UnsAreaId = "AREA-1", Name = "l" });
        db.Equipment.Add(new Equipment
        {
            EquipmentId = "EQ-1",
            EquipmentUuid = Guid.NewGuid(),
            UnsLineId = "LINE-1",
            Name = "machine-1",
            MachineCode = "machine_001",
        });
        db.SaveChanges();
    }

    private static TagInput Input(string tagId, string name, string tagConfig = "{}") =>
        new(tagId, name, "DRV-EQ", DataType: "Float",
            AccessLevel: TagAccessLevel.Read, WriteIdempotent: false,
            PollGroupId: null, TagConfig: tagConfig);

    // ----- CreateTag / UpdateTag: retired → Batch-1 refusal -----

    /// <summary>Per-equipment tag creation is retired: the service returns the Batch-1 "moved to Raw tree" refusal.</summary>
    [Fact]
    public async Task CreateTag_is_retired_returns_batch1_stub()
    {
        var (service, dbName) = Fresh();
        SeedHierarchy(dbName);

        var result = await service.CreateTagAsync("EQ-1", Input("TAG-1", "speed"));

        result.Ok.ShouldBeFalse();
        result.Error.ShouldBe(TagAuthoringMovedError);

        using var db = UnsTreeTestDb.CreateNamed(dbName);
        db.Tags.Any(t => t.TagId == "TAG-1").ShouldBeFalse();
    }

    /// <summary>The refusal is uniform — it does not depend on equipment resolvability or TagConfig validity.</summary>
    [Fact]
    public async Task CreateTag_refusal_is_uniform_even_for_unknown_equipment_or_bad_json()
    {
        var (service, dbName) = Fresh();
        SeedHierarchy(dbName);

        (await service.CreateTagAsync("EQ-NOPE", Input("TAG-1", "speed"))).Error
            .ShouldBe(TagAuthoringMovedError);
        (await service.CreateTagAsync("EQ-1", Input("TAG-2", "speed", tagConfig: "{ not json"))).Error
            .ShouldBe(TagAuthoringMovedError);
    }

    /// <summary>Per-equipment tag update is retired: the service returns the Batch-1 "moved to Raw tree" refusal.</summary>
    [Fact]
    public async Task UpdateTag_is_retired_returns_batch1_stub()
    {
        var (service, _) = Fresh();

        var result = await service.UpdateTagAsync("TAG-1", Input("TAG-1", "renamed"), []);

        result.Ok.ShouldBeFalse();
        result.Error.ShouldBe(TagAuthoringMovedError);
    }

    // ----- DeleteTag: still live against a raw Tag row -----

    /// <summary>Deleting a raw tag removes the row (DeleteTagAsync remains functional in Batch 1).</summary>
    [Fact]
    public async Task DeleteTag_removes_row()
    {
        var (service, dbName) = Fresh();
        byte[] rv;
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.Devices.Add(new Device
            {
                DeviceId = "DEV-1", DriverInstanceId = "DRV-1", Name = "d", DeviceConfig = "{}",
            });
            db.Tags.Add(new Tag
            {
                TagId = "TAG-1", DeviceId = "DEV-1", Name = "speed", DataType = "Float",
                AccessLevel = TagAccessLevel.Read, TagConfig = "{}",
            });
            db.SaveChanges();
            rv = db.Tags.Single(t => t.TagId == "TAG-1").RowVersion;
        }

        var result = await service.DeleteTagAsync("TAG-1", rv);

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();

        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.Tags.Any(t => t.TagId == "TAG-1").ShouldBeFalse();
    }

    /// <summary>Deleting a tag that is already gone is a no-op success.</summary>
    [Fact]
    public async Task DeleteTag_already_gone_returns_ok()
    {
        var (service, _) = Fresh();

        var result = await service.DeleteTagAsync("TAG-ghost", []);

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();
    }

    // ----- Per-equipment loaders: retired → empty -----

    /// <summary>The per-equipment tag list is retired (Tag is Raw-only) → always empty.</summary>
    [Fact]
    public async Task LoadTagsForEquipment_returns_empty()
    {
        var (service, dbName) = Fresh();
        SeedHierarchy(dbName);

        (await service.LoadTagsForEquipmentAsync("EQ-1")).ShouldBeEmpty();
    }

    /// <summary>The per-equipment candidate-driver loader is retired (equipment↔driver binding gone) → always empty.</summary>
    [Fact]
    public async Task LoadTagDriversForEquipment_returns_empty()
    {
        var (service, dbName) = Fresh();
        SeedHierarchy(dbName);

        (await service.LoadTagDriversForEquipmentAsync("EQ-1")).ShouldBeEmpty();
    }
}
