<?php

namespace App\Serp\Operations;

use App\Models\SerpBudgetEntry;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

final class BudgetManager
{
    public function __construct(private KillSwitch $kill, private OperationAuditLogger $audit, private RampUpPolicy $ramp) {}

    public function reserve(string $key, string $mode, ?int $checkId = null): SerpBudgetEntry
    {
        return Cache::lock('serp-budget-global', 10)->block(5, function () use ($key, $mode, $checkId) {
            $result = DB::transaction(function () use ($key, $mode, $checkId) {
                if ($e = SerpBudgetEntry::where('reservation_key', $key)->lockForUpdate()->first()) {
                    return $e;
                }

                $this->ramp->assertAllowsNewPaid();
                $estimate = Money::micros((string) config('serp.estimated_costs.'.$mode));
                $day = SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfDay())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END'));
                $month = SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfMonth())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END'));
                if ($day + $estimate > Money::micros((string) config('serp.daily_budget_usd')) || $month + $estimate > Money::micros((string) config('serp.monthly_budget_usd'))) {
                    return false;
                }

                return SerpBudgetEntry::create(['reservation_key' => $key, 'serp_check_id' => $checkId, 'mode' => $mode, 'reserved_micros' => $estimate, 'currency' => 'USD', 'reserved_at' => now('UTC')]);
            });
            if ($result === false) {
                $this->kill->activate('budget_exceeded');
                $this->audit->record('budget_blocked', ['cost_micros' => Money::micros((string) config('serp.estimated_costs.'.$mode))]);
                throw new OperationBlockedException('budget_blocked');
            }

            $this->audit->record('budget_reserved', ['mode' => $mode, 'cost_micros' => $result->reserved_micros]);

            return $result;
        });
    }

    public function reconcile(string $key, string|int|float|null $actual, ?string $taskId = null): void
    {
        DB::transaction(function () use ($key, $actual, $taskId) {
            $e = SerpBudgetEntry::where('reservation_key', $key)->lockForUpdate()->first();
            if (! $e || $e->reconciled_at) {
                return;
            }$e->forceFill(['provider_task_id' => $taskId, 'actual_micros' => Money::micros($actual), 'reconciled_at' => now('UTC')])->save();
            $this->audit->record('budget_reconciled', ['cost_micros' => $e->actual_micros]);
        });
    }

    public function usage(): array
    {
        $daily = (int) SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfDay())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END'));
        $monthly = (int) SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfMonth())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END'));

        return ['daily_micros' => $daily, 'monthly_micros' => $monthly];
    }
}
