<?php

namespace App\Jobs;

use App\Core\Currency\CurrencyDriverManager;
use App\Core\Hook\HookManager;
use App\Models\Currency;
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 Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

/**
 * PRC-79 — multi-provider currency rate update with driver failover.
 *
 * Reads `fx_provider_failover_order` (CSV: ecb,open_er_api,...) and tries
 * each driver in turn until one succeeds. Per-currency rates persist with
 * an audit log row in `currency_rate_logs`.
 */
class UpdateCurrencyRates implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $tries = 3;

    public $backoff = 60;

    public function handle(CurrencyDriverManager $manager): void
    {
        $defaultCurrency = Currency::where('is_default', true)->first();
        if (! $defaultCurrency) {
            Log::error('UpdateCurrencyRates: no default currency configured');

            return;
        }

        $base = strtoupper($defaultCurrency->code);
        $order = $manager->failoverOrder();
        if (empty($order)) {
            Log::error('UpdateCurrencyRates: no fx providers configured');

            return;
        }

        $rates = null;
        $usedProvider = null;
        $failures = [];
        foreach ($order as $name) {
            try {
                $driver = $manager->driver($name);
                $rates = $driver->fetch($base);
                $usedProvider = $name;
                Log::info("UpdateCurrencyRates: provider {$name} succeeded ({$base} base, ".count($rates).' rates)');
                break;
            } catch (\Throwable $e) {
                $failures[$name] = $e->getMessage();
                Log::warning("UpdateCurrencyRates: provider {$name} failed — ".$e->getMessage());
            }
        }

        if ($rates === null) {
            Log::error('UpdateCurrencyRates: all providers failed', $failures);
            $this->fail(new \RuntimeException('All FX providers failed'));

            return;
        }

        // Allow hook handlers to inspect/override fetched rates
        $rates = app(HookManager::class)->filter('FetchCurrencyExchangeRates', $rates);

        $this->persistRates($rates, $usedProvider, $defaultCurrency);

        Cache::put('currency_rates_last_updated', now()->toDateTimeString(), now()->addDays(30));
        Cache::put('currency_rates_last_provider', $usedProvider, now()->addDays(30));
    }

    protected function persistRates(array $rates, string $provider, Currency $defaultCurrency): void
    {
        $currencies = Currency::where('status', true)
            ->where('id', '!=', $defaultCurrency->id)
            ->get();

        $updated = 0;
        $skipped = 0;
        $hasLastUpdated = Schema::hasColumn('currencies', 'last_updated_at');
        $hasLastProvider = Schema::hasColumn('currencies', 'last_provider');
        $hasLogTable = Schema::hasTable('currency_rate_logs');

        foreach ($currencies as $currency) {
            if (! isset($rates[$currency->code])) {
                $skipped++;
                continue;
            }
            $newRate = (float) $rates[$currency->code];
            if ((float) $currency->rate === $newRate) {
                $skipped++;
                continue;
            }
            $currency->rate = $newRate;
            if ($hasLastUpdated) {
                $currency->last_updated_at = now();
            }
            if ($hasLastProvider) {
                $currency->last_provider = $provider;
            }
            $currency->save();
            $updated++;

            if ($hasLogTable) {
                DB::table('currency_rate_logs')->insert([
                    'currency_code' => $currency->code,
                    'rate' => $newRate,
                    'provider' => $provider,
                    'fetched_at' => now(),
                ]);
            }
        }

        Log::info("UpdateCurrencyRates done: updated={$updated} skipped={$skipped} provider={$provider}");
    }

    public function failed(\Throwable $exception): void
    {
        Log::error('UpdateCurrencyRates failed: '.$exception->getMessage());
    }
}
