using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     Server-layer write-authorization policy. ACL enforcement lives here — drivers report
///     <see cref="SecurityClassification"/> as discovery metadata only; the server decides
///     whether a given session is allowed to write a given attribute by checking the session's
///     roles (resolved at login via <see cref="LdapUserAuthenticator"/>) against the required
///     role for the attribute's classification.
/// </summary>
/// <remarks>
///     Matches the table in <c>docs/Configuration.md</c>:
///     <list type="bullet">
///         <item><c>FreeAccess</c>: no role required — anonymous sessions can write (matches v1 default).</item>
///         <item><c>Operate</c> / <c>SecuredWrite</c>: <c>WriteOperate</c> role required.</item>
///         <item><c>Tune</c>: <c>WriteTune</c> role required.</item>
///         <item><c>VerifiedWrite</c> / <c>Configure</c>: <c>WriteConfigure</c> role required.</item>
///         <item><c>ViewOnly</c>: no role grants write access.</item>
///     </list>
///     <c>AlarmAck</c> is checked at the alarm-acknowledge path, not here.
/// </remarks>
public static class WriteAuthzPolicy
{
    public const string RoleWriteOperate = "WriteOperate";
    public const string RoleWriteTune = "WriteTune";
    public const string RoleWriteConfigure = "WriteConfigure";

    /// <summary>
    ///     Decide whether a session with <paramref name="userRoles"/> is allowed to write to an
    ///     attribute with the given <paramref name="classification"/>. Returns true for
    ///     <c>FreeAccess</c> regardless of roles (including empty / anonymous sessions) and
    ///     false for <c>ViewOnly</c> regardless of roles. Every other classification requires
    ///     the session to carry the mapped role — case-insensitive match.
    /// </summary>
    public static bool IsAllowed(SecurityClassification classification, IReadOnlyCollection<string> userRoles)
    {
        if (classification == SecurityClassification.FreeAccess) return true;
        if (classification == SecurityClassification.ViewOnly) return false;

        var required = RequiredRole(classification);
        if (required is null) return false;

        foreach (var r in userRoles)
        {
            if (string.Equals(r, required, StringComparison.OrdinalIgnoreCase))
                return true;
        }
        return false;
    }

    /// <summary>
    ///     Required role for a classification, or null when no role grants access
    ///     (<see cref="SecurityClassification.ViewOnly"/>) or no role is needed
    ///     (<see cref="SecurityClassification.FreeAccess"/> — also returns null; callers use
    ///     <see cref="IsAllowed"/> which handles the special-cases rather than branching on
    ///     null themselves).
    /// </summary>
    public static string? RequiredRole(SecurityClassification classification) => classification switch
    {
        SecurityClassification.FreeAccess => null, // IsAllowed short-circuits
        SecurityClassification.Operate => RoleWriteOperate,
        SecurityClassification.SecuredWrite => RoleWriteOperate,
        SecurityClassification.Tune => RoleWriteTune,
        SecurityClassification.VerifiedWrite => RoleWriteConfigure,
        SecurityClassification.Configure => RoleWriteConfigure,
        SecurityClassification.ViewOnly => null, // IsAllowed short-circuits
        _ => null,
    };

    /// <summary>
    ///     Maps a driver-reported <see cref="SecurityClassification"/> to the
    ///     <see cref="Core.Abstractions.OpcUaOperation"/> the Phase 6.2 evaluator consults
    ///     for the matching <see cref="Configuration.Enums.NodePermissions"/> bit.
    ///     FreeAccess + ViewOnly fall back to WriteOperate — the evaluator never sees them
    ///     because <see cref="IsAllowed"/> short-circuits first.
    /// </summary>
    public static Core.Abstractions.OpcUaOperation ToOpcUaOperation(SecurityClassification classification) =>
        classification switch
        {
            SecurityClassification.Operate => Core.Abstractions.OpcUaOperation.WriteOperate,
            SecurityClassification.SecuredWrite => Core.Abstractions.OpcUaOperation.WriteOperate,
            SecurityClassification.Tune => Core.Abstractions.OpcUaOperation.WriteTune,
            SecurityClassification.VerifiedWrite => Core.Abstractions.OpcUaOperation.WriteConfigure,
            SecurityClassification.Configure => Core.Abstractions.OpcUaOperation.WriteConfigure,
            _ => Core.Abstractions.OpcUaOperation.WriteOperate,
        };
}
