<?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\Audit\WorkspaceAuditLogger;
use App\Services\Intune\AuditLogger as TenantAuditLogger;
use App\Services\Intune\ManagedEnvironmentPermissionService;
use App\Services\OperationRunService;
use App\Services\Providers\Contracts\HealthResult;
use App\Services\Providers\MicrosoftProviderHealthCheck;
use App\Services\Providers\ProviderConnectionStateProjector;
use App\Services\Providers\ProviderGateway;
use App\Services\Providers\ProviderIdentityResolution;
use App\Services\Providers\ProviderIdentityResolver;
use App\Support\Audit\AuditActionId;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Providers\ProviderNextStepsRegistry;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\Providers\TargetScope\ProviderConnectionTargetScopeNormalizer;
use App\Support\Verification\ManagedEnvironmentPermissionCheckClusters;
use App\Support\Verification\VerificationReportWriter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use RuntimeException;

class ProviderConnectionHealthCheckJob 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(
        MicrosoftProviderHealthCheck $healthCheck,
        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.');
        }

        $result = $healthCheck->check($connection);
        $previousConsentStatus = $connection->consent_status?->value ?? $connection->consent_status;
        $previousVerificationStatus = $connection->verification_status?->value ?? $connection->verification_status;
        $stateTransition = $this->applyHealthResult($connection, $result);
        $identity = app(ProviderIdentityResolver::class)->resolve($connection);

        if (! $this->operationRun instanceof OperationRun) {
            return;
        }

        $entraTenantName = $this->resolveEntraTenantName($connection, $result);

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

        $this->updateRunTargetScope($this->operationRun, $connection, $entraTenantName);

        $permissionService = app(ManagedEnvironmentPermissionService::class);

        $graphOptions = null;

        if ($result->healthy) {
            try {
                $graphOptions = app(ProviderGateway::class)->graphOptions($connection);
            } catch (\Throwable) {
                $graphOptions = null;
            }
        }

        $permissionComparison = $result->healthy
                ? ($graphOptions === null
                    ? $permissionService->compare(
                        $tenant,
                        persist: false,
                        liveCheck: false,
                        useConfiguredStub: false,
                    )
                    : $permissionService->compare(
                        $tenant,
                        persist: true,
                        liveCheck: true,
                        useConfiguredStub: false,
                        graphOptions: $graphOptions,
                    ))
                : $permissionService->compare(
                    $tenant,
                    persist: false,
                    liveCheck: false,
                    useConfiguredStub: false,
                );

        $permissionRows = $permissionComparison['permissions'] ?? [];
        $permissionRows = is_array($permissionRows) ? $permissionRows : [];

        $inventory = null;

        if (! $result->healthy) {
            $inventory = [
                'fresh' => false,
                'reason_code' => $stateTransition['effective_reason_code'] ?? $result->reasonCode ?? 'dependency_unreachable',
                'message' => 'Provider connection check failed; permissions were not refreshed during this run.',
            ];
        } elseif ($graphOptions === null) {
            $inventory = [
                'fresh' => false,
                'reason_code' => $identity->effectiveReasonCode(),
                'message' => 'Provider identity could not be resolved; observed permissions inventory was not refreshed during this run.',
            ];
        } else {
            $liveCheck = $permissionComparison['live_check'] ?? null;
            $liveCheck = is_array($liveCheck) ? $liveCheck : [];

            $reasonCode = is_string($liveCheck['reason_code'] ?? null) ? (string) $liveCheck['reason_code'] : 'dependency_unreachable';
            $appId = is_string($liveCheck['app_id'] ?? null) && $liveCheck['app_id'] !== '' ? (string) $liveCheck['app_id'] : null;
            $observedCount = is_numeric($liveCheck['observed_permissions_count'] ?? null)
                ? (int) $liveCheck['observed_permissions_count']
                : null;

            $message = ($liveCheck['succeeded'] ?? false) === true
                ? 'Observed permissions inventory refreshed successfully.'
                : match ($reasonCode) {
                    'permissions_inventory_empty' => $appId !== null
                        ? sprintf('No application permissions were detected for app id %s. Verify admin consent was granted for this exact app registration, then retry verification.', $appId)
                        : 'No application permissions were detected. Verify admin consent was granted for the configured app registration, then retry verification.',
                    default => 'Unable to refresh observed permissions inventory during this run. Retry verification.',
                };

            $inventory = [
                'fresh' => ($liveCheck['succeeded'] ?? false) === true,
                'reason_code' => $reasonCode,
                'message' => $message,
                'app_id' => $appId,
                'observed_permissions_count' => $observedCount,
            ];
        }

        $permissionChecks = ManagedEnvironmentPermissionCheckClusters::buildChecks($tenant, $permissionRows, $inventory);
        $targetScope = app(ProviderConnectionTargetScopeNormalizer::class)
            ->descriptorForConnection($connection)
            ->toArray();

        $report = VerificationReportWriter::write(
            run: $this->operationRun,
            checks: [
                [
                    'key' => 'provider.connection.check',
                    'title' => 'Provider connection check',
                    'status' => $result->healthy ? 'pass' : 'fail',
                    'severity' => $result->healthy ? 'info' : 'critical',
                    'blocking' => ! $result->healthy,
                    'reason_code' => $result->healthy ? 'ok' : ($stateTransition['effective_reason_code'] ?? $result->reasonCode ?? 'unknown_error'),
                    'message' => $result->healthy ? 'Connection is healthy.' : ($result->message ?? 'Health check failed.'),
                    'evidence' => array_values(array_filter([
                        [
                            'kind' => 'provider_connection_id',
                            'value' => (int) $connection->getKey(),
                        ],
                        [
                            'kind' => 'target_scope_identifier',
                            'value' => (string) ($targetScope['scope_identifier'] ?? $connection->entra_tenant_id),
                        ],
                        is_numeric($result->meta['http_status'] ?? null) ? [
                            'kind' => 'http_status',
                            'value' => (int) $result->meta['http_status'],
                        ] : null,
                        is_string($result->meta['organization_id'] ?? null) ? [
                            'kind' => 'organization_id',
                            'value' => (string) $result->meta['organization_id'],
                        ] : null,
                    ])),
                    'next_steps' => $result->healthy
                        ? []
                        : app(ProviderNextStepsRegistry::class)->forReason(
                            $tenant,
                            is_string($stateTransition['effective_reason_code'] ?? null) && $stateTransition['effective_reason_code'] !== ''
                                ? (string) $stateTransition['effective_reason_code']
                                : (is_string($result->reasonCode) && $result->reasonCode !== ''
                                    ? $result->reasonCode
                                    : 'unknown_error'),
                            $connection,
                        ),
                ],
                ...$permissionChecks,
            ],
            identity: [
                'provider_connection_id' => (int) $connection->getKey(),
                'target_scope' => $targetScope,
            ],
        );

        // Dispatch posture finding generation when permission comparison is available
        if (($permissionComparison['overall_status'] ?? null) !== 'error') {
            GeneratePermissionPostureFindingsJob::dispatch(
                (int) $tenant->getKey(),
                $permissionComparison,
            );
        }

        if ($result->healthy) {
            $run = $runs->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: OperationRunOutcome::Succeeded->value,
            );

            $this->logVerificationResult(
                tenant: $tenant,
                actor: $user,
                connection: $connection,
                run: $run,
                identity: $identity,
                outcomeStatus: 'success',
                reasonCode: null,
                previousConsentStatus: is_string($previousConsentStatus) ? $previousConsentStatus : null,
                previousVerificationStatus: is_string($previousVerificationStatus) ? $previousVerificationStatus : null,
            );
            $this->logVerificationCompletion($tenant, $user, $run, $report);

            return;
        }

        $reasonCode = is_string($stateTransition['effective_reason_code'] ?? null) && $stateTransition['effective_reason_code'] !== ''
            ? (string) $stateTransition['effective_reason_code']
            : (is_string($result->reasonCode) && $result->reasonCode !== ''
                ? $result->reasonCode
                : 'unknown_error');

        $nextSteps = app(ProviderNextStepsRegistry::class)->forReason(
            $tenant,
            $reasonCode,
            $connection,
        );

        if (in_array($reasonCode, [
            ProviderReasonCodes::ProviderConsentMissing,
            ProviderReasonCodes::ProviderConsentFailed,
            ProviderReasonCodes::ProviderConsentRevoked,
        ], true)) {
            $run = $runs->finalizeBlockedRun(
                $this->operationRun,
                reasonCode: $reasonCode,
                nextSteps: $nextSteps,
                message: $result->message ?? 'Admin consent is required before verification can proceed.',
            );

            $this->logVerificationResult(
                tenant: $tenant,
                actor: $user,
                connection: $connection,
                run: $run,
                identity: $identity,
                outcomeStatus: 'failed',
                reasonCode: $reasonCode,
                previousConsentStatus: is_string($previousConsentStatus) ? $previousConsentStatus : null,
                previousVerificationStatus: is_string($previousVerificationStatus) ? $previousVerificationStatus : null,
            );
            if (($stateTransition['consent_revoked_detected'] ?? false) === true) {
                $this->logConsentRevocation(
                    tenant: $tenant,
                    actor: $user,
                    connection: $connection,
                    run: $run,
                    previousConsentStatus: is_string($previousConsentStatus) ? $previousConsentStatus : null,
                    detectedReasonCode: $result->reasonCode,
                );
            }
            $this->logVerificationCompletion($tenant, $user, $run, $report);

            return;
        }

        $run = $runs->updateRun(
            $this->operationRun,
            status: OperationRunStatus::Completed->value,
            outcome: OperationRunOutcome::Failed->value,
            failures: [[
                'code' => 'provider.connection.check.failed',
                'reason_code' => $reasonCode,
                'message' => $result->message ?? 'Health check failed.',
            ]],
        );

        $this->logVerificationResult(
            tenant: $tenant,
            actor: $user,
            connection: $connection,
            run: $run,
            identity: $identity,
            outcomeStatus: 'failed',
            reasonCode: $reasonCode,
            previousConsentStatus: is_string($previousConsentStatus) ? $previousConsentStatus : null,
            previousVerificationStatus: is_string($previousVerificationStatus) ? $previousVerificationStatus : null,
        );
        if (($stateTransition['consent_revoked_detected'] ?? false) === true) {
            $this->logConsentRevocation(
                tenant: $tenant,
                actor: $user,
                connection: $connection,
                run: $run,
                previousConsentStatus: is_string($previousConsentStatus) ? $previousConsentStatus : null,
                detectedReasonCode: $result->reasonCode,
            );
        }
        $this->logVerificationCompletion($tenant, $user, $run, $report);
    }

    private function resolveEntraTenantName(ProviderConnection $connection, HealthResult $result): ?string
    {
        $existing = Arr::get(is_array($connection->metadata) ? $connection->metadata : [], 'entra_tenant_name');

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

        $candidate = $result->meta['organization_display_name'] ?? null;

        return is_string($candidate) && trim($candidate) !== '' ? trim($candidate) : 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();

        $context['connection_type'] = $connection->connection_type?->value ?? $connection->connection_type;

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

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

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

    /**
     * @return array{effective_reason_code: ?string, consent_revoked_detected: bool}
     */
    private function applyHealthResult(ProviderConnection $connection, HealthResult $result): array
    {
        $projected = app(ProviderConnectionStateProjector::class)->projectVerificationOutcome($connection, $result);

        $connection->update([
            'consent_status' => $projected['consent_status'],
            'verification_status' => $projected['verification_status'],
            'last_health_check_at' => now(),
            'last_error_reason_code' => $projected['last_error_reason_code'],
            'last_error_message' => $projected['last_error_message'],
            'consent_last_checked_at' => now(),
            'consent_error_code' => $projected['consent_error_code'],
            'consent_error_message' => $projected['consent_error_message'],
        ]);

        return [
            'effective_reason_code' => $projected['last_error_reason_code'],
            'consent_revoked_detected' => $projected['consent_revoked_detected'],
        ];
    }

    /**
     * @param  array<string, mixed>  $report
     */
    private function logVerificationCompletion(ManagedEnvironment $tenant, User $actor, OperationRun $run, array $report): void
    {
        $workspace = $tenant->workspace;

        if (! $workspace) {
            return;
        }

        $counts = $report['summary']['counts'] ?? [];
        $counts = is_array($counts) ? $counts : [];

        app(WorkspaceAuditLogger::class)->log(
            workspace: $workspace,
            action: AuditActionId::VerificationCompleted->value,
            context: [
                'metadata' => [
                    'operation_run_id' => (int) $run->getKey(),
                    'counts' => $counts,
                ],
            ],
            actor: $actor,
            status: $run->outcome === OperationRunOutcome::Succeeded->value ? 'success' : 'failed',
            resourceType: 'operation_run',
            resourceId: (string) $run->getKey(),
        );
    }

    private function logVerificationResult(
        ManagedEnvironment $tenant,
        User $actor,
        ProviderConnection $connection,
        OperationRun $run,
        ProviderIdentityResolution $identity,
        string $outcomeStatus,
        ?string $reasonCode,
        ?string $previousConsentStatus,
        ?string $previousVerificationStatus,
    ): void {
        app(TenantAuditLogger::class)->log(
            tenant: $tenant,
            action: 'provider_connection.verification_result',
            context: [
                'metadata' => [
                    'provider_connection_id' => (int) $connection->getKey(),
                    'connection_type' => $connection->connection_type?->value ?? $connection->connection_type,
                    'is_enabled' => (bool) $connection->is_enabled,
                    'consent_status' => $connection->consent_status?->value ?? $connection->consent_status,
                    'verification_status' => $connection->verification_status?->value ?? $connection->verification_status,
                    'credential_source' => $identity->credentialSource,
                    'effective_client_id' => $identity->effectiveClientId,
                    'target_scope' => $identity->targetScope?->toArray(),
                    'provider_context' => array_map(
                        static fn ($detail): array => $detail->toArray(),
                        $identity->providerContextDetails,
                    ),
                    'reason_code' => $reasonCode,
                    'operation_run_id' => (int) $run->getKey(),
                    'previous_consent_status' => $previousConsentStatus,
                    'previous_verification_status' => $previousVerificationStatus,
                ],
            ],
            actorId: (int) $actor->getKey(),
            actorEmail: $actor->email,
            actorName: $actor->name,
            status: $outcomeStatus,
            resourceType: 'provider_connection',
            resourceId: (string) $connection->getKey(),
            operationRunId: (int) $run->getKey(),
        );
    }

    private function logConsentRevocation(
        ManagedEnvironment $tenant,
        User $actor,
        ProviderConnection $connection,
        OperationRun $run,
        ?string $previousConsentStatus,
        ?string $detectedReasonCode,
    ): void {
        app(TenantAuditLogger::class)->log(
            tenant: $tenant,
            action: 'provider_connection.consent_revoked',
            context: [
                'metadata' => [
                    'provider_connection_id' => (int) $connection->getKey(),
                    'connection_type' => $connection->connection_type?->value ?? $connection->connection_type,
                    'previous_consent_status' => $previousConsentStatus,
                    'consent_status' => $connection->consent_status?->value ?? $connection->consent_status,
                    'verification_status' => $connection->verification_status?->value ?? $connection->verification_status,
                    'detected_reason_code' => $detectedReasonCode,
                    'operation_run_id' => (int) $run->getKey(),
                ],
            ],
            actorId: (int) $actor->getKey(),
            actorEmail: $actor->email,
            actorName: $actor->name,
            status: 'failed',
            resourceType: 'provider_connection',
            resourceId: (string) $connection->getKey(),
            operationRunId: (int) $run->getKey(),
        );
    }
}
