using Opc.Ua;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Authorization;

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

/// <summary>
///     Bridges the OPC UA stack's <see cref="ISystemContext.UserIdentity"/> to the
///     <see cref="IPermissionEvaluator"/> evaluator. Resolves the session's
///     <see cref="UserAuthorizationState"/> from whatever the identity claims + the stack's
///     session handle, then delegates to the evaluator and returns a single bool the
///     dispatch paths can use to short-circuit with <c>BadUserAccessDenied</c>.
/// </summary>
/// <remarks>
///     <para>This class is deliberately the single integration seam between the Server
///     project and the <c>Core.Authorization</c> evaluator. DriverNodeManager holds one
///     reference and calls <see cref="IsAllowed"/> on every Read / Write / HistoryRead /
///     Browse / Call / CreateMonitoredItems / etc. The evaluator itself stays pure — it
///     doesn't know about the OPC UA stack types.</para>
///
///     <para>Fail-open-during-transition: when the evaluator is configured with
///     <c>StrictMode = false</c>, missing cluster tries OR sessions without resolved
///     LDAP groups get <c>true</c> so existing deployments keep working while ACLs are
///     populated. Flip to strict via <c>Authorization:StrictMode = true</c> in production.</para>
/// </remarks>
public sealed class AuthorizationGate
{
    private readonly IPermissionEvaluator _evaluator;
    private readonly bool _strictMode;
    private readonly TimeProvider _timeProvider;

    public AuthorizationGate(IPermissionEvaluator evaluator, bool strictMode = false, TimeProvider? timeProvider = null)
    {
        _evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator));
        _strictMode = strictMode;
        _timeProvider = timeProvider ?? TimeProvider.System;
    }

    /// <summary>True when strict authorization is enabled — no-grant = denied.</summary>
    public bool StrictMode => _strictMode;

    /// <summary>
    ///     Authorize an OPC UA operation against the session identity + scope. Returns true to
    ///     allow the dispatch to continue; false to surface <c>BadUserAccessDenied</c>.
    /// </summary>
    public bool IsAllowed(IUserIdentity? identity, OpcUaOperation operation, NodeScope scope)
    {
        // Anonymous / unknown identity — strict mode denies, lax mode allows so the fallback
        // auth layers (WriteAuthzPolicy) still see the call.
        if (identity is null) return !_strictMode;

        var session = BuildSessionState(identity, scope.ClusterId);
        if (session is null)
        {
            // Identity doesn't carry LDAP groups. In lax mode let the dispatch proceed so
            // older deployments keep working; strict mode denies.
            return !_strictMode;
        }

        var decision = _evaluator.Authorize(session, operation, scope);
        if (decision.IsAllowed) return true;

        return !_strictMode;
    }

    /// <summary>
    ///     Materialize a <see cref="UserAuthorizationState"/> from the session identity.
    ///     Returns null when the identity doesn't carry LDAP group metadata.
    /// </summary>
    public UserAuthorizationState? BuildSessionState(IUserIdentity identity, string clusterId)
    {
        if (identity is not ILdapGroupsBearer bearer || bearer.LdapGroups.Count == 0)
            return null;

        var sessionId = identity.DisplayName ?? Guid.NewGuid().ToString("N");
        return new UserAuthorizationState
        {
            SessionId = sessionId,
            ClusterId = clusterId,
            LdapGroups = bearer.LdapGroups,
            MembershipResolvedUtc = _timeProvider.GetUtcNow().UtcDateTime,
            AuthGenerationId = 0,
            MembershipVersion = 0,
        };
    }
}
