using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
using Akka.Actor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ScadaLink.Commons.Entities.ExternalSystems;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Entities.Instances;
using ScadaLink.Commons.Entities.Scripts;
using ScadaLink.Commons.Entities.Templates;
using ScadaLink.Commons.Entities.Notifications;
using ScadaLink.Commons.Entities.Security;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Commons.Messages.DebugView;
using ScadaLink.Commons.Messages.Management;
using ScadaLink.Commons.Messages.RemoteQuery;
using ScadaLink.DeploymentManager;
using ScadaLink.HealthMonitoring;
using ScadaLink.Communication;
using ScadaLink.Security;
using ScadaLink.TemplateEngine;
using ScadaLink.TemplateEngine.Services;

namespace ScadaLink.ManagementService;

/// <summary>
/// Central actor that handles all management commands from the CLI (via ClusterClient).
/// Receives ManagementEnvelope messages, authorizes based on roles, then delegates to
/// the appropriate service or repository using scoped DI.
/// </summary>
public class ManagementActor : ReceiveActor
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<ManagementActor> _logger;

    public ManagementActor(IServiceProvider serviceProvider, ILogger<ManagementActor> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
        Receive<ManagementEnvelope>(HandleEnvelope);
    }

    /// <summary>
    /// Serializer settings for command results. <see cref="ReferenceHandler.IgnoreCycles"/>
    /// keeps EF-backed entity graphs with bidirectional navigation properties from throwing;
    /// camelCase matches what the CLI / HTTP layer expect (finding ManagementService-007).
    /// </summary>
    private static readonly JsonSerializerOptions ResultSerializerOptions = new()
    {
        ReferenceHandler = ReferenceHandler.IgnoreCycles,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    /// <summary>
    /// Serializes a command result to JSON using <see cref="System.Text.Json"/> — the same
    /// serializer the HTTP endpoint uses — with cycle-safe settings.
    /// </summary>
    public static string SerializeResult(object? result) =>
        JsonSerializer.Serialize(result, ResultSerializerOptions);

    private void HandleEnvelope(ManagementEnvelope envelope)
    {
        var sender = Sender;
        var correlationId = envelope.CorrelationId;
        var user = envelope.User;

        // Check authorization
        var requiredRole = GetRequiredRole(envelope.Command);
        if (requiredRole != null && !user.Roles.Contains(requiredRole, StringComparer.OrdinalIgnoreCase))
        {
            sender.Tell(new ManagementUnauthorized(correlationId,
                $"Role '{requiredRole}' required for {envelope.Command.GetType().Name}"));
            return;
        }

        // Process the command and pipe the mapped result back to the captured sender.
        // PipeTo (rather than Task.Run + Tell-from-continuation) is the project's Akka.NET
        // convention: faults are mapped in the failure continuation, no actor state is
        // captured in the closure, and synchronous faults in command setup are still mapped.
        ProcessCommand(envelope, user)
            .PipeTo(sender,
                success: result => result,
                failure: ex => MapFault(ex, correlationId, envelope.Command));
    }

    /// <summary>
    /// Runs a command on a scoped service provider and maps the result to a management
    /// response message. Returns a faulted task on error so the PipeTo failure
    /// continuation maps it uniformly.
    /// </summary>
    private async Task<object> ProcessCommand(ManagementEnvelope envelope, AuthenticatedUser user)
    {
        using var scope = _serviceProvider.CreateScope();
        var result = await DispatchCommand(scope.ServiceProvider, envelope.Command, user);
        return new ManagementSuccess(envelope.CorrelationId, SerializeResult(result));
    }

    /// <summary>
    /// Maps an exception from command processing to the appropriate management response.
    /// </summary>
    private object MapFault(Exception ex, string correlationId, object command)
    {
        // PipeTo wraps continuation exceptions; unwrap to find the real cause.
        var cause = ex is AggregateException agg ? agg.Flatten().InnerException ?? ex : ex;

        if (cause is SiteScopeViolationException scope)
            return new ManagementUnauthorized(correlationId, scope.Message);

        _logger.LogError(cause, "Management command {Command} failed (CorrelationId={CorrelationId})",
            command.GetType().Name, correlationId);
        return new ManagementError(correlationId, cause.Message, "COMMAND_FAILED");
    }

    private static string? GetRequiredRole(object command) => command switch
    {
        // Admin operations
        CreateSiteCommand or UpdateSiteCommand or DeleteSiteCommand
            or ListRoleMappingsCommand or CreateRoleMappingCommand
            or UpdateRoleMappingCommand or DeleteRoleMappingCommand
            or ListApiKeysCommand or CreateApiKeyCommand or DeleteApiKeyCommand
            or UpdateApiKeyCommand
            or ListScopeRulesCommand or AddScopeRuleCommand or DeleteScopeRuleCommand => "Admin",

        // Design operations
        CreateAreaCommand or DeleteAreaCommand
            or CreateTemplateCommand or UpdateTemplateCommand or DeleteTemplateCommand
            or ValidateTemplateCommand
            or CreateExternalSystemCommand or UpdateExternalSystemCommand
            or DeleteExternalSystemCommand
            or CreateExternalSystemMethodCommand or UpdateExternalSystemMethodCommand
            or DeleteExternalSystemMethodCommand
            or CreateNotificationListCommand or UpdateNotificationListCommand
            or DeleteNotificationListCommand
            or UpdateSmtpConfigCommand
            or CreateDataConnectionCommand or UpdateDataConnectionCommand
            or DeleteDataConnectionCommand
            or AddTemplateAttributeCommand or UpdateTemplateAttributeCommand or DeleteTemplateAttributeCommand
            or AddTemplateAlarmCommand or UpdateTemplateAlarmCommand or DeleteTemplateAlarmCommand
            or AddTemplateScriptCommand or UpdateTemplateScriptCommand or DeleteTemplateScriptCommand
            or AddTemplateCompositionCommand or DeleteTemplateCompositionCommand
            or CreateSharedScriptCommand or UpdateSharedScriptCommand or DeleteSharedScriptCommand
            or CreateDatabaseConnectionDefCommand or UpdateDatabaseConnectionDefCommand or DeleteDatabaseConnectionDefCommand
            or CreateApiMethodCommand or UpdateApiMethodCommand or DeleteApiMethodCommand
            or UpdateAreaCommand
            or CreateTemplateFolderCommand or RenameTemplateFolderCommand
            or MoveTemplateFolderCommand or DeleteTemplateFolderCommand
            or MoveTemplateToFolderCommand => "Design",

        // Deployment operations
        CreateInstanceCommand or MgmtDeployInstanceCommand or MgmtEnableInstanceCommand
            or MgmtDisableInstanceCommand or MgmtDeleteInstanceCommand
            or SetConnectionBindingsCommand or SetInstanceOverridesCommand or SetInstanceAreaCommand
            or SetInstanceAlarmOverrideCommand or DeleteInstanceAlarmOverrideCommand
            or GetDeploymentDiffCommand
            or MgmtDeployArtifactsCommand
            or RetryParkedMessageCommand or DiscardParkedMessageCommand
            or DebugSnapshotCommand => "Deployment",

        // Read-only queries -- any authenticated user
        _ => null
    };

    private async Task<object?> DispatchCommand(IServiceProvider sp, object command, AuthenticatedUser user)
    {
        return command switch
        {
            // Templates
            ListTemplatesCommand => await HandleListTemplates(sp),
            GetTemplateCommand cmd => await HandleGetTemplate(sp, cmd),
            CreateTemplateCommand cmd => await HandleCreateTemplate(sp, cmd, user.Username),
            UpdateTemplateCommand cmd => await HandleUpdateTemplate(sp, cmd, user.Username),
            DeleteTemplateCommand cmd => await HandleDeleteTemplate(sp, cmd, user.Username),
            ValidateTemplateCommand cmd => await HandleValidateTemplate(sp, cmd),

            // Template members
            AddTemplateAttributeCommand cmd => await HandleAddAttribute(sp, cmd, user.Username),
            UpdateTemplateAttributeCommand cmd => await HandleUpdateAttribute(sp, cmd, user.Username),
            DeleteTemplateAttributeCommand cmd => await HandleDeleteAttribute(sp, cmd, user.Username),
            AddTemplateAlarmCommand cmd => await HandleAddAlarm(sp, cmd, user.Username),
            UpdateTemplateAlarmCommand cmd => await HandleUpdateAlarm(sp, cmd, user.Username),
            DeleteTemplateAlarmCommand cmd => await HandleDeleteAlarm(sp, cmd, user.Username),
            AddTemplateScriptCommand cmd => await HandleAddScript(sp, cmd, user.Username),
            UpdateTemplateScriptCommand cmd => await HandleUpdateScript(sp, cmd, user.Username),
            DeleteTemplateScriptCommand cmd => await HandleDeleteScript(sp, cmd, user.Username),
            AddTemplateCompositionCommand cmd => await HandleAddComposition(sp, cmd, user.Username),
            DeleteTemplateCompositionCommand cmd => await HandleDeleteComposition(sp, cmd, user.Username),

            // Template folders
            ListTemplateFoldersCommand => await HandleListTemplateFolders(sp),
            CreateTemplateFolderCommand cmd => await HandleCreateTemplateFolder(sp, cmd, user.Username),
            RenameTemplateFolderCommand cmd => await HandleRenameTemplateFolder(sp, cmd, user.Username),
            MoveTemplateFolderCommand cmd => await HandleMoveTemplateFolder(sp, cmd, user.Username),
            DeleteTemplateFolderCommand cmd => await HandleDeleteTemplateFolder(sp, cmd, user.Username),
            MoveTemplateToFolderCommand cmd => await HandleMoveTemplateToFolder(sp, cmd, user.Username),

            // Instances
            ListInstancesCommand cmd => await HandleListInstances(sp, cmd, user),
            GetInstanceCommand cmd => await HandleGetInstance(sp, cmd, user),
            CreateInstanceCommand cmd => await HandleCreateInstance(sp, cmd, user),
            MgmtDeployInstanceCommand cmd => await HandleDeployInstance(sp, cmd, user),
            MgmtEnableInstanceCommand cmd => await HandleEnableInstance(sp, cmd, user),
            MgmtDisableInstanceCommand cmd => await HandleDisableInstance(sp, cmd, user),
            MgmtDeleteInstanceCommand cmd => await HandleDeleteInstance(sp, cmd, user),
            SetConnectionBindingsCommand cmd => await HandleSetConnectionBindings(sp, cmd, user),
            SetInstanceOverridesCommand cmd => await HandleSetInstanceOverrides(sp, cmd, user),
            SetInstanceAreaCommand cmd => await HandleSetInstanceArea(sp, cmd, user),
            SetInstanceAlarmOverrideCommand cmd => await HandleSetInstanceAlarmOverride(sp, cmd, user),
            DeleteInstanceAlarmOverrideCommand cmd => await HandleDeleteInstanceAlarmOverride(sp, cmd, user),
            ListInstanceAlarmOverridesCommand cmd => await HandleListInstanceAlarmOverrides(sp, cmd, user),

            // Sites
            ListSitesCommand => await HandleListSites(sp, user),
            GetSiteCommand cmd => await HandleGetSite(sp, cmd, user),
            CreateSiteCommand cmd => await HandleCreateSite(sp, cmd, user.Username),
            UpdateSiteCommand cmd => await HandleUpdateSite(sp, cmd, user.Username),
            DeleteSiteCommand cmd => await HandleDeleteSite(sp, cmd, user.Username),
            ListAreasCommand cmd => await HandleListAreas(sp, cmd, user),
            CreateAreaCommand cmd => await HandleCreateArea(sp, cmd, user.Username),
            DeleteAreaCommand cmd => await HandleDeleteArea(sp, cmd, user.Username),
            UpdateAreaCommand cmd => await HandleUpdateArea(sp, cmd, user.Username),

            // Data Connections
            ListDataConnectionsCommand cmd => await HandleListDataConnections(sp, cmd),
            GetDataConnectionCommand cmd => await HandleGetDataConnection(sp, cmd, user),
            CreateDataConnectionCommand cmd => await HandleCreateDataConnection(sp, cmd, user.Username),
            UpdateDataConnectionCommand cmd => await HandleUpdateDataConnection(sp, cmd, user.Username),
            DeleteDataConnectionCommand cmd => await HandleDeleteDataConnection(sp, cmd, user.Username),

            // External Systems
            ListExternalSystemsCommand => await HandleListExternalSystems(sp),
            GetExternalSystemCommand cmd => await HandleGetExternalSystem(sp, cmd),
            CreateExternalSystemCommand cmd => await HandleCreateExternalSystem(sp, cmd, user.Username),
            UpdateExternalSystemCommand cmd => await HandleUpdateExternalSystem(sp, cmd, user.Username),
            DeleteExternalSystemCommand cmd => await HandleDeleteExternalSystem(sp, cmd, user.Username),
            ListExternalSystemMethodsCommand cmd => await HandleListExternalSystemMethods(sp, cmd),
            GetExternalSystemMethodCommand cmd => await HandleGetExternalSystemMethod(sp, cmd),
            CreateExternalSystemMethodCommand cmd => await HandleCreateExternalSystemMethod(sp, cmd, user.Username),
            UpdateExternalSystemMethodCommand cmd => await HandleUpdateExternalSystemMethod(sp, cmd, user.Username),
            DeleteExternalSystemMethodCommand cmd => await HandleDeleteExternalSystemMethod(sp, cmd, user.Username),

            // Notification Lists
            ListNotificationListsCommand => await HandleListNotificationLists(sp),
            GetNotificationListCommand cmd => await HandleGetNotificationList(sp, cmd),
            CreateNotificationListCommand cmd => await HandleCreateNotificationList(sp, cmd, user.Username),
            UpdateNotificationListCommand cmd => await HandleUpdateNotificationList(sp, cmd, user.Username),
            DeleteNotificationListCommand cmd => await HandleDeleteNotificationList(sp, cmd, user.Username),
            ListSmtpConfigsCommand => await HandleListSmtpConfigs(sp),
            UpdateSmtpConfigCommand cmd => await HandleUpdateSmtpConfig(sp, cmd, user.Username),

            // Shared Scripts
            ListSharedScriptsCommand => await HandleListSharedScripts(sp),
            GetSharedScriptCommand cmd => await HandleGetSharedScript(sp, cmd),
            CreateSharedScriptCommand cmd => await HandleCreateSharedScript(sp, cmd, user.Username),
            UpdateSharedScriptCommand cmd => await HandleUpdateSharedScript(sp, cmd, user.Username),
            DeleteSharedScriptCommand cmd => await HandleDeleteSharedScript(sp, cmd, user.Username),

            // Database Connections (External System)
            ListDatabaseConnectionsCommand => await HandleListDatabaseConnections(sp),
            GetDatabaseConnectionCommand cmd => await HandleGetDatabaseConnection(sp, cmd),
            CreateDatabaseConnectionDefCommand cmd => await HandleCreateDatabaseConnection(sp, cmd, user.Username),
            UpdateDatabaseConnectionDefCommand cmd => await HandleUpdateDatabaseConnection(sp, cmd, user.Username),
            DeleteDatabaseConnectionDefCommand cmd => await HandleDeleteDatabaseConnection(sp, cmd, user.Username),

            // Inbound API Methods
            ListApiMethodsCommand => await HandleListApiMethods(sp),
            GetApiMethodCommand cmd => await HandleGetApiMethod(sp, cmd),
            CreateApiMethodCommand cmd => await HandleCreateApiMethod(sp, cmd, user.Username),
            UpdateApiMethodCommand cmd => await HandleUpdateApiMethod(sp, cmd, user.Username),
            DeleteApiMethodCommand cmd => await HandleDeleteApiMethod(sp, cmd, user.Username),

            // Security
            ListRoleMappingsCommand => await HandleListRoleMappings(sp),
            CreateRoleMappingCommand cmd => await HandleCreateRoleMapping(sp, cmd, user.Username),
            UpdateRoleMappingCommand cmd => await HandleUpdateRoleMapping(sp, cmd, user.Username),
            DeleteRoleMappingCommand cmd => await HandleDeleteRoleMapping(sp, cmd, user.Username),
            ListApiKeysCommand => await HandleListApiKeys(sp),
            CreateApiKeyCommand cmd => await HandleCreateApiKey(sp, cmd, user.Username),
            DeleteApiKeyCommand cmd => await HandleDeleteApiKey(sp, cmd, user.Username),
            UpdateApiKeyCommand cmd => await HandleUpdateApiKey(sp, cmd, user.Username),
            ListScopeRulesCommand cmd => await HandleListScopeRules(sp, cmd),
            AddScopeRuleCommand cmd => await HandleAddScopeRule(sp, cmd, user.Username),
            DeleteScopeRuleCommand cmd => await HandleDeleteScopeRule(sp, cmd, user.Username),

            // Deployments
            MgmtDeployArtifactsCommand cmd => await HandleDeployArtifacts(sp, cmd, user.Username),
            QueryDeploymentsCommand cmd => await HandleQueryDeployments(sp, cmd),
            GetDeploymentDiffCommand cmd => await HandleGetDeploymentDiff(sp, cmd, user),

            // Audit Log
            QueryAuditLogCommand cmd => await HandleQueryAuditLog(sp, cmd),

            // Health
            GetHealthSummaryCommand => HandleGetHealthSummary(sp),
            GetSiteHealthCommand cmd => HandleGetSiteHealth(sp, cmd),

            // Remote Queries
            QueryEventLogsCommand cmd => await HandleQueryEventLogs(sp, cmd, user),
            QueryParkedMessagesCommand cmd => await HandleQueryParkedMessages(sp, cmd, user),
            RetryParkedMessageCommand cmd => await HandleRetryParkedMessage(sp, cmd, user),
            DiscardParkedMessageCommand cmd => await HandleDiscardParkedMessage(sp, cmd, user),
            DebugSnapshotCommand cmd => await HandleDebugSnapshot(sp, cmd, user),

            // Role resolution (for CLI LDAP auth)
            ResolveRolesCommand cmd => await HandleResolveRoles(sp, cmd),

            _ => throw new NotSupportedException($"Unknown command type: {command.GetType().Name}")
        };
    }

    // ========================================================================
    // Role resolution
    // ========================================================================

    private static async Task<object?> HandleResolveRoles(IServiceProvider sp, ResolveRolesCommand cmd)
    {
        var roleMapper = new RoleMapper(sp.GetRequiredService<ISecurityRepository>());
        var result = await roleMapper.MapGroupsToRolesAsync(cmd.LdapGroups);
        return new
        {
            Roles = result.Roles,
            PermittedSiteIds = result.PermittedSiteIds,
            IsSystemWideDeployment = result.IsSystemWideDeployment
        };
    }

    // ========================================================================
    // Site-scope enforcement
    // ========================================================================

    /// <summary>
    /// Throws SiteScopeViolationException if the user has site-scoped Deployment
    /// and the target site is not in their permitted sites.
    /// Users with Admin or Design roles, or system-wide Deployment, are not restricted.
    /// </summary>
    private static void EnforceSiteScope(AuthenticatedUser user, int? targetSiteId)
    {
        if (targetSiteId == null) return;
        if (user.PermittedSiteIds.Length == 0) return; // system-wide access
        if (user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase)) return;

        if (!user.PermittedSiteIds.Contains(targetSiteId.Value.ToString()))
        {
            throw new SiteScopeViolationException(
                $"Access denied: your Deployment role is scoped to sites [{string.Join(", ", user.PermittedSiteIds)}] " +
                $"and does not include site {targetSiteId.Value}.");
        }
    }

    /// <summary>
    /// Resolves the site ID for an instance and enforces site-scope.
    /// </summary>
    private static async Task EnforceSiteScopeForInstance(IServiceProvider sp, AuthenticatedUser user, int instanceId)
    {
        if (user.PermittedSiteIds.Length == 0) return;
        if (user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase)) return;

        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        var instance = await repo.GetInstanceByIdAsync(instanceId);
        if (instance != null)
            EnforceSiteScope(user, instance.SiteId);
    }

    /// <summary>
    /// Resolves a site by its string identifier and enforces site-scope.
    /// Used by remote-query handlers that key off the site identifier rather than its ID.
    /// </summary>
    private static async Task EnforceSiteScopeForIdentifier(IServiceProvider sp, AuthenticatedUser user, string siteIdentifier)
    {
        if (user.PermittedSiteIds.Length == 0) return;
        if (user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase)) return;

        var repo = sp.GetRequiredService<ISiteRepository>();
        var site = await repo.GetSiteByIdentifierAsync(siteIdentifier);
        if (site != null)
            EnforceSiteScope(user, site.Id);
    }

    /// <summary>
    /// Logs an audit entry after a successful mutation.
    /// </summary>
    /// <remarks>
    /// Audit-logging contract (finding ManagementService-009). Every mutating operation is
    /// audited exactly once, by whichever layer owns the write:
    /// <list type="bullet">
    /// <item>Handlers that mutate a repository directly (site, area, data-connection,
    /// external-system, notification, security, API-key, scope-rule) call this helper
    /// explicitly after the successful change.</item>
    /// <item>Handlers that delegate to a domain service (<c>TemplateService</c>,
    /// <c>SharedScriptService</c>, <c>InstanceService</c>, <c>AreaService</c>,
    /// <c>SiteService</c>, <c>TemplateFolderService</c>, <c>DeploymentService</c>,
    /// <c>ArtifactDeploymentService</c>) do NOT call this helper — those services own their
    /// own <see cref="IAuditService"/> dependency and audit internally. Adding an explicit
    /// <see cref="AuditAsync"/> call in such a handler would double-log.</item>
    /// </list>
    /// </remarks>
    private static async Task AuditAsync(IServiceProvider sp, string user, string action, string entityType, string entityId, string entityName, object? afterState)
    {
        var auditService = sp.GetRequiredService<IAuditService>();
        await auditService.LogAsync(user, action, entityType, entityId, entityName, afterState);
    }

    // ========================================================================
    // Template handlers
    // ========================================================================

    private static async Task<object?> HandleListTemplates(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        return await repo.GetAllTemplatesAsync();
    }

    private static async Task<object?> HandleGetTemplate(IServiceProvider sp, GetTemplateCommand cmd)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        return await repo.GetTemplateWithChildrenAsync(cmd.TemplateId);
    }

    private static async Task<object?> HandleCreateTemplate(IServiceProvider sp, CreateTemplateCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.CreateTemplateAsync(cmd.Name, cmd.Description, cmd.ParentTemplateId, user);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleUpdateTemplate(IServiceProvider sp, UpdateTemplateCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.UpdateTemplateAsync(cmd.TemplateId, cmd.Name, cmd.Description, cmd.ParentTemplateId, user);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteTemplate(IServiceProvider sp, DeleteTemplateCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.DeleteTemplateAsync(cmd.TemplateId, user);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleValidateTemplate(IServiceProvider sp, ValidateTemplateCommand cmd)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();

        // Load the template with all members
        var template = await repo.GetTemplateWithChildrenAsync(cmd.TemplateId)
            ?? throw new InvalidOperationException($"Template with ID {cmd.TemplateId} not found.");

        var attributes = await repo.GetAttributesByTemplateIdAsync(cmd.TemplateId);
        var alarms = await repo.GetAlarmsByTemplateIdAsync(cmd.TemplateId);
        var scripts = await repo.GetScriptsByTemplateIdAsync(cmd.TemplateId);

        // Build a FlattenedConfiguration from the template for the full validation pipeline
        var flatConfig = new Commons.Types.Flattening.FlattenedConfiguration
        {
            InstanceUniqueName = $"validation-{template.Name}",
            TemplateId = template.Id,
            Attributes = attributes.Select(a => new Commons.Types.Flattening.ResolvedAttribute
            {
                CanonicalName = a.Name,
                Value = a.Value,
                DataType = a.DataType.ToString(),
                IsLocked = a.IsLocked,
                DataSourceReference = a.DataSourceReference
            }).ToList(),
            Alarms = alarms.Select(a => new Commons.Types.Flattening.ResolvedAlarm
            {
                CanonicalName = a.Name,
                PriorityLevel = a.PriorityLevel,
                IsLocked = a.IsLocked,
                TriggerType = a.TriggerType.ToString(),
                TriggerConfiguration = a.TriggerConfiguration
            }).ToList(),
            Scripts = scripts.Select(s => new Commons.Types.Flattening.ResolvedScript
            {
                CanonicalName = s.Name,
                Code = s.Code,
                IsLocked = s.IsLocked,
                TriggerType = s.TriggerType,
                TriggerConfiguration = s.TriggerConfiguration,
                ParameterDefinitions = s.ParameterDefinitions,
                ReturnDefinition = s.ReturnDefinition
            }).ToList()
        };

        // Run full validation pipeline (collisions, script compilation, trigger refs, bindings)
        var validationService = new TemplateEngine.Validation.ValidationService();
        var validationResult = validationService.Validate(flatConfig);

        // Also detect naming collisions across the inheritance/composition graph
        var svc = sp.GetRequiredService<TemplateService>();
        var collisions = await svc.DetectCollisionsAsync(cmd.TemplateId);
        if (collisions.Count > 0)
        {
            var collisionErrors = collisions.Select(c =>
                Commons.Types.Flattening.ValidationEntry.Error(
                    Commons.Types.Flattening.ValidationCategory.NamingCollision, c)).ToArray();
            var collisionResult = new Commons.Types.Flattening.ValidationResult { Errors = collisionErrors };
            validationResult = Commons.Types.Flattening.ValidationResult.Merge(validationResult, collisionResult);
        }

        return validationResult;
    }

    // ========================================================================
    // Template folder handlers
    // ========================================================================

    private static async Task<object?> HandleListTemplateFolders(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        return await repo.GetAllFoldersAsync();
    }

    private static async Task<object?> HandleCreateTemplateFolder(IServiceProvider sp, CreateTemplateFolderCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateFolderService>();
        var result = await svc.CreateFolderAsync(cmd.Name, cmd.ParentFolderId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleRenameTemplateFolder(IServiceProvider sp, RenameTemplateFolderCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateFolderService>();
        var result = await svc.RenameFolderAsync(cmd.FolderId, cmd.NewName, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleMoveTemplateFolder(IServiceProvider sp, MoveTemplateFolderCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateFolderService>();
        var result = await svc.MoveFolderAsync(cmd.FolderId, cmd.NewParentFolderId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteTemplateFolder(IServiceProvider sp, DeleteTemplateFolderCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateFolderService>();
        var result = await svc.DeleteFolderAsync(cmd.FolderId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleMoveTemplateToFolder(IServiceProvider sp, MoveTemplateToFolderCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.MoveTemplateAsync(cmd.TemplateId, cmd.NewFolderId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    // ========================================================================
    // Instance handlers
    // ========================================================================

    private static async Task<object?> HandleListInstances(IServiceProvider sp, ListInstancesCommand cmd, AuthenticatedUser user)
    {
        var repo = sp.GetRequiredService<ICentralUiRepository>();
        var instances = await repo.GetInstancesFilteredAsync(cmd.SiteId, cmd.TemplateId, cmd.SearchTerm);
        // Filter by permitted sites for site-scoped users
        if (user.PermittedSiteIds.Length > 0 && !user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase))
        {
            var permittedIds = new HashSet<string>(user.PermittedSiteIds);
            instances = instances.Where(i => permittedIds.Contains(i.SiteId.ToString())).ToList();
        }
        return instances;
    }

    private static async Task<object?> HandleGetInstance(IServiceProvider sp, GetInstanceCommand cmd, AuthenticatedUser user)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        var instance = await repo.GetInstanceByIdAsync(cmd.InstanceId);
        if (instance != null)
            EnforceSiteScope(user, instance.SiteId);
        return instance;
    }

    private static async Task<object?> HandleCreateInstance(IServiceProvider sp, CreateInstanceCommand cmd, AuthenticatedUser user)
    {
        EnforceSiteScope(user, cmd.SiteId);
        var svc = sp.GetRequiredService<InstanceService>();
        var result = await svc.CreateInstanceAsync(cmd.UniqueName, cmd.TemplateId, cmd.SiteId, cmd.AreaId, user.Username);
        if (!result.IsSuccess) throw new InvalidOperationException(result.Error);
        await AuditAsync(sp, user.Username, "Create", "Instance", result.Value.Id.ToString(), result.Value.UniqueName, result.Value);
        return result.Value;
    }

    private static async Task<object?> HandleDeployInstance(IServiceProvider sp, MgmtDeployInstanceCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<DeploymentService>();
        var result = await svc.DeployInstanceAsync(cmd.InstanceId, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleEnableInstance(IServiceProvider sp, MgmtEnableInstanceCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<DeploymentService>();
        var result = await svc.EnableInstanceAsync(cmd.InstanceId, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDisableInstance(IServiceProvider sp, MgmtDisableInstanceCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<DeploymentService>();
        var result = await svc.DisableInstanceAsync(cmd.InstanceId, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteInstance(IServiceProvider sp, MgmtDeleteInstanceCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<DeploymentService>();
        var result = await svc.DeleteInstanceAsync(cmd.InstanceId, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleSetConnectionBindings(IServiceProvider sp, SetConnectionBindingsCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<InstanceService>();
        var result = await svc.SetConnectionBindingsAsync(cmd.InstanceId, cmd.Bindings, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleSetInstanceOverrides(IServiceProvider sp, SetInstanceOverridesCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<InstanceService>();
        var results = new List<InstanceAttributeOverride>();
        foreach (var (attrName, overrideValue) in cmd.Overrides)
        {
            var result = await svc.SetAttributeOverrideAsync(cmd.InstanceId, attrName, overrideValue, user.Username);
            if (!result.IsSuccess) throw new InvalidOperationException(result.Error);
            results.Add(result.Value);
        }
        return results;
    }

    private static async Task<object?> HandleSetInstanceArea(IServiceProvider sp, SetInstanceAreaCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<InstanceService>();
        var result = await svc.AssignToAreaAsync(cmd.InstanceId, cmd.AreaId, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleSetInstanceAlarmOverride(IServiceProvider sp, SetInstanceAlarmOverrideCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<InstanceService>();
        var result = await svc.SetAlarmOverrideAsync(
            cmd.InstanceId, cmd.AlarmCanonicalName,
            cmd.TriggerConfigurationOverride, cmd.PriorityLevelOverride,
            user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteInstanceAlarmOverride(IServiceProvider sp, DeleteInstanceAlarmOverrideCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<InstanceService>();
        var result = await svc.DeleteAlarmOverrideAsync(
            cmd.InstanceId, cmd.AlarmCanonicalName, user.Username);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleListInstanceAlarmOverrides(IServiceProvider sp, ListInstanceAlarmOverridesCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        return await repo.GetAlarmOverridesByInstanceIdAsync(cmd.InstanceId);
    }

    private static async Task<object?> HandleGetDeploymentDiff(IServiceProvider sp, GetDeploymentDiffCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
        var svc = sp.GetRequiredService<DeploymentService>();
        var result = await svc.GetDeploymentComparisonAsync(cmd.InstanceId);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleRetryParkedMessage(IServiceProvider sp, RetryParkedMessageCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteIdentifier);
        var commService = sp.GetRequiredService<CommunicationService>();
        var request = new Commons.Messages.RemoteQuery.ParkedMessageRetryRequest(
            Guid.NewGuid().ToString("N"), cmd.SiteIdentifier, cmd.MessageId, DateTimeOffset.UtcNow);
        return await commService.RetryParkedMessageAsync(cmd.SiteIdentifier, request);
    }

    private static async Task<object?> HandleDiscardParkedMessage(IServiceProvider sp, DiscardParkedMessageCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteIdentifier);
        var commService = sp.GetRequiredService<CommunicationService>();
        var request = new Commons.Messages.RemoteQuery.ParkedMessageDiscardRequest(
            Guid.NewGuid().ToString("N"), cmd.SiteIdentifier, cmd.MessageId, DateTimeOffset.UtcNow);
        return await commService.DiscardParkedMessageAsync(cmd.SiteIdentifier, request);
    }

    // ========================================================================
    // Site handlers
    // ========================================================================

    private static async Task<object?> HandleListSites(IServiceProvider sp, AuthenticatedUser user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var sites = await repo.GetAllSitesAsync();
        if (user.PermittedSiteIds.Length > 0 && !user.Roles.Contains("Admin", StringComparer.OrdinalIgnoreCase))
        {
            var permittedIds = new HashSet<string>(user.PermittedSiteIds);
            sites = sites.Where(s => permittedIds.Contains(s.Id.ToString())).ToList();
        }
        return sites;
    }

    private static async Task<object?> HandleGetSite(IServiceProvider sp, GetSiteCommand cmd, AuthenticatedUser user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var site = await repo.GetSiteByIdAsync(cmd.SiteId);
        if (site != null)
            EnforceSiteScope(user, site.Id);
        return site;
    }

    private static async Task<object?> HandleCreateSite(IServiceProvider sp, CreateSiteCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var site = new Site(cmd.Name, cmd.SiteIdentifier)
        {
            Description = cmd.Description,
            NodeAAddress = cmd.NodeAAddress,
            NodeBAddress = cmd.NodeBAddress,
            GrpcNodeAAddress = cmd.GrpcNodeAAddress,
            GrpcNodeBAddress = cmd.GrpcNodeBAddress
        };
        await repo.AddSiteAsync(site);
        await repo.SaveChangesAsync();
        var commService = sp.GetService<CommunicationService>();
        commService?.RefreshSiteAddresses();
        await AuditAsync(sp, user, "Create", "Site", site.Id.ToString(), site.Name, site);
        return site;
    }

    private static async Task<object?> HandleUpdateSite(IServiceProvider sp, UpdateSiteCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var site = await repo.GetSiteByIdAsync(cmd.SiteId)
            ?? throw new InvalidOperationException($"Site with ID {cmd.SiteId} not found.");
        site.Name = cmd.Name;
        site.Description = cmd.Description;
        site.NodeAAddress = cmd.NodeAAddress;
        site.NodeBAddress = cmd.NodeBAddress;
        site.GrpcNodeAAddress = cmd.GrpcNodeAAddress;
        site.GrpcNodeBAddress = cmd.GrpcNodeBAddress;
        await repo.UpdateSiteAsync(site);
        await repo.SaveChangesAsync();
        var commService = sp.GetService<CommunicationService>();
        commService?.RefreshSiteAddresses();
        await AuditAsync(sp, user, "Update", "Site", site.Id.ToString(), site.Name, site);
        return site;
    }

    private static async Task<object?> HandleDeleteSite(IServiceProvider sp, DeleteSiteCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var site = await repo.GetSiteByIdAsync(cmd.SiteId);
        var instances = await repo.GetInstancesBySiteIdAsync(cmd.SiteId);
        if (instances.Count > 0)
            throw new InvalidOperationException(
                $"Cannot delete site {cmd.SiteId}: it has {instances.Count} instance(s).");
        await repo.DeleteSiteAsync(cmd.SiteId);
        await repo.SaveChangesAsync();
        var commService = sp.GetService<CommunicationService>();
        commService?.RefreshSiteAddresses();
        await AuditAsync(sp, user, "Delete", "Site", cmd.SiteId.ToString(), site?.Name ?? cmd.SiteId.ToString(), null);
        return true;
    }

    private static async Task<object?> HandleListAreas(IServiceProvider sp, ListAreasCommand cmd, AuthenticatedUser user)
    {
        EnforceSiteScope(user, cmd.SiteId);
        var repo = sp.GetRequiredService<ICentralUiRepository>();
        return await repo.GetAreaTreeBySiteIdAsync(cmd.SiteId);
    }

    private static async Task<object?> HandleCreateArea(IServiceProvider sp, CreateAreaCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        var area = new Area(cmd.Name)
        {
            SiteId = cmd.SiteId,
            ParentAreaId = cmd.ParentAreaId
        };
        await repo.AddAreaAsync(area);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "Area", area.Id.ToString(), area.Name, area);
        return area;
    }

    private static async Task<object?> HandleDeleteArea(IServiceProvider sp, DeleteAreaCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        await repo.DeleteAreaAsync(cmd.AreaId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "Area", cmd.AreaId.ToString(), cmd.AreaId.ToString(), null);
        return true;
    }

    // ========================================================================
    // Data Connection handlers
    // ========================================================================

    private static async Task<object?> HandleListDataConnections(IServiceProvider sp, ListDataConnectionsCommand cmd)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        if (cmd.SiteId.HasValue)
            return await repo.GetDataConnectionsBySiteIdAsync(cmd.SiteId.Value);
        return await repo.GetAllDataConnectionsAsync();
    }

    private static async Task<object?> HandleGetDataConnection(IServiceProvider sp, GetDataConnectionCommand cmd, AuthenticatedUser user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var conn = await repo.GetDataConnectionByIdAsync(cmd.DataConnectionId);
        if (conn != null)
            EnforceSiteScope(user, conn.SiteId);
        return conn;
    }

    private static async Task<object?> HandleCreateDataConnection(IServiceProvider sp, CreateDataConnectionCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var conn = new DataConnection(cmd.Name, cmd.Protocol, cmd.SiteId)
        {
            PrimaryConfiguration = cmd.PrimaryConfiguration,
            BackupConfiguration = cmd.BackupConfiguration,
            FailoverRetryCount = cmd.FailoverRetryCount
        };
        await repo.AddDataConnectionAsync(conn);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "DataConnection", conn.Id.ToString(), conn.Name, conn);
        return conn;
    }

    private static async Task<object?> HandleUpdateDataConnection(IServiceProvider sp, UpdateDataConnectionCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        var conn = await repo.GetDataConnectionByIdAsync(cmd.DataConnectionId)
            ?? throw new InvalidOperationException($"DataConnection with ID {cmd.DataConnectionId} not found.");
        conn.Name = cmd.Name;
        conn.Protocol = cmd.Protocol;
        conn.PrimaryConfiguration = cmd.PrimaryConfiguration;
        conn.BackupConfiguration = cmd.BackupConfiguration;
        conn.FailoverRetryCount = cmd.FailoverRetryCount;
        await repo.UpdateDataConnectionAsync(conn);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "DataConnection", conn.Id.ToString(), conn.Name, conn);
        return conn;
    }

    private static async Task<object?> HandleDeleteDataConnection(IServiceProvider sp, DeleteDataConnectionCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISiteRepository>();
        await repo.DeleteDataConnectionAsync(cmd.DataConnectionId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "DataConnection", cmd.DataConnectionId.ToString(), cmd.DataConnectionId.ToString(), null);
        return true;
    }


    // ========================================================================
    // External System handlers
    // ========================================================================

    private static async Task<object?> HandleListExternalSystems(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        return await repo.GetAllExternalSystemsAsync();
    }

    private static async Task<object?> HandleGetExternalSystem(IServiceProvider sp, GetExternalSystemCommand cmd)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        return await repo.GetExternalSystemByIdAsync(cmd.ExternalSystemId);
    }

    private static async Task<object?> HandleCreateExternalSystem(IServiceProvider sp, CreateExternalSystemCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        var def = new ExternalSystemDefinition(cmd.Name, cmd.EndpointUrl, cmd.AuthType)
        {
            AuthConfiguration = cmd.AuthConfiguration
        };
        await repo.AddExternalSystemAsync(def);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "ExternalSystem", def.Id.ToString(), def.Name, def);
        return def;
    }

    private static async Task<object?> HandleUpdateExternalSystem(IServiceProvider sp, UpdateExternalSystemCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        var def = await repo.GetExternalSystemByIdAsync(cmd.ExternalSystemId)
            ?? throw new InvalidOperationException($"ExternalSystem with ID {cmd.ExternalSystemId} not found.");
        def.Name = cmd.Name;
        def.EndpointUrl = cmd.EndpointUrl;
        def.AuthType = cmd.AuthType;
        def.AuthConfiguration = cmd.AuthConfiguration;
        await repo.UpdateExternalSystemAsync(def);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "ExternalSystem", def.Id.ToString(), def.Name, def);
        return def;
    }

    private static async Task<object?> HandleDeleteExternalSystem(IServiceProvider sp, DeleteExternalSystemCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        await repo.DeleteExternalSystemAsync(cmd.ExternalSystemId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "ExternalSystem", cmd.ExternalSystemId.ToString(), cmd.ExternalSystemId.ToString(), null);
        return true;
    }

    private static async Task<object?> HandleListExternalSystemMethods(IServiceProvider sp, ListExternalSystemMethodsCommand cmd)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        return await repo.GetMethodsByExternalSystemIdAsync(cmd.ExternalSystemId);
    }

    private static async Task<object?> HandleGetExternalSystemMethod(IServiceProvider sp, GetExternalSystemMethodCommand cmd)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        return await repo.GetExternalSystemMethodByIdAsync(cmd.MethodId);
    }

    private static async Task<object?> HandleCreateExternalSystemMethod(IServiceProvider sp, CreateExternalSystemMethodCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        var method = new ExternalSystemMethod(cmd.Name, cmd.HttpMethod, cmd.Path)
        {
            ExternalSystemDefinitionId = cmd.ExternalSystemId,
            ParameterDefinitions = cmd.ParameterDefinitions,
            ReturnDefinition = cmd.ReturnDefinition
        };
        await repo.AddExternalSystemMethodAsync(method);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "ExternalSystemMethod", method.Id.ToString(), method.Name, method);
        return method;
    }

    private static async Task<object?> HandleUpdateExternalSystemMethod(IServiceProvider sp, UpdateExternalSystemMethodCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        var method = await repo.GetExternalSystemMethodByIdAsync(cmd.MethodId)
            ?? throw new InvalidOperationException($"ExternalSystemMethod with ID {cmd.MethodId} not found.");
        if (cmd.Name != null) method.Name = cmd.Name;
        if (cmd.HttpMethod != null) method.HttpMethod = cmd.HttpMethod;
        if (cmd.Path != null) method.Path = cmd.Path;
        if (cmd.ParameterDefinitions != null) method.ParameterDefinitions = cmd.ParameterDefinitions;
        if (cmd.ReturnDefinition != null) method.ReturnDefinition = cmd.ReturnDefinition;
        await repo.UpdateExternalSystemMethodAsync(method);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "ExternalSystemMethod", method.Id.ToString(), method.Name, method);
        return method;
    }

    private static async Task<object?> HandleDeleteExternalSystemMethod(IServiceProvider sp, DeleteExternalSystemMethodCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        await repo.DeleteExternalSystemMethodAsync(cmd.MethodId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "ExternalSystemMethod", cmd.MethodId.ToString(), cmd.MethodId.ToString(), null);
        return true;
    }

    // ========================================================================
    // Notification handlers
    // ========================================================================

    private static async Task<object?> HandleListNotificationLists(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        return await repo.GetAllNotificationListsAsync();
    }

    private static async Task<object?> HandleGetNotificationList(IServiceProvider sp, GetNotificationListCommand cmd)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        return await repo.GetNotificationListByIdAsync(cmd.NotificationListId);
    }

    private static async Task<object?> HandleCreateNotificationList(IServiceProvider sp, CreateNotificationListCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        var list = new NotificationList(cmd.Name);
        foreach (var email in cmd.RecipientEmails)
        {
            list.Recipients.Add(new NotificationRecipient(email, email));
        }
        await repo.AddNotificationListAsync(list);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "NotificationList", list.Id.ToString(), list.Name, list);
        return list;
    }

    private static async Task<object?> HandleUpdateNotificationList(IServiceProvider sp, UpdateNotificationListCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        var list = await repo.GetNotificationListByIdAsync(cmd.NotificationListId)
            ?? throw new InvalidOperationException($"NotificationList with ID {cmd.NotificationListId} not found.");
        list.Name = cmd.Name;

        var existingRecipients = await repo.GetRecipientsByListIdAsync(cmd.NotificationListId);
        foreach (var r in existingRecipients)
        {
            await repo.DeleteRecipientAsync(r.Id);
        }

        foreach (var email in cmd.RecipientEmails)
        {
            await repo.AddRecipientAsync(new NotificationRecipient(email, email)
            {
                NotificationListId = cmd.NotificationListId
            });
        }

        await repo.UpdateNotificationListAsync(list);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "NotificationList", list.Id.ToString(), list.Name, list);
        return list;
    }

    private static async Task<object?> HandleDeleteNotificationList(IServiceProvider sp, DeleteNotificationListCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        await repo.DeleteNotificationListAsync(cmd.NotificationListId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "NotificationList", cmd.NotificationListId.ToString(), cmd.NotificationListId.ToString(), null);
        return true;
    }

    private static async Task<object?> HandleListSmtpConfigs(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        return await repo.GetAllSmtpConfigurationsAsync();
    }

    private static async Task<object?> HandleUpdateSmtpConfig(IServiceProvider sp, UpdateSmtpConfigCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<INotificationRepository>();
        var config = await repo.GetSmtpConfigurationByIdAsync(cmd.SmtpConfigId)
            ?? throw new InvalidOperationException($"SmtpConfiguration with ID {cmd.SmtpConfigId} not found.");
        config.Host = cmd.Server;
        config.Port = cmd.Port;
        config.AuthType = cmd.AuthMode;
        config.FromAddress = cmd.FromAddress;
        await repo.UpdateSmtpConfigurationAsync(config);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "SmtpConfiguration", config.Id.ToString(), config.Host, config);
        return config;
    }

    // ========================================================================
    // Security handlers
    // ========================================================================

    private static async Task<object?> HandleListRoleMappings(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        return await repo.GetAllMappingsAsync();
    }

    private static async Task<object?> HandleCreateRoleMapping(IServiceProvider sp, CreateRoleMappingCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        var mapping = new LdapGroupMapping(cmd.LdapGroupName, cmd.Role);
        await repo.AddMappingAsync(mapping);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "RoleMapping", mapping.Id.ToString(), $"{mapping.LdapGroupName}->{mapping.Role}", mapping);
        return mapping;
    }

    private static async Task<object?> HandleUpdateRoleMapping(IServiceProvider sp, UpdateRoleMappingCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        var mapping = await repo.GetMappingByIdAsync(cmd.MappingId)
            ?? throw new InvalidOperationException($"RoleMapping with ID {cmd.MappingId} not found.");
        mapping.LdapGroupName = cmd.LdapGroupName;
        mapping.Role = cmd.Role;
        await repo.UpdateMappingAsync(mapping);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "RoleMapping", mapping.Id.ToString(), $"{mapping.LdapGroupName}->{mapping.Role}", mapping);
        return mapping;
    }

    private static async Task<object?> HandleDeleteRoleMapping(IServiceProvider sp, DeleteRoleMappingCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        await repo.DeleteMappingAsync(cmd.MappingId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "RoleMapping", cmd.MappingId.ToString(), cmd.MappingId.ToString(), null);
        return true;
    }

    private static async Task<object?> HandleListApiKeys(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        return await repo.GetAllApiKeysAsync();
    }

    private static async Task<object?> HandleCreateApiKey(IServiceProvider sp, CreateApiKeyCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        var keyValue = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
        var apiKey = new ApiKey(cmd.Name, keyValue) { IsEnabled = true };
        await repo.AddApiKeyAsync(apiKey);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "ApiKey", apiKey.Id.ToString(), apiKey.Name, new { apiKey.Id, apiKey.Name, apiKey.IsEnabled });
        return apiKey;
    }

    private static async Task<object?> HandleDeleteApiKey(IServiceProvider sp, DeleteApiKeyCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        await repo.DeleteApiKeyAsync(cmd.ApiKeyId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "ApiKey", cmd.ApiKeyId.ToString(), cmd.ApiKeyId.ToString(), null);
        return true;
    }

    // ========================================================================
    // Deployment handlers
    // ========================================================================

    private static async Task<object?> HandleDeployArtifacts(IServiceProvider sp, MgmtDeployArtifactsCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<ArtifactDeploymentService>();
        var result = await svc.DeployToAllSitesAsync(user);
        return result.IsSuccess
            ? result.Value
            : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleQueryDeployments(IServiceProvider sp, QueryDeploymentsCommand cmd)
    {
        var repo = sp.GetRequiredService<IDeploymentManagerRepository>();
        if (cmd.InstanceId.HasValue)
            return await repo.GetDeploymentsByInstanceIdAsync(cmd.InstanceId.Value);
        return await repo.GetAllDeploymentRecordsAsync();
    }

    // ========================================================================
    // Audit Log handler
    // ========================================================================

    private static async Task<object?> HandleQueryAuditLog(IServiceProvider sp, QueryAuditLogCommand cmd)
    {
        var repo = sp.GetRequiredService<ICentralUiRepository>();
        return await repo.GetAuditLogEntriesAsync(
            cmd.User, cmd.EntityType, cmd.Action, cmd.From, cmd.To,
            page: cmd.Page, pageSize: cmd.PageSize);
    }

    // ========================================================================
    // Health handlers
    // ========================================================================

    private static object? HandleGetHealthSummary(IServiceProvider sp)
    {
        var aggregator = sp.GetRequiredService<ICentralHealthAggregator>();
        return aggregator.GetAllSiteStates();
    }

    private static object? HandleGetSiteHealth(IServiceProvider sp, GetSiteHealthCommand cmd)
    {
        var aggregator = sp.GetRequiredService<ICentralHealthAggregator>();
        return aggregator.GetSiteState(cmd.SiteIdentifier);
    }

    // ========================================================================
    // Template member handlers
    // ========================================================================

    private static async Task<object?> HandleAddAttribute(IServiceProvider sp, AddTemplateAttributeCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var attr = new TemplateAttribute(cmd.Name)
        {
            DataType = Enum.Parse<ScadaLink.Commons.Types.Enums.DataType>(cmd.DataType, ignoreCase: true),
            Value = cmd.Value,
            Description = cmd.Description,
            DataSourceReference = cmd.DataSourceReference,
            IsLocked = cmd.IsLocked
        };
        var result = await svc.AddAttributeAsync(cmd.TemplateId, attr, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleUpdateAttribute(IServiceProvider sp, UpdateTemplateAttributeCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var attr = new TemplateAttribute(cmd.Name)
        {
            DataType = Enum.Parse<ScadaLink.Commons.Types.Enums.DataType>(cmd.DataType, ignoreCase: true),
            Value = cmd.Value,
            Description = cmd.Description,
            DataSourceReference = cmd.DataSourceReference,
            IsLocked = cmd.IsLocked
        };
        var result = await svc.UpdateAttributeAsync(cmd.AttributeId, attr, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteAttribute(IServiceProvider sp, DeleteTemplateAttributeCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.DeleteAttributeAsync(cmd.AttributeId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleAddAlarm(IServiceProvider sp, AddTemplateAlarmCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var alarm = new TemplateAlarm(cmd.Name)
        {
            TriggerType = Enum.Parse<ScadaLink.Commons.Types.Enums.AlarmTriggerType>(cmd.TriggerType, ignoreCase: true),
            PriorityLevel = cmd.PriorityLevel,
            Description = cmd.Description,
            TriggerConfiguration = cmd.TriggerConfiguration,
            IsLocked = cmd.IsLocked
        };
        var result = await svc.AddAlarmAsync(cmd.TemplateId, alarm, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleUpdateAlarm(IServiceProvider sp, UpdateTemplateAlarmCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var alarm = new TemplateAlarm(cmd.Name)
        {
            TriggerType = Enum.Parse<ScadaLink.Commons.Types.Enums.AlarmTriggerType>(cmd.TriggerType, ignoreCase: true),
            PriorityLevel = cmd.PriorityLevel,
            Description = cmd.Description,
            TriggerConfiguration = cmd.TriggerConfiguration,
            IsLocked = cmd.IsLocked
        };
        var result = await svc.UpdateAlarmAsync(cmd.AlarmId, alarm, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteAlarm(IServiceProvider sp, DeleteTemplateAlarmCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.DeleteAlarmAsync(cmd.AlarmId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleAddScript(IServiceProvider sp, AddTemplateScriptCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var script = new TemplateScript(cmd.Name, cmd.Code)
        {
            TriggerType = cmd.TriggerType,
            TriggerConfiguration = cmd.TriggerConfiguration,
            IsLocked = cmd.IsLocked,
            ParameterDefinitions = cmd.ParameterDefinitions,
            ReturnDefinition = cmd.ReturnDefinition
        };
        var result = await svc.AddScriptAsync(cmd.TemplateId, script, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleUpdateScript(IServiceProvider sp, UpdateTemplateScriptCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var script = new TemplateScript(cmd.Name, cmd.Code)
        {
            TriggerType = cmd.TriggerType,
            TriggerConfiguration = cmd.TriggerConfiguration,
            IsLocked = cmd.IsLocked,
            ParameterDefinitions = cmd.ParameterDefinitions,
            ReturnDefinition = cmd.ReturnDefinition
        };
        var result = await svc.UpdateScriptAsync(cmd.ScriptId, script, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteScript(IServiceProvider sp, DeleteTemplateScriptCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.DeleteScriptAsync(cmd.ScriptId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleAddComposition(IServiceProvider sp, AddTemplateCompositionCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.AddCompositionAsync(cmd.TemplateId, cmd.ComposedTemplateId, cmd.InstanceName, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteComposition(IServiceProvider sp, DeleteTemplateCompositionCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<TemplateService>();
        var result = await svc.DeleteCompositionAsync(cmd.CompositionId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    // ========================================================================
    // Shared Script handlers
    // ========================================================================

    private static async Task<object?> HandleListSharedScripts(IServiceProvider sp)
    {
        var svc = sp.GetRequiredService<SharedScriptService>();
        return await svc.GetAllSharedScriptsAsync();
    }

    private static async Task<object?> HandleGetSharedScript(IServiceProvider sp, GetSharedScriptCommand cmd)
    {
        var svc = sp.GetRequiredService<SharedScriptService>();
        return await svc.GetSharedScriptByIdAsync(cmd.SharedScriptId);
    }

    private static async Task<object?> HandleCreateSharedScript(IServiceProvider sp, CreateSharedScriptCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<SharedScriptService>();
        var result = await svc.CreateSharedScriptAsync(cmd.Name, cmd.Code, cmd.ParameterDefinitions, cmd.ReturnDefinition, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleUpdateSharedScript(IServiceProvider sp, UpdateSharedScriptCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<SharedScriptService>();
        var result = await svc.UpdateSharedScriptAsync(cmd.SharedScriptId, cmd.Code, cmd.ParameterDefinitions, cmd.ReturnDefinition, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    private static async Task<object?> HandleDeleteSharedScript(IServiceProvider sp, DeleteSharedScriptCommand cmd, string user)
    {
        var svc = sp.GetRequiredService<SharedScriptService>();
        var result = await svc.DeleteSharedScriptAsync(cmd.SharedScriptId, user);
        return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
    }

    // ========================================================================
    // Database Connection Definition handlers
    // ========================================================================

    private static async Task<object?> HandleListDatabaseConnections(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        return await repo.GetAllDatabaseConnectionsAsync();
    }

    private static async Task<object?> HandleGetDatabaseConnection(IServiceProvider sp, GetDatabaseConnectionCommand cmd)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        return await repo.GetDatabaseConnectionByIdAsync(cmd.DatabaseConnectionId);
    }

    private static async Task<object?> HandleCreateDatabaseConnection(IServiceProvider sp, CreateDatabaseConnectionDefCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        var def = new DatabaseConnectionDefinition(cmd.Name, cmd.ConnectionString);
        await repo.AddDatabaseConnectionAsync(def);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "DatabaseConnection", def.Id.ToString(), def.Name, new { def.Id, def.Name });
        return def;
    }

    private static async Task<object?> HandleUpdateDatabaseConnection(IServiceProvider sp, UpdateDatabaseConnectionDefCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        var def = await repo.GetDatabaseConnectionByIdAsync(cmd.DatabaseConnectionId)
            ?? throw new InvalidOperationException($"DatabaseConnection with ID {cmd.DatabaseConnectionId} not found.");
        def.Name = cmd.Name;
        def.ConnectionString = cmd.ConnectionString;
        await repo.UpdateDatabaseConnectionAsync(def);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "DatabaseConnection", def.Id.ToString(), def.Name, new { def.Id, def.Name });
        return def;
    }

    private static async Task<object?> HandleDeleteDatabaseConnection(IServiceProvider sp, DeleteDatabaseConnectionDefCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IExternalSystemRepository>();
        await repo.DeleteDatabaseConnectionAsync(cmd.DatabaseConnectionId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "DatabaseConnection", cmd.DatabaseConnectionId.ToString(), cmd.DatabaseConnectionId.ToString(), null);
        return true;
    }

    // ========================================================================
    // Inbound API Method handlers
    // ========================================================================

    private static async Task<object?> HandleListApiMethods(IServiceProvider sp)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        return await repo.GetAllApiMethodsAsync();
    }

    private static async Task<object?> HandleGetApiMethod(IServiceProvider sp, GetApiMethodCommand cmd)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        return await repo.GetApiMethodByIdAsync(cmd.ApiMethodId);
    }

    private static async Task<object?> HandleCreateApiMethod(IServiceProvider sp, CreateApiMethodCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        var method = new ApiMethod(cmd.Name, cmd.Script)
        {
            TimeoutSeconds = cmd.TimeoutSeconds,
            ParameterDefinitions = cmd.ParameterDefinitions,
            ReturnDefinition = cmd.ReturnDefinition
        };
        await repo.AddApiMethodAsync(method);
        await repo.SaveChangesAsync();
        sp.GetService<InboundAPI.InboundScriptExecutor>()?.CompileAndRegister(method);
        await AuditAsync(sp, user, "Create", "ApiMethod", method.Id.ToString(), method.Name, method);
        return method;
    }

    private static async Task<object?> HandleUpdateApiMethod(IServiceProvider sp, UpdateApiMethodCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        var method = await repo.GetApiMethodByIdAsync(cmd.ApiMethodId)
            ?? throw new InvalidOperationException($"ApiMethod with ID {cmd.ApiMethodId} not found.");
        method.Script = cmd.Script;
        method.TimeoutSeconds = cmd.TimeoutSeconds;
        method.ParameterDefinitions = cmd.ParameterDefinitions;
        method.ReturnDefinition = cmd.ReturnDefinition;
        await repo.UpdateApiMethodAsync(method);
        await repo.SaveChangesAsync();
        sp.GetService<InboundAPI.InboundScriptExecutor>()?.CompileAndRegister(method);
        await AuditAsync(sp, user, "Update", "ApiMethod", method.Id.ToString(), method.Name, method);
        return method;
    }

    private static async Task<object?> HandleDeleteApiMethod(IServiceProvider sp, DeleteApiMethodCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        var method = await repo.GetApiMethodByIdAsync(cmd.ApiMethodId);
        await repo.DeleteApiMethodAsync(cmd.ApiMethodId);
        await repo.SaveChangesAsync();
        if (method != null)
            sp.GetService<InboundAPI.InboundScriptExecutor>()?.RemoveHandler(method.Name);
        await AuditAsync(sp, user, "Delete", "ApiMethod", cmd.ApiMethodId.ToString(), method?.Name ?? cmd.ApiMethodId.ToString(), null);
        return true;
    }

    // ========================================================================
    // Additional Security handlers (API key update, scope rules)
    // ========================================================================

    private static async Task<object?> HandleUpdateApiKey(IServiceProvider sp, UpdateApiKeyCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<IInboundApiRepository>();
        var key = await repo.GetApiKeyByIdAsync(cmd.ApiKeyId)
            ?? throw new InvalidOperationException($"ApiKey with ID {cmd.ApiKeyId} not found.");
        key.IsEnabled = cmd.IsEnabled;
        await repo.UpdateApiKeyAsync(key);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "ApiKey", key.Id.ToString(), key.Name, new { key.Id, key.Name, key.IsEnabled });
        return key;
    }

    private static async Task<object?> HandleListScopeRules(IServiceProvider sp, ListScopeRulesCommand cmd)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        return await repo.GetScopeRulesForMappingAsync(cmd.MappingId);
    }

    private static async Task<object?> HandleAddScopeRule(IServiceProvider sp, AddScopeRuleCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        var rule = new SiteScopeRule { LdapGroupMappingId = cmd.MappingId, SiteId = cmd.SiteId };
        await repo.AddScopeRuleAsync(rule);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Create", "ScopeRule", rule.Id.ToString(), $"Mapping:{cmd.MappingId}/Site:{cmd.SiteId}", rule);
        return rule;
    }

    private static async Task<object?> HandleDeleteScopeRule(IServiceProvider sp, DeleteScopeRuleCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ISecurityRepository>();
        await repo.DeleteScopeRuleAsync(cmd.ScopeRuleId);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Delete", "ScopeRule", cmd.ScopeRuleId.ToString(), cmd.ScopeRuleId.ToString(), null);
        return true;
    }

    // ========================================================================
    // Area update handler
    // ========================================================================

    private static async Task<object?> HandleUpdateArea(IServiceProvider sp, UpdateAreaCommand cmd, string user)
    {
        var repo = sp.GetRequiredService<ITemplateEngineRepository>();
        var area = await repo.GetAreaByIdAsync(cmd.AreaId)
            ?? throw new InvalidOperationException($"Area with ID {cmd.AreaId} not found.");
        area.Name = cmd.Name;
        await repo.UpdateAreaAsync(area);
        await repo.SaveChangesAsync();
        await AuditAsync(sp, user, "Update", "Area", area.Id.ToString(), area.Name, area);
        return area;
    }

    // ========================================================================
    // Remote Query handlers
    // ========================================================================

    private static async Task<object?> HandleQueryEventLogs(IServiceProvider sp, QueryEventLogsCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteIdentifier);
        var commService = sp.GetRequiredService<CommunicationService>();
        var request = new EventLogQueryRequest(
            Guid.NewGuid().ToString("N"),
            cmd.SiteIdentifier,
            cmd.From, cmd.To,
            cmd.EventType, cmd.Severity,
            cmd.InstanceName, // InstanceId filter
            cmd.Keyword,
            null, // ContinuationToken
            cmd.PageSize,
            DateTimeOffset.UtcNow);
        return await commService.QueryEventLogsAsync(cmd.SiteIdentifier, request);
    }

    private static async Task<object?> HandleQueryParkedMessages(IServiceProvider sp, QueryParkedMessagesCommand cmd, AuthenticatedUser user)
    {
        await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteIdentifier);
        var commService = sp.GetRequiredService<CommunicationService>();
        var request = new ParkedMessageQueryRequest(
            Guid.NewGuid().ToString("N"),
            cmd.SiteIdentifier,
            cmd.Page,
            cmd.PageSize,
            DateTimeOffset.UtcNow);
        return await commService.QueryParkedMessagesAsync(cmd.SiteIdentifier, request);
    }

    private static async Task<object?> HandleDebugSnapshot(IServiceProvider sp, DebugSnapshotCommand cmd, AuthenticatedUser user)
    {
        var instanceRepo = sp.GetRequiredService<ITemplateEngineRepository>();
        var instance = await instanceRepo.GetInstanceByIdAsync(cmd.InstanceId)
            ?? throw new InvalidOperationException($"Instance {cmd.InstanceId} not found.");

        EnforceSiteScope(user, instance.SiteId);

        var siteRepo = sp.GetRequiredService<ISiteRepository>();
        var site = await siteRepo.GetSiteByIdAsync(instance.SiteId)
            ?? throw new InvalidOperationException($"Site {instance.SiteId} not found.");

        var commService = sp.GetRequiredService<CommunicationService>();
        var request = new DebugSnapshotRequest(instance.UniqueName, Guid.NewGuid().ToString("N"));
        return await commService.RequestDebugSnapshotAsync(site.SiteIdentifier, request);
    }
}

/// <summary>
/// Thrown when a site-scoped user attempts an operation on a site they don't have access to.
/// </summary>
public class SiteScopeViolationException : Exception
{
    public SiteScopeViolationException(string message) : base(message) { }
}
