<?php

namespace App\Services\Intune;

use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use App\Services\Providers\ProviderConnectionResolver;
use App\Services\Providers\ProviderGateway;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\RbacReason;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use RuntimeException;

class RbacOnboardingService
{
    private const DEFAULT_GROUP_NAME = 'TenantPilot-Intune-RBAC';

    public function __construct(
        private readonly GraphClientInterface $graph,
        private readonly GraphContractRegistry $contracts,
        private readonly AuditLogger $auditLogger,
        private readonly ?ProviderConnectionResolver $providerConnections = null,
        private readonly ?ProviderGateway $providerGateway = null,
    ) {}

    /**
     * Run the RBAC onboarding synchronously. Returns an array with status, ids, and warnings.
     *
     * @param  array{role_definition_id:?string,role_display_name?:?string,scope?:string,scope_group_id?:?string,group_mode?:string,group_name?:?string,existing_group_id?:?string}  $input
     * @return array{status:string,message?:string,warnings:array<int,string>,service_principal_id:?string,group_id:?string,role_definition_id:?string,role_display_name:?string,role_assignment_id:?string,steps:array<int,string>}
     */
    public function run(ManagedEnvironment $tenant, array $input, ?User $actor = null, ?string $accessToken = null): array
    {
        if (! $tenant->isActive()) {
            return $this->failure($tenant, 'ManagedEnvironment is not active', $actor);
        }

        if (empty($accessToken)) {
            return $this->failure($tenant, 'Delegated access token missing. Please sign in first.', $actor);
        }

        $roleDefinitionId = $input['role_definition_id'] ?? null;
        $roleDisplayName = $input['role_display_name'] ?? null;

        if (! $roleDefinitionId) {
            return $this->failure($tenant, 'Select an Intune RBAC role (roleDefinitionId required). Login to load roles.', $actor);
        }

        $resolution = $this->providerConnections()->resolveDefault($tenant, 'microsoft');

        if (! $resolution->resolved || ! $resolution->connection instanceof ProviderConnection) {
            $reasonCode = $resolution->effectiveReasonCode();
            $reasonMessage = $resolution->message ?? 'Provider connection is not configured.';

            return $this->failure(
                $tenant,
                sprintf(
                    '[%s] %s',
                    ProviderReasonCodes::isKnown($reasonCode) ? $reasonCode : ProviderReasonCodes::UnknownError,
                    $reasonMessage,
                ),
                $actor
            );
        }

        $connection = $resolution->connection;
        $context = $this->providerGateway()->graphOptions($connection);
        $context['access_token'] = $accessToken;
        $appClientId = is_string($context['client_id'] ?? null) ? (string) $context['client_id'] : null;

        if ($appClientId === null || $appClientId === '') {
            return $this->failure($tenant, 'Provider credential is missing client_id.', $actor);
        }

        $result = [
            'status' => 'success',
            'warnings' => [],
            'service_principal_id' => null,
            'group_id' => null,
            'role_definition_id' => $roleDefinitionId,
            'role_display_name' => $roleDisplayName,
            'role_assignment_id' => null,
            'steps' => [],
            'canaries' => [],
        ];

        $this->audit($tenant, 'rbac.setup.started', [
            'role_definition_id' => $roleDefinitionId,
            'role_display_name' => $roleDisplayName,
            'scope' => $input['scope'] ?? null,
            'provider_connection_id' => (int) $connection->getKey(),
        ], 'success', $actor);

        try {
            $servicePrincipal = $this->resolveServicePrincipal($appClientId, $context);
            $result['service_principal_id'] = $servicePrincipal['id'];
            $result['steps'][] = 'service_principal_resolved';

            $group = $this->ensureGroup($input, $context);
            $result['group_id'] = $group['id'] ?? null;
            $result['steps'][] = 'group_resolved';

            $this->ensureGroupMembership($group['id'], $servicePrincipal['id'], $context);
            $result['steps'][] = 'group_membership';

            $roleDefinition = [
                'id' => $roleDefinitionId,
                'displayName' => $roleDisplayName,
            ];
            $result['steps'][] = 'role_definition_selected';

            try {
                $assignment = $this->ensureRoleAssignment(
                    roleDefinitionId: $roleDefinition['id'],
                    groupId: $group['id'],
                    groupDisplayName: $group['displayName'] ?? null,
                    scope: $input['scope'] ?? 'all_devices',
                    scopeGroupId: $input['scope_group_id'] ?? null,
                    context: $context
                );

                $result['role_assignment_id'] = $assignment['id'] ?? null;
                $result['steps'][] = $assignment['action'];
                $manualAssignmentRequired = false;
            } catch (RuntimeException $e) {
                // Check if this is the unsupported API error
                if (str_contains($e->getMessage(), 'not support') && str_contains($e->getMessage(), 'account type')) {
                    // Partial success - group and membership created, but role assignment needs manual setup
                    $result['steps'][] = 'role_assignment_manual_required';
                    $result['warnings'][] = 'manual_role_assignment_required';
                    $manualAssignmentRequired = true;
                    $result['message'] = $e->getMessage();
                } else {
                    // Re-throw other errors
                    throw $e;
                }
            }

            $postCheck = $this->postCheck($tenant, $context, $input['scope'] ?? 'all_devices', $actor);
            $result['canaries'] = $postCheck['canaries'] ?? [];
            $result['warnings'] = array_merge(
                $result['warnings'],
                $postCheck['warnings'] ?? [],
                (($input['scope'] ?? null) === 'scope_group') ? ['scope_limited'] : []
            );

            $hasCanaryError = collect($result['canaries'])->contains(fn ($status) => $status === 'error');

            // Determine status based on what succeeded
            if ($manualAssignmentRequired) {
                $status = 'manual_assignment_required';
                $statusReason = RbacReason::ManualAssignmentRequired->value;
            } elseif ($hasCanaryError) {
                $status = 'partial';
                $statusReason = RbacReason::CanaryFailed->value;
            } else {
                $status = 'ok';
                $statusReason = null;
            }

            // Update result status to match what's being persisted
            $result['status'] = $status;

            $this->persistArtifacts($tenant, [
                'group_id' => $group['id'] ?? $input['existing_group_id'] ?? null,
                'role_assignment_id' => $result['role_assignment_id'] ?? null,
                'role_definition_id' => $roleDefinition['id'] ?? null,
                'role_display_name' => $roleDefinition['displayName'] ?? null,
                'scope_mode' => $input['scope'] ?? 'all_devices',
                'scope_id' => $input['scope_group_id'] ?? null,
                'warnings' => $result['warnings'],
                'canaries' => $result['canaries'],
                'executed_by' => $actor?->id,
                'status' => $status,
                'status_reason' => $statusReason,
            ]);

            $this->audit($tenant, 'rbac.setup.completed', [
                'role_definition_id' => $roleDefinition['id'],
                'group_id' => $group['id'],
                'role_assignment_id' => $assignment['id'] ?? null,
            ], 'success', $actor);
        } catch (RuntimeException $exception) {
            return $this->failure($tenant, $exception->getMessage(), $actor);
        }

        return $result;
    }

    /**
     * @return array{id:string}
     */
    private function resolveServicePrincipal(string $appClientId, array $context): array
    {
        $response = $this->graph->request('GET', 'servicePrincipals', [
            'query' => [
                '$filter' => "appId eq '{$appClientId}'",
            ],
        ] + $context);

        if ($response->failed()) {
            throw new RuntimeException('Failed to resolve service principal: '.json_encode($response->errors));
        }

        $servicePrincipal = $response->data['value'][0] ?? null;

        if (! $servicePrincipal || empty($servicePrincipal['id'])) {
            throw new RuntimeException('Service principal not found for provider connection client_id');
        }

        return $servicePrincipal;
    }

    /**
     * @param  array{group_mode?:string,existing_group_id?:?string,group_name?:?string}  $input
     * @return array{id:string}
     */
    private function ensureGroup(array $input, array $context): array
    {
        $mode = $input['group_mode'] ?? 'create';
        $groupName = $input['group_name'] ?? self::DEFAULT_GROUP_NAME;

        if ($mode === 'existing' && ! empty($input['existing_group_id'])) {
            return ['id' => $input['existing_group_id']];
        }

        $existing = $this->graph->request('GET', 'groups', [
            'query' => [
                '$filter' => "displayName eq '{$groupName}'",
            ],
        ] + $context);

        if ($existing->successful() && ! empty($existing->data['value'][0]['id'])) {
            return $existing->data['value'][0];
        }

        $response = $this->graph->request('POST', 'groups', [
            'json' => [
                'displayName' => $groupName,
                'mailEnabled' => false,
                'mailNickname' => $this->mailNickname($groupName),
                'securityEnabled' => true,
            ],
        ] + $context);

        if ($response->failed() || empty($response->data['id'])) {
            throw new RuntimeException('Failed to create or find security group: '.json_encode($response->errors));
        }

        return $response->data;
    }

    private function ensureGroupMembership(string $groupId, string $servicePrincipalId, array $context): void
    {
        $path = "groups/{$groupId}/members/\$ref";

        $response = $this->graph->request('POST', $path, [
            'json' => [
                '@odata.id' => "https://graph.microsoft.com/v1.0/directoryObjects/{$servicePrincipalId}",
            ],
        ] + $context);

        if ($response->failed()) {
            if ($this->referenceAlreadyExists($response->errors, $response->data)) {
                return;
            }

            $status = $response->status ?? 'unknown';
            $error = $this->extractErrorMessage($response->errors, $response->data);

            throw new RuntimeException(sprintf(
                'step=ensureGroupMembership path=/%s status=%s error=%s',
                $path,
                $status,
                $error
            ));
        }
    }

    private function referenceAlreadyExists(array $errors, array $data): bool
    {
        if ($this->isExistingReferenceError($data['error'] ?? [])) {
            return true;
        }

        if (isset($data['error']) && is_string($data['error'])) {
            $decoded = $this->decodeJsonString($data['error']);
            if ($this->isExistingReferenceError($decoded['error'] ?? $decoded)) {
                return true;
            }
        }

        foreach ($errors as $error) {
            if (is_array($error) && $this->isExistingReferenceError($error['error'] ?? $error)) {
                return true;
            }

            if (is_string($error) && $this->containsReferenceExistsString($error)) {
                return true;
            }

            if (is_string($error)) {
                $decoded = $this->decodeJsonString($error);
                if ($this->isExistingReferenceError($decoded['error'] ?? $decoded)) {
                    return true;
                }

                if ($this->containsReferenceExistsString(json_encode($decoded) ?: '')) {
                    return true;
                }
            }
        }

        if ($this->containsReferenceExistsString(json_encode($data) ?: '')) {
            return true;
        }

        if ($this->containsReferenceExistsString(json_encode($errors) ?: '')) {
            return true;
        }

        return false;
    }

    private function isExistingReferenceError(array $payload): bool
    {
        $code = $payload['code'] ?? null;
        $message = $payload['message'] ?? null;

        return $code === 'Request_BadRequest'
            && is_string($message)
            && Str::contains($message, 'added object references already exist', true);
    }

    private function containsReferenceExistsString(string $value): bool
    {
        return Str::contains(strtolower($value), 'added object references already exist');
    }

    /**
     * Check if the error indicates an unsupported account type for RBAC API.
     */
    private function isUnsupportedAccountTypeError(string $errorMessage, ?int $status): bool
    {
        return $status === 400
            && (Str::contains($errorMessage, 'This API is not supported for AAD accounts', true)
                || Str::contains($errorMessage, 'no addressUrl for Microsoft.Intune.Rbac', true));
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeJsonString(?string $value): array
    {
        if (! is_string($value) || $value === '') {
            return [];
        }

        $decoded = json_decode($value, true);

        return is_array($decoded) ? $decoded : [];
    }

    private function extractErrorMessage(array $errors, array $data): string
    {
        $message = $data['error']['message'] ?? null;

        if (is_string($message) && $message !== '') {
            return $message;
        }

        foreach ($errors as $error) {
            if (is_array($error) && is_string($error['error']['message'] ?? null)) {
                return $error['error']['message'];
            }

            if (is_array($error) && is_string($error['message'] ?? null)) {
                return $error['message'];
            }

            if (is_string($error)) {
                $decoded = $this->decodeJsonString($error);

                if (is_string($decoded['error']['message'] ?? null)) {
                    return $decoded['error']['message'];
                }

                if (is_string($decoded['message'] ?? null)) {
                    return $decoded['message'];
                }
            }

            if (is_string($error) && $error !== '') {
                return $error;
            }
        }

        return 'unknown error';
    }

    /**
     * @return array{id:?string,action:string}
     */
    private function ensureRoleAssignment(string $roleDefinitionId, string $groupId, ?string $groupDisplayName, string $scope, ?string $scopeGroupId, array $context): array
    {
        $desiredScopes = $scope === 'scope_group' && $scopeGroupId
            ? [$scopeGroupId]
            : ['/'];
        $roleAssignmentsPath = $this->contracts->rbacRoleAssignmentListPath();

        $assignments = $this->graph->request('GET', $roleAssignmentsPath, [
            'query' => [
                '$select' => 'id,displayName,resourceScopes,members',
                '$expand' => 'roleDefinition($select=id,displayName)',
            ],
        ] + $context);

        if ($assignments->failed()) {
            $status = $assignments->status ?? 'unknown';
            $error = $this->extractErrorMessage($assignments->errors, $assignments->data);

            throw new RuntimeException(sprintf(
                'step=listRoleAssignments path=%s status=%s error=%s',
                $roleAssignmentsPath,
                $status,
                $error
            ));
        }

        $assignmentsWithMembers = $this->hydrateAssignmentMembers($assignments->data['value'] ?? [], $context);

        $matching = collect($assignmentsWithMembers)
            ->first(function (array $assignment) use ($groupId, $roleDefinitionId) {
                $definition = $assignment['roleDefinition']['id'] ?? null;

                if (! $definition || strcasecmp($definition, $roleDefinitionId) !== 0) {
                    return false;
                }

                $members = $this->extractMemberIds($assignment);

                return in_array($groupId, $members, true);
            });

        $bindingBase = sprintf('https://graph.microsoft.com/%s', trim(config('graph.version', 'beta'), '/'));

        if ($matching) {
            $currentScopes = $matching['resourceScopes'] ?? [];
            if ($currentScopes === $desiredScopes) {
                return ['id' => $matching['id'] ?? null, 'action' => 'role_assignment_exists'];
            }

            $updatePath = $this->contracts->rbacRoleAssignmentItemPath((string) $matching['id']);
            $update = $this->graph->request('PATCH', $updatePath, [
                'json' => ['resourceScopes' => $desiredScopes],
            ] + $context);

            if ($update->failed()) {
                $error = $this->extractErrorMessage($update->errors, $update->data);

                if ($this->isUnsupportedAccountTypeError($error, $update->status)) {
                    throw new RuntimeException(sprintf(
                        'Automated role assignment updates are not supported for this account type. '
                        .'Please update the role assignment scope manually in the Azure Portal if needed. '
                        .'[Technical: step=updateRoleAssignment id=%s status=%s error=%s]',
                        $matching['id'],
                        $update->status ?? 'unknown',
                        $error
                    ));
                }

                throw new RuntimeException(sprintf(
                    'step=updateRoleAssignment path=%s status=%s error=%s',
                    $updatePath,
                    $update->status ?? 'unknown',
                    $error
                ));
            }

            return ['id' => $matching['id'] ?? null, 'action' => 'role_assignment_updated'];
        }

        $create = $this->graph->request('POST', $roleAssignmentsPath, [
            'json' => [
                'displayName' => "TenantPilot RBAC - {$roleDefinitionId}",
                'description' => 'TenantPilot automated RBAC setup',
                'roleDefinition@odata.bind' => "{$bindingBase}/deviceManagement/roleDefinitions/{$roleDefinitionId}",
                'members@odata.bind' => ["{$bindingBase}/groups/{$groupId}"],
                'resourceScopes' => $desiredScopes,
            ],
        ] + $context);

        if ($create->failed()) {
            $error = $this->extractErrorMessage($create->errors, $create->data);
            $requestId = $create->meta['request_id'] ?? null;
            $clientRequestId = $create->meta['client_request_id'] ?? null;

            // Check for known AAD/Entra ID account limitation
            if ($this->isUnsupportedAccountTypeError($error, $create->status)) {
                $groupLabel = $groupDisplayName ? "{$groupDisplayName} (ID: {$groupId})" : $groupId;
                $details = sprintf(
                    'The Intune RBAC API does not support automated role assignments for this account type. '
                    .'Setup is partially complete: security group %s has been created and the service principal added as member. '
                    .'The application permissions are already granted and working (verified by canary checks). '
                    .'Note: Manual Intune RBAC role assignment via Azure Portal is not required for functionality - '
                    .'the app can already access Intune resources through the granted API permissions.',
                    $groupLabel
                );

                if ($requestId || $clientRequestId) {
                    $details .= sprintf(
                        ' [Technical: step=createRoleAssignment status=%s error=%s request_id=%s client_request_id=%s]',
                        $create->status ?? 'unknown',
                        $error,
                        $requestId ?? 'n/a',
                        $clientRequestId ?? 'n/a'
                    );
                }

                throw new RuntimeException($details);
            }

            $details = sprintf(
                'step=createRoleAssignment path=%s status=%s error=%s',
                $roleAssignmentsPath,
                $create->status ?? 'unknown',
                $error
            );

            if ($requestId || $clientRequestId) {
                $details .= sprintf(
                    ' request_id=%s client_request_id=%s',
                    $requestId ?? 'n/a',
                    $clientRequestId ?? 'n/a'
                );
            }

            throw new RuntimeException($details);
        }

        return ['id' => $create->data['id'] ?? null, 'action' => 'role_assignment_created'];
    }

    /**
     * @param  array<int, array<string, mixed>>  $assignments
     * @return array<int, array<string, mixed>>
     */
    private function hydrateAssignmentMembers(array $assignments, array $context): array
    {
        return collect($assignments)
            ->map(function (array $assignment) use ($context) {
                if (empty($assignment['id'])) {
                    $assignment['members'] = $this->extractMemberIds($assignment);

                    return $assignment;
                }

                $members = $this->extractMemberIds($assignment);

                if (! empty($members)) {
                    $assignment['members'] = $members;

                    return $assignment;
                }

                $membersPath = $this->contracts->rbacRoleAssignmentItemPath((string) $assignment['id']);
                $membersResponse = $this->graph->request('GET', $membersPath, [
                    'query' => [
                        '$select' => 'id,displayName,resourceScopes,members',
                        '$expand' => 'roleDefinition($select=id,displayName)',
                    ],
                ] + $context);

                if ($membersResponse->failed()) {
                    $error = $this->extractErrorMessage($membersResponse->errors, $membersResponse->data);

                    Log::warning('rbac.role_assignments.members_missing', [
                        'assignment_id' => $assignment['id'],
                        'status' => $membersResponse->status,
                        'error' => $error,
                    ]);

                    $assignment['members'] = [];

                    return $assignment;
                }

                $assignment['members'] = $this->extractMemberIds($membersResponse->data ?? []);

                if (empty($assignment['members'])) {
                    $assignment['members'] = $this->extractMemberIds($membersResponse->data['value'] ?? []);
                }

                return $assignment;
            })
            ->all();
    }

    /**
     * @return array<int, string>
     */
    private function extractMemberIds(array $assignment): array
    {
        $members = [];

        $rawMembers = $assignment['members'] ?? [];

        if (is_array($rawMembers)) {
            foreach ($rawMembers as $member) {
                if (is_array($member) && isset($member['id'])) {
                    $members[] = (string) $member['id'];
                }

                if (is_string($member)) {
                    $members[] = $this->trimBindingId($member);
                }
            }
        }

        $bindings = $assignment['members@odata.bind'] ?? [];
        if (is_array($bindings)) {
            foreach ($bindings as $binding) {
                if (is_string($binding)) {
                    $members[] = $this->trimBindingId($binding);
                }
            }
        }

        return array_values(array_unique(array_filter($members)));
    }

    private function trimBindingId(string $binding): string
    {
        if (str_contains($binding, '/')) {
            return (string) Str::afterLast($binding, '/');
        }

        return $binding;
    }

    /**
     * @param  array{group_id:?string,role_assignment_id:?string,role_definition_id:?string,role_display_name:?string,scope_mode:?string,scope_id:?string,warnings?:array,canaries?:array,executed_by?:?int}  $artifacts
     */
    private function persistArtifacts(ManagedEnvironment $tenant, array $artifacts): void
    {
        $canaries = $artifacts['canaries'] ?? [];

        if (! array_key_exists('deviceConfigurations', $canaries)) {
            $canaries['deviceConfigurations'] = 'ok';
        }

        if (! array_key_exists('deviceCompliancePolicies', $canaries)) {
            $canaries['deviceCompliancePolicies'] = 'ok';
        }

        if (config('tenantpilot.features.conditional_access', false)) {
            if (! array_key_exists('conditionalAccess', $canaries)) {
                $canaries['conditionalAccess'] = 'ok';
            }
        } else {
            $canaries['conditionalAccess'] = 'skipped';
        }

        $warnings = array_values(array_unique($artifacts['warnings'] ?? []));

        if (config('tenantpilot.features.conditional_access', false) === false && ! in_array('ca_canary_disabled', $warnings, true)) {
            $warnings[] = 'ca_canary_disabled';
        }

        if ((($artifacts['scope_mode'] ?? null) === 'scope_group' || filled($artifacts['scope_id'] ?? null))) {
            $warnings[] = 'scope_limited';
        }

        $status = $artifacts['status'] ?? null;
        $statusReason = $artifacts['status_reason'] ?? null;

        if ($status === null) {
            $hasArtifacts = filled($artifacts['group_id'] ?? null) && filled($artifacts['role_assignment_id'] ?? null);
            $status = $hasArtifacts ? 'ok' : 'missing';
        }

        if ($status === 'missing' && $statusReason === null) {
            $statusReason = RbacReason::MissingArtifacts->value;
        }

        $tenant->update([
            'rbac_group_id' => $artifacts['group_id'] ?? $artifacts['scope_id'] ?? null,
            'rbac_role_assignment_id' => $artifacts['role_assignment_id'] ?? null,
            'rbac_role_definition_id' => $artifacts['role_definition_id'] ?? null,
            'rbac_role_display_name' => $artifacts['role_display_name'] ?? null,
            'rbac_role_key' => $artifacts['role_display_name'] ?? $artifacts['role_definition_id'] ?? null,
            'rbac_scope_mode' => $artifacts['scope_mode'] ?? null,
            'rbac_scope_id' => $artifacts['scope_id'] ?? null,
            'rbac_last_warnings' => $warnings,
            'rbac_canary_results' => $canaries,
            'rbac_last_setup_by' => $artifacts['executed_by'] ?? null,
            'rbac_last_setup_at' => now(),
            'rbac_status' => $status,
            'rbac_status_reason' => $statusReason,
        ]);
    }

    /**
     * @return array{canaries: array<string,string>, warnings: array<int,string>}
     */
    private function postCheck(ManagedEnvironment $tenant, array $context, string $scopeMode, ?User $actor = null): array
    {
        $this->audit($tenant, 'rbac.verify.started', ['scope_mode' => $scopeMode], 'success', $actor);

        $canaries = [
            'deviceConfigurations' => 'deviceManagement/deviceConfigurations?$top=1',
            'deviceCompliancePolicies' => 'deviceManagement/deviceCompliancePolicies?$top=1',
        ];

        $warnings = [];

        if (config('tenantpilot.features.conditional_access', false)) {
            $canaries['conditionalAccess'] = 'identity/conditionalAccess/policies?$top=1';
        } else {
            $warnings[] = 'ca_canary_disabled';
        }

        $errors = [];
        $results = [];

        foreach ($canaries as $key => $path) {
            $response = $this->graph->request('GET', $path, $context);

            if ($response->failed()) {
                $errors[] = $key;
                $results[$key] = 'error';

                continue;
            }

            $results[$key] = 'ok';
        }

        $this->audit($tenant, 'rbac.verify.completed', [
            'errors' => $errors,
            'scope_mode' => $scopeMode,
        ], empty($errors) ? 'success' : 'error', $actor);

        return [
            'canaries' => $results,
            'warnings' => $warnings,
        ];
    }

    private function mailNickname(string $groupName): string
    {
        $nickname = Str::slug($groupName, '_');

        return $nickname ?: 'tenantpilot_intune_rbac';
    }

    private function failure(ManagedEnvironment $tenant, string $message, ?User $actor = null): array
    {
        $this->audit($tenant, 'rbac.setup.failed', ['error' => $message], 'error', $actor);

        return [
            'status' => 'error',
            'message' => $message,
            'warnings' => [],
            'service_principal_id' => null,
            'group_id' => null,
            'role_definition_id' => null,
            'role_assignment_id' => null,
            'steps' => [],
        ];
    }

    private function providerConnections(): ProviderConnectionResolver
    {
        return $this->providerConnections ?? app(ProviderConnectionResolver::class);
    }

    private function providerGateway(): ProviderGateway
    {
        return $this->providerGateway ?? app(ProviderGateway::class);
    }

    private function audit(ManagedEnvironment $tenant, string $action, array $context, string $status, ?User $actor = null): void
    {
        $this->auditLogger->log(
            tenant: $tenant,
            action: $action,
            resourceType: 'tenant',
            resourceId: (string) $tenant->id,
            status: $status,
            context: ['metadata' => $context],
            actorId: $actor?->id,
            actorEmail: $actor?->email,
            actorName: $actor?->name,
        );
    }
}
