<?php

namespace App\Jobs;

use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\Tenant;
use App\Models\User;
use App\Services\OperationRunService;
use App\Services\Providers\Contracts\HealthResult;
use App\Services\Providers\MicrosoftProviderHealthCheck;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
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 TrackOperationRun];
    }

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

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

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

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

        $result = $healthCheck->check($connection);

        $this->applyHealthResult($connection, $result);

        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);

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

            return;
        }

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

    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 : [];
        $targetScope = $context['target_scope'] ?? [];
        $targetScope = is_array($targetScope) ? $targetScope : [];

        $targetScope['entra_tenant_id'] = $connection->entra_tenant_id;

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

        $context['target_scope'] = $targetScope;

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

    private function applyHealthResult(ProviderConnection $connection, HealthResult $result): void
    {
        $connection->update([
            'status' => $result->status,
            'health_status' => $result->healthStatus,
            'last_health_check_at' => now(),
            'last_error_reason_code' => $result->healthy ? null : $result->reasonCode,
            'last_error_message' => $result->healthy ? null : $result->message,
        ]);
    }
}
