<?php

namespace App\Domain\Budget\Actions;

use App\Domain\Budget\Enums\LedgerType;
use App\Domain\Budget\Models\CreditLedger;
use App\Domain\Experiment\Models\Experiment;
use Illuminate\Support\Facades\DB;

class SettleBudgetAction
{
    /**
     * Settle a budget reservation against actual cost.
     * If actual < reserved, release the difference.
     * If actual > reserved, deduct the difference.
     */
    public function execute(
        ?CreditLedger $reservation,
        int $actualCost,
        ?string $aiRunId = null,
    ): void {
        // No reservation was made (team without billing configured) — nothing to settle.
        if ($reservation === null) {
            return;
        }

        DB::transaction(function () use ($reservation, $actualCost, $aiRunId) {
            $reservedAmount = abs($reservation->amount);
            $difference = $reservedAmount - $actualCost;

            // Secondary sort by id (UUIDv7 is time-ordered) guarantees deterministic
            // ordering when several entries share the same created_at timestamp.
            $lastEntry = CreditLedger::withoutGlobalScopes()
                ->where('team_id', $reservation->team_id)
                ->lockForUpdate()
                ->orderByDesc('created_at')
                ->orderByDesc('id')
                ->first();

            $currentBalance = $lastEntry->balance_after;

            if ($difference > 0) {
                // Release excess reservation
                CreditLedger::withoutGlobalScopes()->create([
                    'team_id' => $reservation->team_id,
                    'user_id' => $reservation->user_id,
                    'experiment_id' => $reservation->experiment_id,
                    'ai_run_id' => $aiRunId,
                    'type' => LedgerType::Release,
                    'amount' => $difference,
                    'balance_after' => $currentBalance + $difference,
                    'description' => "Released excess reservation ({$difference} credits)",
                    'metadata' => [
                        'reservation_id' => $reservation->id,
                        'reserved' => $reservedAmount,
                        'actual' => $actualCost,
                    ],
                ]);
            } elseif ($difference < 0) {
                // Need additional deduction
                $extra = abs($difference);

                CreditLedger::withoutGlobalScopes()->create([
                    'team_id' => $reservation->team_id,
                    'user_id' => $reservation->user_id,
                    'experiment_id' => $reservation->experiment_id,
                    'ai_run_id' => $aiRunId,
                    'type' => LedgerType::Deduction,
                    'amount' => -$extra,
                    'balance_after' => $currentBalance - $extra,
                    'description' => "Additional cost beyond reservation ({$extra} credits)",
                    'metadata' => [
                        'reservation_id' => $reservation->id,
                        'reserved' => $reservedAmount,
                        'actual' => $actualCost,
                    ],
                ]);
            }

            // Update experiment spend tracking — lock the experiment row first so that
            // concurrent settlements for the same experiment are serialised within the
            // same transaction that already holds the CreditLedger lock.
            if ($reservation->experiment_id) {
                Experiment::withoutGlobalScopes()
                    ->lockForUpdate()
                    ->where('id', $reservation->experiment_id)
                    ->first();

                Experiment::withoutGlobalScopes()
                    ->where('id', $reservation->experiment_id)
                    ->increment('budget_spent_credits', $actualCost);
            }
        });
    }
}
