<?php

declare(strict_types=1);

namespace App\Services\PortfolioCompare;

use App\Jobs\Operations\CrossEnvironmentPromotionExecutionJob;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\OperationRunService;
use App\Services\Providers\ProviderOperationStartResult;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities;
use App\Support\OperationalControls\OperationalControlBlockedException;
use App\Support\OperationalControls\OperationalControlEvaluator;
use App\Support\OperationRunStatus;
use App\Support\PortfolioCompare\CrossEnvironmentCompareSelection;
use App\Support\PortfolioCompare\CrossEnvironmentPromotionExecutionPlanner;
use Carbon\CarbonImmutable;

final class CrossEnvironmentPromotionExecutionService
{
    public function __construct(
        private readonly CrossEnvironmentPromotionExecutionPlanner $planner,
        private readonly OperationRunService $operationRuns,
        private readonly WorkspaceAuditLogger $auditLogger,
        private readonly OperationalControlEvaluator $operationalControls,
    ) {}

    /**
     * @param  array<string, mixed>  $preview
     * @param  array<string, mixed>  $preflight
     */
    public function start(
        CrossEnvironmentCompareSelection $selection,
        array $preview,
        array $preflight,
        User $actor,
    ): ProviderOperationStartResult {
        $workspace = $selection->targetEnvironment->workspace;

        if (! $workspace instanceof Workspace) {
            throw new \RuntimeException('Promotion execution requires a workspace context.');
        }

        $decision = $this->operationalControls->evaluate('promotion.execute', $workspace);

        if ($decision->isPaused()) {
            $this->auditLogger->log(
                workspace: $workspace,
                action: AuditActionId::OperationalControlExecutionBlocked,
                context: [
                    'metadata' => array_filter([
                        'control_key' => $decision->controlKey,
                        'scope_type' => $decision->matchedScopeType,
                        'workspace_id' => (int) $workspace->getKey(),
                        'reason_text' => $decision->reasonText,
                        'expires_at' => $decision->expiresAt?->toIso8601String(),
                        'actor_id' => (int) $actor->getKey(),
                        'source_environment_id' => (int) $selection->sourceEnvironment->getKey(),
                        'target_environment_id' => (int) $selection->targetEnvironment->getKey(),
                        'requested_scope' => 'promotion.execute',
                    ], static fn (mixed $value): bool => $value !== null && $value !== ''),
                ],
                actor: $actor,
                status: 'blocked',
                resourceType: 'operational_control',
                resourceId: $decision->sourceActivationId !== null ? (string) $decision->sourceActivationId : null,
                targetLabel: 'Promotion execution',
                summary: 'Promotion execution blocked by operational control',
                tenant: $selection->targetEnvironment,
            );

            throw OperationalControlBlockedException::forDecision($decision, 'Promotion execution');
        }

        $plan = $this->planner->build($preview, $preflight);
        $providerConnection = $this->defaultProviderConnection((int) $selection->targetEnvironment->getKey());
        $now = CarbonImmutable::now();

        $identity = array_replace($plan['identity'], [
            'provider_connection_id' => $providerConnection?->getKey(),
        ]);

        $context = [
            'operation_type' => 'promotion.execute',
            'source_environment_id' => (int) $selection->sourceEnvironment->getKey(),
            'source_environment_name' => (string) $selection->sourceEnvironment->name,
            'target_environment_id' => (int) $selection->targetEnvironment->getKey(),
            'target_environment_name' => (string) $selection->targetEnvironment->name,
            'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null,
            'required_capability' => Capabilities::TENANT_MANAGE,
            'workspace_required_capability' => Capabilities::WORKSPACE_BASELINES_MANAGE,
            'target_scope' => [
                'workspace_id' => (int) $workspace->getKey(),
                'managed_environment_id' => (int) $selection->targetEnvironment->getKey(),
                'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null,
                'entra_tenant_id' => $providerConnection instanceof ProviderConnection
                    ? (string) $providerConnection->entra_tenant_id
                    : (string) ($selection->targetEnvironment->managed_environment_id ?? $selection->targetEnvironment->external_id ?? $selection->targetEnvironment->getKey()),
            ],
            'promotion_execution' => [
                'queued_at' => $now->toIso8601String(),
                'queued_by_user_id' => (int) $actor->getKey(),
                'plan' => $plan,
            ],
            'selection' => $plan['selection'],
        ];

        $run = $this->operationRuns->ensureRunWithIdentity(
            tenant: $selection->targetEnvironment,
            type: 'promotion.execute',
            identityInputs: $identity,
            context: $context,
            initiator: $actor,
        );

        if (! $run->wasRecentlyCreated) {
            return ProviderOperationStartResult::deduped($run);
        }

        $this->operationRuns->updateRun($run, OperationRunStatus::Queued->value, summaryCounts: [
            'total' => (int) $plan['summary']['ready'],
            'processed' => 0,
            'succeeded' => 0,
            'failed' => 0,
            'skipped' => 0,
            'created' => 0,
            'updated' => 0,
        ]);

        $this->operationRuns->dispatchOrFail(
            $run,
            fn (OperationRun $operationRun): mixed => CrossEnvironmentPromotionExecutionJob::dispatch($operationRun),
        );

        $this->auditLogger->logCrossEnvironmentPromotionExecutionQueued(
            workspace: $workspace,
            sourceEnvironment: $selection->sourceEnvironment,
            targetEnvironment: $selection->targetEnvironment,
            operationRun: $run->fresh() ?? $run,
            plan: $plan,
            actor: $actor,
        );

        return ProviderOperationStartResult::started($run->fresh() ?? $run, true);
    }

    private function defaultProviderConnection(int $environmentId): ?ProviderConnection
    {
        return ProviderConnection::query()
            ->where('managed_environment_id', $environmentId)
            ->where('provider', 'microsoft')
            ->where('is_default', true)
            ->orderBy('id')
            ->first();
    }
}
