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

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

[Trait("Category", "Unit")]
public sealed class WriteAuthzPolicyTests
{
    // --- FreeAccess and ViewOnly special-cases ---

    [Fact]
    public void FreeAccess_allows_write_even_for_empty_role_set()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.FreeAccess, []).ShouldBeTrue();
    }

    [Fact]
    public void FreeAccess_allows_write_for_arbitrary_roles()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.FreeAccess, ["SomeOtherRole"]).ShouldBeTrue();
    }

    [Fact]
    public void ViewOnly_denies_write_even_with_every_role()
    {
        var allRoles = new[] { "WriteOperate", "WriteTune", "WriteConfigure", "AlarmAck" };
        WriteAuthzPolicy.IsAllowed(SecurityClassification.ViewOnly, allRoles).ShouldBeFalse();
    }

    // --- Operate tier ---

    [Fact]
    public void Operate_requires_WriteOperate_role()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["WriteOperate"]).ShouldBeTrue();
    }

    [Fact]
    public void Operate_role_match_is_case_insensitive()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["writeoperate"]).ShouldBeTrue();
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["WRITEOPERATE"]).ShouldBeTrue();
    }

    [Fact]
    public void Operate_denies_empty_role_set()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, []).ShouldBeFalse();
    }

    [Fact]
    public void Operate_denies_wrong_role()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["ReadOnly"]).ShouldBeFalse();
    }

    [Fact]
    public void SecuredWrite_maps_to_same_WriteOperate_requirement_as_Operate()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.SecuredWrite, ["WriteOperate"]).ShouldBeTrue();
        WriteAuthzPolicy.IsAllowed(SecurityClassification.SecuredWrite, ["WriteTune"]).ShouldBeFalse();
    }

    // --- Tune tier ---

    [Fact]
    public void Tune_requires_WriteTune_role()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Tune, ["WriteTune"]).ShouldBeTrue();
    }

    [Fact]
    public void Tune_denies_WriteOperate_only_session()
    {
        // Important: role roles do NOT cascade — a session with WriteOperate can't write a Tune
        // attribute. Operators escalate by adding WriteTune to the session's roles, not by a
        // hierarchy the policy infers on its own.
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Tune, ["WriteOperate"]).ShouldBeFalse();
    }

    // --- Configure tier ---

    [Fact]
    public void Configure_requires_WriteConfigure_role()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Configure, ["WriteConfigure"]).ShouldBeTrue();
    }

    [Fact]
    public void VerifiedWrite_maps_to_same_WriteConfigure_requirement_as_Configure()
    {
        WriteAuthzPolicy.IsAllowed(SecurityClassification.VerifiedWrite, ["WriteConfigure"]).ShouldBeTrue();
        WriteAuthzPolicy.IsAllowed(SecurityClassification.VerifiedWrite, ["WriteOperate"]).ShouldBeFalse();
    }

    // --- Multi-role sessions ---

    [Fact]
    public void Session_with_multiple_roles_is_allowed_when_any_matches()
    {
        var roles = new[] { "ReadOnly", "WriteTune", "AlarmAck" };
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Tune, roles).ShouldBeTrue();
    }

    [Fact]
    public void Session_with_only_unrelated_roles_is_denied()
    {
        var roles = new[] { "ReadOnly", "AlarmAck", "SomeCustomRole" };
        WriteAuthzPolicy.IsAllowed(SecurityClassification.Configure, roles).ShouldBeFalse();
    }

    // --- Mapping table ---

    [Theory]
    [InlineData(SecurityClassification.Operate, WriteAuthzPolicy.RoleWriteOperate)]
    [InlineData(SecurityClassification.SecuredWrite, WriteAuthzPolicy.RoleWriteOperate)]
    [InlineData(SecurityClassification.Tune, WriteAuthzPolicy.RoleWriteTune)]
    [InlineData(SecurityClassification.VerifiedWrite, WriteAuthzPolicy.RoleWriteConfigure)]
    [InlineData(SecurityClassification.Configure, WriteAuthzPolicy.RoleWriteConfigure)]
    public void RequiredRole_returns_expected_role_for_classification(SecurityClassification c, string expected)
    {
        WriteAuthzPolicy.RequiredRole(c).ShouldBe(expected);
    }

    [Theory]
    [InlineData(SecurityClassification.FreeAccess)]
    [InlineData(SecurityClassification.ViewOnly)]
    public void RequiredRole_returns_null_for_special_classifications(SecurityClassification c)
    {
        WriteAuthzPolicy.RequiredRole(c).ShouldBeNull();
    }
}
