<?php

namespace App\Jobs;

use App\Enums\SerpCheckStatus;
use App\Models\SerpCheck;
use App\Serp\Contracts\AsyncSerpProvider;
use App\Serp\Data\SerpQuery;
use App\Serp\Exceptions\SerpProviderException;
use App\Serp\Exceptions\UnexpectedProviderResponseException;
use App\Serp\Operations\ActivationManager;
use App\Serp\Operations\BudgetManager;
use App\Serp\Operations\CircuitBreaker;
use App\Serp\Operations\OperationAuditLogger;
use App\Serp\SerpProviderRegistry;
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\Facades\Cache;
use Throwable;

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

    public int $tries = 1;

    public int $timeout = 60;

    public array $backoff = [10, 60, 300];

    public function __construct(public readonly string $checkUlid) {}

    public function handle(SerpProviderRegistry $registry, ?ActivationManager $activation = null, ?BudgetManager $budget = null, ?CircuitBreaker $circuit = null, ?OperationAuditLogger $audit = null): void
    {
        $activation ??= app(ActivationManager::class);
        $budget ??= app(BudgetManager::class);
        $circuit ??= app(CircuitBreaker::class);
        $audit ??= app(OperationAuditLogger::class);
        Cache::lock('serp-job:'.$this->checkUlid, 75)->block(1, function () use ($registry, $activation, $budget, $circuit, $audit) {
            $c = SerpCheck::where('ulid', $this->checkUlid)->firstOrFail();
            if (in_array($c->status, [SerpCheckStatus::Completed, SerpCheckStatus::Failed], true)) {
                return;
            }if ($c->provider_reference) {
                $this->poll($c);

                return;
            }if ($c->status === SerpCheckStatus::Pending && ! $c->begin()) {
                return;
            }try {
                if ($c->provider === 'dataforseo') {
                    $activation->assertPaid();
                    $circuit->assertClosed();
                    $budget->reserve('check:'.$c->ulid, 'standard', $c->id);
                }$audit->record('standard_submit_started', ['check_ulid' => $c->ulid, 'provider' => $c->provider, 'mode' => 'standard']);
                $p = $registry->resolve($c->provider);
                if (! $p instanceof AsyncSerpProvider) {
                    throw new UnexpectedProviderResponseException('Provider does not support standard mode.');
                }$s = $p->submit(new SerpQuery($c->phrase, $c->search_engine->value, $c->country_code, $c->language_code, $c->device->value, 10));
                $c->forceFill(['provider_reference' => $s->taskId, 'provider_task_posted_at' => now(), 'provider_cost' => $s->cost, 'provider_cost_currency' => $s->currency, 'provider_status_code' => $s->statusCode])->save();
                if ($c->provider === 'dataforseo') {
                    $budget->reconcile('check:'.$c->ulid, $s->cost, $s->taskId);
                    $circuit->success();
                }$audit->record('standard_submit_completed', ['check_ulid' => $c->ulid, 'provider' => $c->provider, 'mode' => 'standard', 'task_reference' => substr($s->taskId, 0, 8).'…']);
                $this->poll($c);
            } catch (SerpProviderException $e) {
                if ($c->provider === 'dataforseo') {
                    $circuit->failure($e);
                }$audit->record('standard_submit_failed', ['check_ulid' => $c->ulid, 'error_category' => $e->safeCode()]);
                if ($e->retryable()) {
                    throw $e;
                }$c->fail($e->safeCode(), $e->safeMessage());
            } catch (Throwable) {
                $c->fail('unexpected_error', 'SERP kontrolü tamamlanamadı.');
            }
        });
    }

    private function poll(SerpCheck $c): void
    {
        $at = now()->addSeconds((int) config('serp.dataforseo.poll_initial_delay'));
        $c->forceFill(['provider_next_poll_at' => $at])->save();
        PollSerpCheck::dispatch($c->ulid)->delay($at)->onQueue(config('serp.queue'));
    }

    public function failed(?Throwable $e): void
    {
        $c = SerpCheck::where('ulid', $this->checkUlid)->first();
        if ($c && $e instanceof SerpProviderException) {
            $c->fail($e->safeCode(), $e->safeMessage());
        } elseif ($c) {
            $c->fail('job_failed', 'SERP kontrolü tamamlanamadı.');
        }
    }

    public function tags(): array
    {
        return ['serp-check:'.$this->checkUlid, 'serp-mode:standard-submit'];
    }
}
