<?php

namespace App\Jobs;

use App\Jobs\Middleware\EnsureQueuedExecutionLegitimate;
use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\OperationRunService;
use App\Services\Providers\MicrosoftComplianceSnapshotService;
use App\Services\Providers\ProviderGateway;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OpsUx\RunFailureSanitizer;
use App\Support\Providers\TargetScope\ProviderConnectionTargetScopeNormalizer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RuntimeException;
use Throwable;

class ProviderComplianceSnapshotJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public ?OperationRun $operationRun = null;

    public function __construct(
        public int $tenantId,
        public int $userId,
        public int $providerConnectionId,
        ?OperationRun $operationRun = null,
    ) {
        $this->operationRun = $operationRun;
    }

    /**
     * @return array<int, object>
     */
    public function middleware(): array
    {
        return [new EnsureQueuedExecutionLegitimate, new TrackOperationRun];
    }

    public function handle(
        MicrosoftComplianceSnapshotService $collector,
        ProviderGateway $gateway,
        OperationRunService $runs,
    ): void {
        $tenant = ManagedEnvironment::query()->find($this->tenantId);
        if (! $tenant instanceof ManagedEnvironment) {
            throw new RuntimeException('ManagedEnvironment not found.');
        }

        $user = User::query()->find($this->userId);
        if (! $user instanceof User) {
            throw new RuntimeException('User not found.');
        }

        $connection = ProviderConnection::query()
            ->where('managed_environment_id', $tenant->getKey())
            ->find($this->providerConnectionId);

        if (! $connection instanceof ProviderConnection) {
            throw new RuntimeException('ProviderConnection not found.');
        }

        try {
            $counts = $collector->snapshot($connection);
            $entraTenantName = $this->resolveEntraTenantName($connection, $gateway);

            if ($entraTenantName !== null) {
                $metadata = is_array($connection->metadata) ? $connection->metadata : [];
                $metadata['entra_tenant_name'] = $entraTenantName;
                $connection->update(['metadata' => $metadata]);
            }

            if ($this->operationRun instanceof OperationRun) {
                $this->updateRunTargetScope($this->operationRun, $connection, $entraTenantName);

                $runs->updateRun(
                    $this->operationRun,
                    status: OperationRunStatus::Completed->value,
                    outcome: OperationRunOutcome::Succeeded->value,
                    summaryCounts: $counts,
                );
            }
        } catch (Throwable $throwable) {
            if (! $this->operationRun instanceof OperationRun) {
                throw $throwable;
            }

            $message = RunFailureSanitizer::sanitizeMessage($throwable->getMessage());
            $reasonCode = RunFailureSanitizer::normalizeReasonCode($throwable->getMessage());

            $runs->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: OperationRunOutcome::Failed->value,
                failures: [[
                    'code' => 'compliance.snapshot.failed',
                    'reason_code' => $reasonCode,
                    'message' => $message !== '' ? $message : 'Compliance snapshot failed.',
                ]],
            );
        }
    }

    private function resolveEntraTenantName(ProviderConnection $connection, ProviderGateway $gateway): ?string
    {
        $metadata = is_array($connection->metadata) ? $connection->metadata : [];
        $existing = $metadata['entra_tenant_name'] ?? null;

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

        try {
            $response = $gateway->getOrganization($connection);
        } catch (Throwable) {
            return null;
        }

        if (! $response->successful()) {
            return null;
        }

        $displayName = $response->data['displayName'] ?? null;

        return is_string($displayName) && trim($displayName) !== '' ? trim($displayName) : null;
    }

    private function updateRunTargetScope(OperationRun $run, ProviderConnection $connection, ?string $entraTenantName): void
    {
        $context = is_array($run->context) ? $run->context : [];
        $normalizer = app(ProviderConnectionTargetScopeNormalizer::class);
        $targetScope = $normalizer->descriptorForConnection($connection)->toArray();

        if (is_string($entraTenantName) && $entraTenantName !== '') {
            $targetScope['scope_display_name'] = $entraTenantName;
        }

        $context['connection_type'] = $connection->connection_type?->value ?? $connection->connection_type;
        $context['target_scope'] = $targetScope;
        $context['provider_context'] = $normalizer->providerContext(
            provider: (string) $connection->provider,
            details: $normalizer->contextualIdentityDetailsForConnection($connection),
        );

        $run->update(['context' => $context]);
    }
}
