using System.Runtime.CompilerServices;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Security;

namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests;

/// <summary>
/// Frozen authorization matrix (arch-review UA2). Every registered management command
/// MUST have an entry in <see cref="Expected"/> giving its required-roles set (any-of
/// semantics) or <c>null</c> when the command is available to any authenticated user.
/// A newly-added command with no entry fails <see cref="EveryRegisteredCommand_IsInTheFrozenTable"/>,
/// forcing a deliberate authorization decision instead of silently defaulting to the
/// read-only (any-authenticated) gate — the class of gap that produced C2/C4.
///
/// The table reflects ACTUAL current behavior of
/// <c>ManagementActor.GetRequiredRoles</c>; it is authored by hand from the switch and
/// cross-checked against <c>docs/requirements/Component-ManagementService.md</c>
/// §Authorization. Do not regenerate it mechanically from the source under test — that
/// would defeat the freeze.
/// </summary>
public class RequiredRoleMatrixTests
{
    private static readonly string[] AreaManagers = [Roles.Designer, Roles.Deployer];
    private static readonly string[] SecuredWriteReaders = [Roles.Operator, Roles.Verifier, Roles.Administrator];

    private static readonly Dictionary<string, string[]?> Expected = new(StringComparer.OrdinalIgnoreCase)
    {
        // ---- Administrator-only -----------------------------------------------------
        ["CreateSite"] = [Roles.Administrator],
        ["UpdateSite"] = [Roles.Administrator],
        ["DeleteSite"] = [Roles.Administrator],
        ["ListRoleMappings"] = [Roles.Administrator],
        ["CreateRoleMapping"] = [Roles.Administrator],
        ["UpdateRoleMapping"] = [Roles.Administrator],
        ["DeleteRoleMapping"] = [Roles.Administrator],
        ["ListApiKeys"] = [Roles.Administrator],
        ["CreateApiKey"] = [Roles.Administrator],
        ["DeleteApiKey"] = [Roles.Administrator],
        ["UpdateApiKey"] = [Roles.Administrator],
        ["SetApiKeyMethods"] = [Roles.Administrator],
        ["ListScopeRules"] = [Roles.Administrator],
        ["AddScopeRule"] = [Roles.Administrator],
        ["DeleteScopeRule"] = [Roles.Administrator],
        ["QueryAuditLog"] = [Roles.Administrator],
        ["UpdateSmtpConfig"] = [Roles.Administrator],
        ["UpdateSmsConfig"] = [Roles.Administrator],
        ["TrustServerCert"] = [Roles.Administrator],
        ["RemoveServerCert"] = [Roles.Administrator],
        ["ListServerCerts"] = [Roles.Administrator],
        // Transport inbound bundle handling mutates cross-cutting config → Admin.
        ["PreviewBundle"] = [Roles.Administrator],
        ["ImportBundle"] = [Roles.Administrator],

        // ---- Area management: any-of [Designer, Deployer] ---------------------------
        ["CreateArea"] = AreaManagers,
        ["UpdateArea"] = AreaManagers,
        // Re-parenting an area is the same structural authoring act as create /
        // rename / delete, so it carries the identical any-of gate.
        ["MoveArea"] = AreaManagers,
        ["DeleteArea"] = AreaManagers,

        // ---- Designer-only ----------------------------------------------------------
        ["CreateTemplate"] = [Roles.Designer],
        ["UpdateTemplate"] = [Roles.Designer],
        ["DeleteTemplate"] = [Roles.Designer],
        ["ValidateTemplate"] = [Roles.Designer],
        ["CreateExternalSystem"] = [Roles.Designer],
        ["UpdateExternalSystem"] = [Roles.Designer],
        ["DeleteExternalSystem"] = [Roles.Designer],
        ["CreateExternalSystemMethod"] = [Roles.Designer],
        ["UpdateExternalSystemMethod"] = [Roles.Designer],
        ["DeleteExternalSystemMethod"] = [Roles.Designer],
        ["CreateNotificationList"] = [Roles.Designer],
        ["UpdateNotificationList"] = [Roles.Designer],
        ["DeleteNotificationList"] = [Roles.Designer],
        ["CreateDataConnection"] = [Roles.Designer],
        ["UpdateDataConnection"] = [Roles.Designer],
        ["DeleteDataConnection"] = [Roles.Designer],
        ["MoveDataConnection"] = [Roles.Designer],
        ["AddTemplateAttribute"] = [Roles.Designer],
        ["UpdateTemplateAttribute"] = [Roles.Designer],
        ["DeleteTemplateAttribute"] = [Roles.Designer],
        ["AddTemplateAlarm"] = [Roles.Designer],
        ["UpdateTemplateAlarm"] = [Roles.Designer],
        ["DeleteTemplateAlarm"] = [Roles.Designer],
        ["AddTemplateNativeAlarmSource"] = [Roles.Designer],
        ["UpdateTemplateNativeAlarmSource"] = [Roles.Designer],
        ["DeleteTemplateNativeAlarmSource"] = [Roles.Designer],
        ["AddTemplateScript"] = [Roles.Designer],
        ["UpdateTemplateScript"] = [Roles.Designer],
        ["DeleteTemplateScript"] = [Roles.Designer],
        ["AddTemplateComposition"] = [Roles.Designer],
        ["DeleteTemplateComposition"] = [Roles.Designer],
        ["ResyncInheritedMembers"] = [Roles.Designer],
        ["CreateSharedScript"] = [Roles.Designer],
        ["UpdateSharedScript"] = [Roles.Designer],
        ["DeleteSharedScript"] = [Roles.Designer],
        ["CreateSharedSchema"] = [Roles.Designer],
        ["UpdateSharedSchema"] = [Roles.Designer],
        ["DeleteSharedSchema"] = [Roles.Designer],
        ["CreateDatabaseConnectionDef"] = [Roles.Designer],
        ["UpdateDatabaseConnectionDef"] = [Roles.Designer],
        ["DeleteDatabaseConnectionDef"] = [Roles.Designer],
        ["CreateApiMethod"] = [Roles.Designer],
        ["UpdateApiMethod"] = [Roles.Designer],
        ["DeleteApiMethod"] = [Roles.Designer],
        ["CreateTemplateFolder"] = [Roles.Designer],
        ["RenameTemplateFolder"] = [Roles.Designer],
        ["MoveTemplateFolder"] = [Roles.Designer],
        ["ReorderTemplateFolder"] = [Roles.Designer],
        ["DeleteTemplateFolder"] = [Roles.Designer],
        ["MoveTemplateToFolder"] = [Roles.Designer],
        ["BrowseNode"] = [Roles.Designer],
        ["SearchAddressSpace"] = [Roles.Designer],
        ["VerifyEndpoint"] = [Roles.Designer],
        ["ExportBundle"] = [Roles.Designer],

        // ---- Deployer-only ----------------------------------------------------------
        ["CreateInstance"] = [Roles.Deployer],
        ["MgmtDeployInstance"] = [Roles.Deployer],
        // Bulk site deploy (WP2.5). Same authority as the single-instance deploy
        // it batches — it is N of those, not a new class of privilege.
        ["MgmtDeploySite"] = [Roles.Deployer],
        ["MgmtEnableInstance"] = [Roles.Deployer],
        ["MgmtDisableInstance"] = [Roles.Deployer],
        ["MgmtDeleteInstance"] = [Roles.Deployer],
        ["SetConnectionBindings"] = [Roles.Deployer],
        ["SetInstanceOverrides"] = [Roles.Deployer],
        ["SetInstanceArea"] = [Roles.Deployer],
        ["SetInstanceAlarmOverride"] = [Roles.Deployer],
        ["DeleteInstanceAlarmOverride"] = [Roles.Deployer],
        ["SetInstanceNativeAlarmSourceOverride"] = [Roles.Deployer],
        ["SetInstanceNativeAlarmSourceOverrides"] = [Roles.Deployer],
        ["DeleteInstanceNativeAlarmSourceOverride"] = [Roles.Deployer],
        ["GetDeploymentDiff"] = [Roles.Deployer],
        ["MgmtDeployArtifacts"] = [Roles.Deployer],
        ["QueryDeployments"] = [Roles.Deployer],
        ["RetryParkedMessage"] = [Roles.Deployer],
        ["DiscardParkedMessage"] = [Roles.Deployer],
        ["DebugSnapshot"] = [Roles.Deployer],

        // ---- Two-person secured write ----------------------------------------------
        ["SubmitSecuredWrite"] = [Roles.Operator],
        ["ApproveSecuredWrite"] = [Roles.Verifier],
        ["RejectSecuredWrite"] = [Roles.Verifier],
        ["ListSecuredWrites"] = SecuredWriteReaders,

        // ---- Read-only: any authenticated user (null gate) -------------------------
        ["ListTemplates"] = null,
        ["GetTemplate"] = null,
        ["GetResolvedTemplateMembers"] = null,
        ["ListTemplateFolders"] = null,
        ["ListTemplateNativeAlarmSources"] = null,
        ["ListInstances"] = null,
        ["GetInstance"] = null,
        ["ListInstanceAlarmOverrides"] = null,
        ["ListInstanceNativeAlarmSourceOverrides"] = null,
        ["ListSites"] = null,
        ["GetSite"] = null,
        ["ListAreas"] = null,
        ["ListDataConnections"] = null,
        ["GetDataConnection"] = null,
        ["ListExternalSystems"] = null,
        ["GetExternalSystem"] = null,
        ["ListExternalSystemMethods"] = null,
        ["GetExternalSystemMethod"] = null,
        ["ListNotificationLists"] = null,
        ["GetNotificationList"] = null,
        ["ListSmtpConfigs"] = null,
        ["ListSmsConfigs"] = null,
        ["ListSharedScripts"] = null,
        ["GetSharedScript"] = null,
        ["ListSharedSchemas"] = null,
        ["GetSharedSchema"] = null,
        ["ListDatabaseConnections"] = null,
        ["GetDatabaseConnection"] = null,
        ["ListApiMethods"] = null,
        ["GetApiMethod"] = null,
        ["GetHealthSummary"] = null,
        ["GetSiteHealth"] = null,
        ["QueryEventLogs"] = null,
        ["QueryParkedMessages"] = null,
        ["ReadTagValues"] = null,
        // ResolveRoles is intentionally not dispatched (retired two-step flow); it
        // falls through to the default null gate. Present here so the frozen table
        // still covers the type reflected in the assembly.
        ["ResolveRoles"] = null,
    };

    public static IEnumerable<object[]> AllRegisteredCommands()
        => typeof(ManagementEnvelope).Assembly.GetTypes()
            .Where(t => t.Namespace == typeof(ManagementEnvelope).Namespace
                        && t.Name.EndsWith("Command", StringComparison.Ordinal) && !t.IsAbstract)
            .Select(t => new object[] { t });

    [Theory, MemberData(nameof(AllRegisteredCommands))]
    public void EveryRegisteredCommand_IsInTheFrozenTable(Type commandType)
    {
        var name = commandType.Name[..^"Command".Length];
        Assert.True(Expected.ContainsKey(name),
            $"Command '{name}' has no entry in the frozen authorization table — add one deliberately.");
        var instance = RuntimeHelpers.GetUninitializedObject(commandType);
        var actual = ManagementActor.GetRequiredRoles(instance);
        var expected = Expected[name];
        if (expected is null)
        {
            Assert.Null(actual);
        }
        else
        {
            Assert.NotNull(actual);
            Assert.Equal(expected.OrderBy(x => x), actual!.OrderBy(x => x));
        }
    }
}
