<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Jobs\RouteSupplierLeadJob;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Hourly cron: re-dispatch RouteSupplierLeadJob для supplier-marked rows в
 * failed_webhook_jobs.
 *
 * Spec:
 *   - docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md §7 (retry-mechanism).
 *
 * Schema adaptation (db/schema.sql v8.11 failed_webhook_jobs):
 *   - НЕТ supplier_lead_id колонки → марка supplier-flow rows:
 *     tenant_id IS NULL AND raw_payload->>'supplier_lead_id' IS NOT NULL.
 *     Эти rows вставляются исключительно RouteSupplierLeadJob::failed() (BYPASSRLS
 *     через DB_CONNECTION='pgsql_supplier').
 *   - НЕТ retry_attempts/last_retried_at → используем existing колонки:
 *     - retry_count (INT) — counter оставшихся manual retry-attempts.
 *     - retried_at (TIMESTAMPTZ) — last retry timestamp (cooldown 1h).
 *     - resolved_at (TIMESTAMPTZ) — терминальное состояние (исключает retry).
 *     - failed_at (TIMESTAMPTZ) — 24h window (старше — skip).
 *
 * Selection criteria:
 *   1. tenant_id IS NULL (supplier-flow marker)
 *   2. raw_payload ? 'supplier_lead_id' (JSONB key existence)
 *   3. resolved_at IS NULL (не закрыт)
 *   4. failed_at >= NOW() - 24h (window safety cap)
 *   5. retry_count > 0 (есть оставшиеся попытки)
 *   6. retried_at IS NULL OR retried_at < NOW() - 1h (cooldown)
 *
 * On dispatch per row:
 *   - RouteSupplierLeadJob::dispatch(supplier_lead_id из raw_payload)
 *   - retried_at = NOW()
 *   - retry_count = retry_count - 1
 *   - если retry_count - 1 <= 0: resolved_at = NOW() (exhausted, не будет re-tried)
 *
 * Использует connection `pgsql_supplier` (BYPASSRLS-роль crm_supplier_worker) для
 * доступа к row'ам с tenant_id=NULL (политика RLS под обычной ролью их скрывает).
 */
final class RetryFailedSupplierJobsCommand extends Command
{
    /** @var string */
    protected $signature = 'supplier:retry-failed';

    /** @var string */
    protected $description = 'Re-dispatch RouteSupplierLeadJob для supplier-flow failed_webhook_jobs (hourly cron, max retries cap)';

    private const DB_CONNECTION = 'pgsql_supplier';

    private const MAX_AGE_HOURS = 24;

    private const RETRY_COOLDOWN_HOURS = 1;

    public function handle(): int
    {
        $now = Carbon::now();
        $ageCutoff = $now->copy()->subHours(self::MAX_AGE_HOURS);
        $cooldownCutoff = $now->copy()->subHours(self::RETRY_COOLDOWN_HOURS);

        $eligible = DB::connection(self::DB_CONNECTION)
            ->table('failed_webhook_jobs')
            ->whereNull('tenant_id')
            // PG JSONB key-existence operator `?` коллизирует с PDO placeholder.
            // Escape `?` как `??` (Laravel-конвенция) для прохода raw `?` в SQL.
            ->whereRaw("raw_payload ?? 'supplier_lead_id'")
            ->whereNull('resolved_at')
            ->where('failed_at', '>=', $ageCutoff)
            ->where('retry_count', '>', 0)
            ->where(function ($q) use ($cooldownCutoff) {
                $q->whereNull('retried_at')
                    ->orWhere('retried_at', '<', $cooldownCutoff);
            })
            ->orderBy('id')
            ->get();

        $dispatched = 0;
        $exhausted = 0;

        foreach ($eligible as $row) {
            $payload = $this->decodePayload($row->raw_payload);
            $supplierLeadId = isset($payload['supplier_lead_id'])
                ? (int) $payload['supplier_lead_id']
                : null;

            if ($supplierLeadId === null || $supplierLeadId <= 0) {
                // Defensive: whereRaw уже фильтрует, но decode мог дать null.
                Log::warning('supplier.retry_failed.invalid_payload', [
                    'failed_webhook_job_id' => $row->id,
                ]);

                continue;
            }

            RouteSupplierLeadJob::dispatch($supplierLeadId);

            $newRetryCount = max((int) $row->retry_count - 1, 0);
            $update = [
                'retried_at' => $now,
                'retry_count' => $newRetryCount,
            ];
            if ($newRetryCount === 0) {
                $update['resolved_at'] = $now;
                $exhausted++;
            }

            DB::connection(self::DB_CONNECTION)
                ->table('failed_webhook_jobs')
                ->where('id', $row->id)
                ->update($update);

            $dispatched++;
        }

        Log::info('supplier.retry_failed', [
            'dispatched' => $dispatched,
            'exhausted' => $exhausted,
        ]);

        $this->info("Re-dispatched {$dispatched} failed supplier webhook job(s); exhausted {$exhausted}.");

        return self::SUCCESS;
    }

    /**
     * Decode raw_payload — JSONB column возвращается как string из DB::table().
     *
     * @return array<string, mixed>
     */
    private function decodePayload(mixed $raw): array
    {
        if (is_array($raw)) {
            return $raw;
        }

        if (! is_string($raw)) {
            return [];
        }

        $decoded = json_decode($raw, true);

        return is_array($decoded) ? $decoded : [];
    }
}
