<?php

declare(strict_types=1);

namespace App\Jobs\External;

use App\Services\Dashboard\BalanceHealth;
use App\Services\External\BalanceProvider;
use App\Services\External\DadataBalanceProvider;
use App\Services\External\SupplierBalanceProvider;
use App\Services\External\YandexCloudBalanceProvider;
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\DB;

/**
 * Ежедневно собирает баланс внешних сервисов и пишет в external_service_balances.
 * Каждый провайдер изолирован: fetch() не бросает; ok=false оставляет ПРОШЛЫЙ баланс
 * + метку ошибки (плитка не падает, показывает «данные от ДАТА»). Пишет под
 * crm_supplier_worker (BYPASSRLS) — таблица системная, как supplier_sync_runs.
 *
 * Spec: docs/superpowers/specs/2026-06-28-external-service-balances-design.md
 */
class RefreshExternalBalancesJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public const DB_CONNECTION = 'pgsql_supplier'; // BYPASSRLS для записи системной таблицы

    /** @return array<int,class-string<BalanceProvider>> */
    private function providers(): array
    {
        return [
            DadataBalanceProvider::class,
            SupplierBalanceProvider::class,
            YandexCloudBalanceProvider::class,
        ];
    }

    public function handle(): void
    {
        foreach ($this->providers() as $cls) {
            /** @var BalanceProvider $p */
            $p = app($cls);
            $key = $p->serviceKey();
            $reading = $p->fetch(); // не бросает

            // Свежий query-builder на КАЖДУЮ итерацию: переиспользование одного билдера
            // накапливает where-клаузы (service_key=A AND service_key=B…) → updateOrInsert
            // ошибочно идёт в INSERT существующей строки → нарушение PK.
            $table = DB::connection(self::DB_CONNECTION)->table('external_service_balances');

            if (! $reading->ok) {
                // Оставляем прошлый баланс, помечаем ok=false + ошибку.
                $table->updateOrInsert(
                    ['service_key' => $key],
                    [
                        'ok' => false,
                        'error' => $reading->error,
                        'checked_at' => $reading->checkedAt,
                        'updated_at' => now(),
                    ],
                );

                continue;
            }

            [$red, $amber] = $this->floors($key);
            $h = BalanceHealth::evaluate((float) $reading->balance, $reading->dailySpend, $red, $amber);

            $table->updateOrInsert(
                ['service_key' => $key],
                [
                    'balance_amount' => $reading->balance,
                    'currency' => $reading->currency,
                    'daily_spend_estimate' => $reading->dailySpend,
                    'days_left' => $h['days_left'],
                    'light' => $h['light'],
                    'ok' => true,
                    'error' => null,
                    'checked_at' => $reading->checkedAt,
                    'updated_at' => now(),
                ],
            );
        }
    }

    /** @return array{0:float,1:float} [red_floor, amber_floor] */
    private function floors(string $key): array
    {
        return match ($key) {
            'dadata' => [
                (float) config('services.dadata.red_floor_rub', 500),
                (float) config('services.dadata.amber_floor_rub', 2000),
            ],
            'yandex_cloud' => [
                (float) config('services.yandex_cloud.red_floor_rub', 1000),
                (float) config('services.yandex_cloud.amber_floor_rub', 5000),
            ],
            'supplier' => [
                (float) config('services.supplier.red_floor_rub', 5000),
                (float) config('services.supplier.amber_floor_rub', 15000),
            ],
            default => [0.0, 0.0],
        };
    }
}
