<?php

declare(strict_types=1);

namespace App\Services\Operations;

use App\Contracts\Hardening\WriteGateInterface;
use App\Exceptions\Hardening\ProviderAccessHardeningRequired;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\Tenants\TenantOperabilityService;
use App\Support\OperationCatalog;
use App\Support\Operations\ExecutionAuthorityMode;
use App\Support\Operations\ExecutionDenialReasonCode;
use App\Support\Operations\OperationRunCapabilityResolver;
use App\Support\Operations\QueuedExecutionContext;
use App\Support\Operations\QueuedExecutionLegitimacyDecision;
use App\Support\Tenants\TenantInteractionLane;
use App\Support\Tenants\TenantOperabilityQuestion;

class QueuedExecutionLegitimacyGate
{
    /**
     * @var list<string>
     */
    private const SYSTEM_AUTHORITY_ALLOWLIST = [
        'backup.schedule.execute',
        'backup.schedule.retention',
        'backup.schedule.purge',
    ];

    public function __construct(
        private readonly OperationRunCapabilityResolver $operationRunCapabilityResolver,
        private readonly CapabilityResolver $capabilityResolver,
        private readonly WorkspaceCapabilityResolver $workspaceCapabilityResolver,
        private readonly TenantOperabilityService $tenantOperabilityService,
        private readonly WriteGateInterface $writeGate,
    ) {}

    public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision
    {
        $run = OperationRun::query()->with(['tenant', 'user'])->findOrFail($run->getKey());
        $context = $this->buildContext($run);
        $checks = QueuedExecutionLegitimacyDecision::defaultChecks();

        if ($context->tenant instanceof ManagedEnvironment) {
            $checks['workspace_scope'] = ((int) $context->tenant->workspace_id === $context->workspaceId) ? 'passed' : 'failed';

            if ($checks['workspace_scope'] === 'failed') {
                return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::WorkspaceMismatch);
            }

            $checks['tenant_scope'] = 'passed';
        } elseif ($run->managed_environment_id !== null) {
            $checks['tenant_scope'] = 'failed';

            return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::TenantMissing);
        } else {
            $checks['workspace_scope'] = $context->workspaceId > 0 ? 'passed' : 'not_applicable';
        }

        if ($context->authorityMode === ExecutionAuthorityMode::ActorBound) {
            if (! $context->initiator instanceof User) {
                return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::InitiatorMissing);
            }

            if ($context->tenant instanceof ManagedEnvironment && ! $this->capabilityResolver->isMember($context->initiator, $context->tenant)) {
                $checks['tenant_scope'] = 'failed';

                return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::InitiatorNotEntitled);
            }

            if ($context->requiredCapability !== null) {
                $checks['capability'] = $this->initiatorHasRequiredCapability($context) ? 'passed' : 'failed';

                if ($checks['capability'] === 'failed') {
                    return QueuedExecutionLegitimacyDecision::deny(
                        $context,
                        $checks,
                        ExecutionDenialReasonCode::MissingCapability,
                        ['required_capability' => $context->requiredCapability],
                    );
                }
            }

            if ($context->workspaceRequiredCapability !== null) {
                $checks['capability'] = $this->initiatorHasRequiredWorkspaceCapability($context) ? 'passed' : 'failed';

                if ($checks['capability'] === 'failed') {
                    return QueuedExecutionLegitimacyDecision::deny(
                        $context,
                        $checks,
                        ExecutionDenialReasonCode::MissingCapability,
                        ['required_capability' => $context->workspaceRequiredCapability],
                    );
                }
            }
        } else {
            if (! $this->isSystemAuthorityAllowed($context->operationType)) {
                $checks['execution_prerequisites'] = 'failed';

                return QueuedExecutionLegitimacyDecision::deny(
                    $context,
                    $checks,
                    ExecutionDenialReasonCode::ExecutionPrerequisiteInvalid,
                    ['system_allowlist' => self::SYSTEM_AUTHORITY_ALLOWLIST],
                );
            }
        }

        if ($context->tenant instanceof ManagedEnvironment) {
            $operabilityQuestion = $this->questionForContext($context);
            $operability = $this->tenantOperabilityService->outcomeFor(
                tenant: $context->tenant,
                question: $operabilityQuestion,
                workspaceId: $context->workspaceId,
                lane: $this->laneForContext($context),
            );

            $checks['tenant_operability'] = $operability->allowed ? 'passed' : 'failed';

            if (! $operability->allowed) {
                return QueuedExecutionLegitimacyDecision::deny(
                    $context,
                    $checks,
                    ExecutionDenialReasonCode::TenantNotOperable,
                    [
                        'tenant_operability_question' => $operabilityQuestion->value,
                        'tenant_operability_reason_code' => $operability->reasonCode?->value,
                    ],
                );
            }
        }

        $prerequisiteDecision = $this->evaluateExecutionPrerequisites($context, $checks);

        if ($prerequisiteDecision instanceof QueuedExecutionLegitimacyDecision) {
            return $prerequisiteDecision;
        }

        return QueuedExecutionLegitimacyDecision::allow($context, $prerequisiteDecision);
    }

    public function buildContext(OperationRun $run): QueuedExecutionContext
    {
        $context = is_array($run->context) ? $run->context : [];
        $operationType = OperationCatalog::canonicalCode((string) $run->type);
        $authorityMode = ExecutionAuthorityMode::fromNullable($context['execution_authority_mode'] ?? null)
            ?? ($run->user_id === null ? ExecutionAuthorityMode::SystemAuthority : ExecutionAuthorityMode::ActorBound);
        $providerConnectionId = $this->resolveProviderConnectionId($context);
        $workspaceId = (int) ($run->workspace_id ?? $run->tenant?->workspace_id ?? 0);

        return new QueuedExecutionContext(
            run: $run,
            operationType: $operationType,
            workspaceId: $workspaceId,
            tenant: $run->tenant,
            initiator: $run->user,
            authorityMode: $authorityMode,
            requiredCapability: is_string($context['required_capability'] ?? null)
                ? $context['required_capability']
                : $this->operationRunCapabilityResolver->requiredExecutionCapabilityForType($operationType),
            workspaceRequiredCapability: is_string($context['workspace_required_capability'] ?? null)
                ? $context['workspace_required_capability']
                : null,
            providerConnectionId: $providerConnectionId,
            targetScope: [
                'workspace_id' => $workspaceId,
                'managed_environment_id' => $run->managed_environment_id !== null ? (int) $run->managed_environment_id : null,
                'provider_connection_id' => $providerConnectionId,
            ],
            prerequisiteClasses: $this->prerequisiteClassesFor($operationType, $providerConnectionId),
        );
    }

    public function isSystemAuthorityAllowed(string $operationType): bool
    {
        $operationType = OperationCatalog::canonicalCode($operationType);

        return in_array($operationType, self::SYSTEM_AUTHORITY_ALLOWLIST, true);
    }

    /**
     * @param  array{workspace_scope:string,tenant_scope:string,capability:string,tenant_operability:string,execution_prerequisites:string}  $checks
     * @return array{workspace_scope:string,tenant_scope:string,capability:string,tenant_operability:string,execution_prerequisites:string}|QueuedExecutionLegitimacyDecision
     */
    private function evaluateExecutionPrerequisites(QueuedExecutionContext $context, array $checks): array|QueuedExecutionLegitimacyDecision
    {
        if ($context->providerConnectionId !== null) {
            $validProviderConnection = ProviderConnection::query()
                ->whereKey($context->providerConnectionId)
                ->when(
                    $context->tenant instanceof ManagedEnvironment,
                    fn ($query) => $query->where('managed_environment_id', (int) $context->tenant->getKey()),
                )
                ->exists();

            if (! $validProviderConnection) {
                $checks['execution_prerequisites'] = 'failed';

                return QueuedExecutionLegitimacyDecision::deny(
                    $context,
                    $checks,
                    ExecutionDenialReasonCode::ProviderConnectionInvalid,
                    ['provider_connection_id' => $context->providerConnectionId],
                );
            }
        }

        if ($this->requiresWriteGate($context) && $context->tenant instanceof ManagedEnvironment) {
            try {
                $this->writeGate->evaluate($context->tenant, $context->operationType);
            } catch (ProviderAccessHardeningRequired $exception) {
                $checks['execution_prerequisites'] = 'failed';

                return QueuedExecutionLegitimacyDecision::deny(
                    $context,
                    $checks,
                    ExecutionDenialReasonCode::WriteGateBlocked,
                    [
                        'write_gate_reason_code' => $exception->reasonCode,
                        'write_gate_message' => $exception->reasonMessage,
                    ],
                );
            }
        }

        if ($context->prerequisiteClasses !== []) {
            $checks['execution_prerequisites'] = 'passed';
        }

        return $checks;
    }

    /**
     * @param  array<string, mixed>  $context
     */
    private function resolveProviderConnectionId(array $context): ?int
    {
        $providerConnectionId = $context['provider_connection_id'] ?? ($context['target_scope']['provider_connection_id'] ?? null);

        return is_numeric($providerConnectionId) ? (int) $providerConnectionId : null;
    }

    private function initiatorHasRequiredCapability(QueuedExecutionContext $context): bool
    {
        if (! $context->initiator instanceof User || ! is_string($context->requiredCapability) || $context->requiredCapability === '') {
            return false;
        }

        if (str_starts_with($context->requiredCapability, 'workspace')) {
            if ($context->workspaceId <= 0) {
                return false;
            }

            return $this->workspaceCapabilityResolver->can(
                $context->initiator,
                $context->run->tenant?->workspace ?? $context->run->workspace()->firstOrFail(),
                $context->requiredCapability,
            );
        }

        if (! $context->tenant instanceof ManagedEnvironment) {
            return false;
        }

        return $this->capabilityResolver->can(
            $context->initiator,
            $context->tenant,
            $context->requiredCapability,
        );
    }

    private function initiatorHasRequiredWorkspaceCapability(QueuedExecutionContext $context): bool
    {
        if (! $context->initiator instanceof User || ! is_string($context->workspaceRequiredCapability) || $context->workspaceRequiredCapability === '') {
            return false;
        }

        if ($context->workspaceId <= 0) {
            return false;
        }

        $workspace = $context->run->tenant?->workspace ?? $context->run->workspace()->first();

        if ($workspace === null) {
            return false;
        }

        return $this->workspaceCapabilityResolver->can(
            $context->initiator,
            $workspace,
            $context->workspaceRequiredCapability,
        );
    }

    /**
     * @return list<string>
     */
    private function prerequisiteClassesFor(string $operationType, ?int $providerConnectionId): array
    {
        $prerequisites = [];

        if ($providerConnectionId !== null) {
            $prerequisites[] = 'provider_connection';
        }

        if (str_starts_with($operationType, 'restore.') || $operationType === 'promotion.execute') {
            $prerequisites[] = 'write_gate';
        }

        return $prerequisites;
    }

    private function questionForContext(QueuedExecutionContext $context): TenantOperabilityQuestion
    {
        if ($context->operationType === 'promotion.execute') {
            return TenantOperabilityQuestion::RestoreEligibility;
        }

        if ($context->providerConnectionId !== null || in_array($context->operationType, ['provider.connection.check', 'compliance.snapshot', 'provider.compliance.snapshot'], true)) {
            return TenantOperabilityQuestion::VerificationReadinessEligibility;
        }

        return match ($context->operationType) {
            'restore.execute' => TenantOperabilityQuestion::RestoreEligibility,
            default => TenantOperabilityQuestion::AdministrativeDiscoverability,
        };
    }

    private function requiresWriteGate(QueuedExecutionContext $context): bool
    {
        return in_array('write_gate', $context->prerequisiteClasses, true);
    }

    private function laneForContext(QueuedExecutionContext $context): TenantInteractionLane
    {
        $runContext = is_array($context->run->context) ? $context->run->context : [];
        $wizardFlow = data_get($runContext, 'wizard.flow');

        if (is_string($wizardFlow) && trim($wizardFlow) === 'managed_environment_onboarding') {
            return TenantInteractionLane::OnboardingWorkflow;
        }

        return TenantInteractionLane::AdministrativeManagement;
    }
}
