using Opc.Ua;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
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 AuthorizationGateTests
{
    private static NodeScope Scope(string cluster = "c1", string? tag = "tag1") => new()
    {
        ClusterId = cluster,
        NamespaceId = "ns",
        UnsAreaId = "area",
        UnsLineId = "line",
        EquipmentId = "eq",
        TagId = tag,
        Kind = NodeHierarchyKind.Equipment,
    };

    private static NodeAcl Row(string group, NodePermissions flags) => new()
    {
        NodeAclRowId = Guid.NewGuid(),
        NodeAclId = Guid.NewGuid().ToString(),
        GenerationId = 1,
        ClusterId = "c1",
        LdapGroup = group,
        ScopeKind = NodeAclScopeKind.Cluster,
        ScopeId = null,
        PermissionFlags = flags,
    };

    private static AuthorizationGate MakeGate(bool strict, NodeAcl[] rows)
    {
        var cache = new PermissionTrieCache();
        cache.Install(PermissionTrieBuilder.Build("c1", 1, rows));
        var evaluator = new TriePermissionEvaluator(cache);
        return new AuthorizationGate(evaluator, strictMode: strict);
    }

    private sealed class FakeIdentity : UserIdentity, ILdapGroupsBearer
    {
        public FakeIdentity(string name, IReadOnlyList<string> groups)
        {
            DisplayName = name;
            LdapGroups = groups;
        }
        public new string DisplayName { get; }
        public IReadOnlyList<string> LdapGroups { get; }
    }

    [Fact]
    public void NullIdentity_StrictMode_Denies()
    {
        var gate = MakeGate(strict: true, rows: []);
        gate.IsAllowed(null, OpcUaOperation.Read, Scope()).ShouldBeFalse();
    }

    [Fact]
    public void NullIdentity_LaxMode_Allows()
    {
        var gate = MakeGate(strict: false, rows: []);
        gate.IsAllowed(null, OpcUaOperation.Read, Scope()).ShouldBeTrue();
    }

    [Fact]
    public void IdentityWithoutLdapGroups_StrictMode_Denies()
    {
        var gate = MakeGate(strict: true, rows: []);
        var identity = new UserIdentity(); // anonymous, no LDAP groups

        gate.IsAllowed(identity, OpcUaOperation.Read, Scope()).ShouldBeFalse();
    }

    [Fact]
    public void IdentityWithoutLdapGroups_LaxMode_Allows()
    {
        var gate = MakeGate(strict: false, rows: []);
        var identity = new UserIdentity();

        gate.IsAllowed(identity, OpcUaOperation.Read, Scope()).ShouldBeTrue();
    }

    [Fact]
    public void LdapGroupWithGrant_Allows()
    {
        var gate = MakeGate(strict: true, rows: [Row("cn=ops", NodePermissions.Read)]);
        var identity = new FakeIdentity("ops-user", ["cn=ops"]);

        gate.IsAllowed(identity, OpcUaOperation.Read, Scope()).ShouldBeTrue();
    }

    [Fact]
    public void LdapGroupWithoutGrant_StrictMode_Denies()
    {
        var gate = MakeGate(strict: true, rows: [Row("cn=ops", NodePermissions.Read)]);
        var identity = new FakeIdentity("other-user", ["cn=other"]);

        gate.IsAllowed(identity, OpcUaOperation.Read, Scope()).ShouldBeFalse();
    }

    [Fact]
    public void WrongOperation_Denied()
    {
        var gate = MakeGate(strict: true, rows: [Row("cn=ops", NodePermissions.Read)]);
        var identity = new FakeIdentity("ops-user", ["cn=ops"]);

        gate.IsAllowed(identity, OpcUaOperation.WriteOperate, Scope()).ShouldBeFalse();
    }

    [Fact]
    public void BuildSessionState_IncludesLdapGroups()
    {
        var gate = MakeGate(strict: true, rows: []);
        var identity = new FakeIdentity("u", ["cn=a", "cn=b"]);

        var state = gate.BuildSessionState(identity, "c1");

        state.ShouldNotBeNull();
        state!.LdapGroups.Count.ShouldBe(2);
        state.ClusterId.ShouldBe("c1");
    }

    [Fact]
    public void BuildSessionState_ReturnsNull_ForIdentityWithoutLdapGroups()
    {
        var gate = MakeGate(strict: true, rows: []);

        gate.BuildSessionState(new UserIdentity(), "c1").ShouldBeNull();
    }
}
