using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;

namespace ZB.MOM.WW.ScadaBridge.Security;

/// <summary>
/// Centralised authorization policy names + the role→permission mapping
/// that defines them.
///
/// <para>
/// The codebase uses a thin role-claim model: each policy expresses a
/// permission, satisfied when the principal carries any role claim
/// (<see cref="JwtTokenService.RoleClaimType"/>) that maps to that
/// permission. Role names are free strings configured via
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Security.LdapGroupMapping"/> rows
/// (see <see cref="RoleMapper"/>) — there is no permission claim, just a
/// fan-out from role to allowed policies.
/// </para>
///
/// <para>
/// Default role → permission mapping, post canonicalization + SoD collapse:
/// <list type="table">
///   <listheader>
///     <term>Role</term>
///     <description>Policies granted</description>
///   </listheader>
///   <item>
///     <term><c>Administrator</c></term>
///     <description><see cref="RequireAdmin"/>,
///     <see cref="OperationalAudit"/>, <see cref="AuditExport"/> — admins hold
///     every permission by convention so an Administrator-only user never loses
///     access to a new surface.</description>
///   </item>
///   <item>
///     <term><c>Designer</c></term>
///     <description><see cref="RequireDesign"/></description>
///   </item>
///   <item>
///     <term><c>Deployer</c></term>
///     <description><see cref="RequireDeployment"/></description>
///   </item>
///   <item>
///     <term><c>Viewer</c></term>
///     <description><see cref="OperationalAudit"/> only — read access to the
///     Audit Log + nav, but NOT <see cref="AuditExport"/>. This preserves the
///     half-SoD that the legacy <c>AuditReadOnly</c> role provided (read-not-
///     export) after <c>AuditReadOnly</c> was collapsed into
///     <c>Viewer</c>.</description>
///   </item>
/// </list>
/// <para>
/// SoD collapse: the legacy distinct audit roles were removed. The
/// former <c>Audit</c> role (full audit surface = read + bulk export) was
/// collapsed into <c>Administrator</c> — a deliberate, accepted privilege
/// escalation (former audit-only users gain the entire admin surface: create
/// sites, manage LDAP mappings/API keys, import bundles). The former
/// <c>AuditReadOnly</c> role (read-only audit) was collapsed into
/// <c>Viewer</c>, which keeps audit-read but correctly LACKS export. The net
/// effect on the audit policies: <see cref="OperationalAudit"/> is granted to
/// {<c>Administrator</c>, <c>Viewer</c>} and <see cref="AuditExport"/> only to
/// {<c>Administrator</c>}.
/// </para>
/// LDAP group → role mapping is configured via the central UI Admin → LDAP
/// Mappings page (rows in <c>LdapGroupMappings</c>); the same code path
/// reads them whether the role is one of the built-ins above or any
/// future addition. Adding a role here means adding the LDAP mapping row in
/// the deployment; no schema migration is needed.
/// </para>
/// </summary>
public static class AuthorizationPolicies
{
    public const string RequireAdmin = "RequireAdmin";
    public const string RequireDesign = "RequireDesign";
    public const string RequireDeployment = "RequireDeployment";

    /// <summary>
    /// Permission to initiate a two-person Secured Write.
    /// Satisfied by the <see cref="Roles.Operator"/> role claim. Single-role
    /// policy mirroring <see cref="RequireAdmin"/>.
    /// </summary>
    public const string RequireOperator = "RequireOperator";

    /// <summary>
    /// Permission to approve a two-person Secured Write.
    /// Satisfied by the <see cref="Roles.Verifier"/> role claim. Kept distinct
    /// from <see cref="RequireOperator"/> so initiation and approval are
    /// separable duties.
    /// </summary>
    public const string RequireVerifier = "RequireVerifier";

    /// <summary>
    /// Read access to the Secured Writes surface (the Secured Writes page +
    /// its Pending/history list). The history table exposes process-sensitive
    /// tag values, so it is NOT open to any authenticated user — satisfied by
    /// any of {<see cref="Roles.Operator"/>, <see cref="Roles.Verifier"/>,
    /// <see cref="Roles.Administrator"/>} (arch-review UA1). Aligns with the
    /// ManagementActor's <c>ListSecuredWritesCommand</c> any-of gate.
    /// </summary>
    public const string RequireSecuredWriteAccess = "RequireSecuredWriteAccess";

    /// <summary>
    /// Roles that satisfy <see cref="RequireSecuredWriteAccess"/>. Held in one
    /// place so the policy, the docs, and the ManagementActor gate stay in
    /// lockstep: {<c>Operator</c>, <c>Verifier</c>, <c>Administrator</c>}.
    /// </summary>
    public static readonly string[] SecuredWriteAccessRoles =
        { Roles.Operator, Roles.Verifier, Roles.Administrator };

    /// <summary>
    /// Read access to the Audit Log surface (Audit Log page,
    /// Configuration Audit Log page, Audit nav group). Granted to the
    /// <c>Administrator</c> role and the <c>Viewer</c> role (the latter being
    /// the home of the former <c>AuditReadOnly</c> role).
    /// </summary>
    public const string OperationalAudit = "OperationalAudit";

    /// <summary>
    /// Permission to pull a bulk CSV export of the Audit Log. Separate from
    /// <see cref="OperationalAudit"/> so a <c>Viewer</c> can read the
    /// table without being able to exfiltrate it in bulk. Granted to the
    /// <c>Administrator</c> role only.
    /// </summary>
    public const string AuditExport = "AuditExport";

    /// <summary>
    /// Roles that satisfy <see cref="OperationalAudit"/>. Held in one place
    /// so the seed/docs and the policy stay in lockstep.
    /// </summary>
    /// <remarks>
    /// Public so the ManagementService HTTP API — which gates the
    /// <c>/api/audit/*</c> routes with a manual Basic-Auth + LDAP role check
    /// rather than the ASP.NET authorization-policy pipeline — can reuse the
    /// exact same role set the <see cref="OperationalAudit"/> policy enforces.
    /// {<c>Administrator</c>, <c>Viewer</c>} (was {Admin, Audit,
    /// AuditReadOnly} — the audit roles collapsed into Administrator/Viewer).
    /// </remarks>
    public static readonly string[] OperationalAuditRoles = { Roles.Administrator, Roles.Viewer };

    /// <summary>
    /// Roles that satisfy <see cref="AuditExport"/>. A strict subset of
    /// <see cref="OperationalAuditRoles"/> — read access does NOT imply
    /// export permission, so <c>Viewer</c> can read but not export.
    /// </summary>
    /// <remarks>
    /// Public for the same reason as <see cref="OperationalAuditRoles"/> —
    /// the ManagementService <c>/api/audit/export</c> route checks roles
    /// against this set directly. {<c>Administrator</c>} (was
    /// {Admin, Audit}).
    /// </remarks>
    public static readonly string[] AuditExportRoles = { Roles.Administrator };

    /// <summary>
    /// Registers the ScadaBridge authorization policies (Admin, Design, Deployment, OperationalAudit, AuditExport).
    /// </summary>
    /// <param name="services">The service collection to register into.</param>
    /// <returns>The same <paramref name="services"/> instance, for call chaining.</returns>
    public static IServiceCollection AddScadaBridgeAuthorization(this IServiceCollection services)
    {
        services.AddAuthorization(options =>
        {
            options.AddPolicy(RequireAdmin, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Administrator));

            options.AddPolicy(RequireDesign, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Designer));

            options.AddPolicy(RequireDeployment, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Deployer));

            options.AddPolicy(RequireOperator, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Operator));

            options.AddPolicy(RequireVerifier, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Verifier));

            // Any-of read gate for the Secured Writes surface (arch-review UA1):
            // succeeds when the principal holds any of Operator/Verifier/
            // Administrator, mirroring the ManagementActor ListSecuredWrites gate.
            options.AddPolicy(RequireSecuredWriteAccess, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, SecuredWriteAccessRoles));

            // Multi-role permission policies — the policy succeeds when the
            // principal holds ANY of the mapped roles. RequireClaim with
            // multiple allowed values is the right primitive: it checks
            // whether *any* role claim's value is in the allowed set, so a
            // user with role=Administrator (and nothing else) satisfies the
            // OperationalAudit policy, and a user with role=Viewer satisfies
            // OperationalAudit but not AuditExport.
            options.AddPolicy(OperationalAudit, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, OperationalAuditRoles));

            options.AddPolicy(AuditExport, policy =>
                policy.RequireClaim(JwtTokenService.RoleClaimType, AuditExportRoles));
        });

        return services;
    }
}
