using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Authorization;
using ZB.MOM.WW.OtOpcUa.Server.Security;

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

[Trait("Category", "Unit")]
public sealed class NodeScopeResolverTests
{
    [Fact]
    public void Resolve_PopulatesClusterAndTag()
    {
        var resolver = new NodeScopeResolver("c-warsaw");

        var scope = resolver.Resolve("TestMachine_001/Oven/SetPoint");

        scope.ClusterId.ShouldBe("c-warsaw");
        scope.TagId.ShouldBe("TestMachine_001/Oven/SetPoint");
        scope.Kind.ShouldBe(NodeHierarchyKind.Equipment);
    }

    [Fact]
    public void Resolve_Leaves_UnsPath_Null_When_NoIndexSupplied()
    {
        var resolver = new NodeScopeResolver("c-1");

        var scope = resolver.Resolve("tag-1");

        // Cluster-only fallback path — used pre-ADR-001 and still the active path for
        // unindexed references (e.g. driver-discovered tags that have no Tag row yet).
        scope.NamespaceId.ShouldBeNull();
        scope.UnsAreaId.ShouldBeNull();
        scope.UnsLineId.ShouldBeNull();
        scope.EquipmentId.ShouldBeNull();
    }

    [Fact]
    public void Resolve_Returns_IndexedScope_When_FullReferenceFound()
    {
        var index = new Dictionary<string, NodeScope>
        {
            ["plcaddr-01"] = new NodeScope
            {
                ClusterId = "c-1", NamespaceId = "ns-plc", UnsAreaId = "area-1",
                UnsLineId = "line-a", EquipmentId = "eq-oven-3", TagId = "plcaddr-01",
                Kind = NodeHierarchyKind.Equipment,
            },
        };
        var resolver = new NodeScopeResolver("c-1", index);

        var scope = resolver.Resolve("plcaddr-01");

        scope.UnsAreaId.ShouldBe("area-1");
        scope.UnsLineId.ShouldBe("line-a");
        scope.EquipmentId.ShouldBe("eq-oven-3");
        scope.TagId.ShouldBe("plcaddr-01");
        scope.NamespaceId.ShouldBe("ns-plc");
    }

    [Fact]
    public void Resolve_FallsBack_To_ClusterOnly_When_Reference_NotIndexed()
    {
        var index = new Dictionary<string, NodeScope>
        {
            ["plcaddr-01"] = new NodeScope { ClusterId = "c-1", TagId = "plcaddr-01", Kind = NodeHierarchyKind.Equipment },
        };
        var resolver = new NodeScopeResolver("c-1", index);

        var scope = resolver.Resolve("not-in-index");

        scope.ClusterId.ShouldBe("c-1");
        scope.TagId.ShouldBe("not-in-index");
        scope.EquipmentId.ShouldBeNull();
    }

    [Fact]
    public void Resolve_Throws_OnEmptyFullReference()
    {
        var resolver = new NodeScopeResolver("c-1");

        Should.Throw<ArgumentException>(() => resolver.Resolve(""));
        Should.Throw<ArgumentException>(() => resolver.Resolve("   "));
    }

    [Fact]
    public void Ctor_Throws_OnEmptyClusterId()
    {
        Should.Throw<ArgumentException>(() => new NodeScopeResolver(""));
    }

    [Fact]
    public void Resolver_IsStateless_AcrossCalls()
    {
        var resolver = new NodeScopeResolver("c");
        var s1 = resolver.Resolve("tag-a");
        var s2 = resolver.Resolve("tag-b");

        s1.TagId.ShouldBe("tag-a");
        s2.TagId.ShouldBe("tag-b");
        s1.ClusterId.ShouldBe("c");
        s2.ClusterId.ShouldBe("c");
    }
}
