<?php

namespace App\Support;

use App\Models\Budget;
use App\Models\SubscriptionItem;
use App\Models\Transaction;
use Carbon\Carbon;
use Carbon\CarbonInterface;

final class BudgetSpentSync
{
    private const WEEKS_PER_MONTH = 365 / 7 / 12;

    public static function subscriptionMonthlyEquivalent(float $cost, string $billingCycle): float
    {
        return match ($billingCycle) {
            'yearly' => $cost / 12,
            'quarterly' => $cost / 3,
            default => $cost,
        };
    }

    /**
     * Convert normalized monthly subscription burn into the budget row's period.
     */
    public static function subscriptionContributionForPeriod(float $monthlyEquivalent, string $period): float
    {
        return match ($period) {
            'weekly' => $monthlyEquivalent / self::WEEKS_PER_MONTH,
            'yearly' => $monthlyEquivalent * 12,
            default => $monthlyEquivalent,
        };
    }

    /**
     * Recompute and persist spent_amount for all budgets owned by the user from:
     * - expense transactions in the budget period window (category match)
     * - active/trial subscriptions (category match), normalized to the budget period
     */
    public static function syncSpentForUser(int $userId): void
    {
        $budgets = Budget::query()->where('user_id', $userId)->get();
        if ($budgets->isEmpty()) {
            return;
        }

        $now = Carbon::now()->startOfDay();
        $yearStart = $now->copy()->startOfYear();

        $expenseTxns = Transaction::query()
            ->where('user_id', $userId)
            ->where('type', 'expense')
            ->where('date', '>=', $yearStart)
            ->get(['category_id', 'amount', 'date']);

        $subs = SubscriptionItem::query()
            ->where('user_id', $userId)
            ->whereIn('status', ['active', 'trial'])
            ->get(['category_id', 'cost', 'billing_cycle']);

        /** @var array<string, float> */
        $subMonthlyByCat = [];
        foreach ($subs as $s) {
            $cat = (string) $s->category_id;
            $m = self::subscriptionMonthlyEquivalent((float) $s->cost, (string) $s->billing_cycle);
            $subMonthlyByCat[$cat] = ($subMonthlyByCat[$cat] ?? 0.0) + $m;
        }

        foreach ($budgets as $budget) {
            $period = (string) ($budget->period ?? 'monthly');
            if (! in_array($period, ['weekly', 'monthly', 'yearly'], true)) {
                $period = 'monthly';
            }

            [$winStart, $winEnd] = self::windowBounds($now, $period);
            $cat = (string) $budget->category_id;

            $txnSum = 0.0;
            foreach ($expenseTxns as $t) {
                if ((string) $t->category_id !== $cat) {
                    continue;
                }
                $d = $t->date;
                if ($d === null) {
                    continue;
                }
                if ($d->lt($winStart) || $d->gt($winEnd)) {
                    continue;
                }
                $txnSum += abs((float) $t->amount);
            }

            $monthlySub = (float) ($subMonthlyByCat[$cat] ?? 0.0);
            $subPart = self::subscriptionContributionForPeriod($monthlySub, $period);

            $spent = round($txnSum + $subPart, 2);
            if (abs((float) $budget->spent_amount - $spent) > 0.0001) {
                $budget->spent_amount = $spent;
                $budget->save();
            }
        }
    }

    /**
     * @return array{0: CarbonInterface, 1: CarbonInterface}
     */
    private static function windowBounds(CarbonInterface $now, string $period): array
    {
        return match ($period) {
            'weekly' => [$now->copy()->startOfWeek(), $now->copy()->endOfWeek()],
            'yearly' => [$now->copy()->startOfYear(), $now->copy()->endOfYear()],
            default => [$now->copy()->startOfMonth(), $now->copy()->endOfMonth()],
        };
    }
}
