<?php

namespace App\Support;

use App\Services\FinanceRepository;
use Carbon\Carbon;

final class FinanceDataInsights
{
    private const MUSIC_BRANDS = ['Spotify', 'Apple Music', 'YouTube Music', 'Tidal', 'Deezer'];

    /** @param  array<string, mixed>  $sub */
    public static function subscriptionYearlyCost(array $sub): float
    {
        $c = (float) ($sub['cost'] ?? 0);
        $cycle = (string) ($sub['billingCycle'] ?? 'monthly');

        return match ($cycle) {
            'yearly' => $c,
            'quarterly' => $c * 4,
            default => $c * 12,
        };
    }

    /** @param  array<string, mixed>  $sub */
    public static function isSubscriptionBillableActive(array $sub): bool
    {
        return in_array((string) ($sub['status'] ?? ''), ['active', 'trial'], true);
    }

    public static function nameContainsAdobe(string $name): bool
    {
        $n = mb_strtolower($name);

        return str_contains($n, 'adobe') || str_contains($n, 'creative cloud');
    }

    /**
     * @param  array<int, array<string, mixed>>  $subs
     * @return list<array<string, mixed>>
     */
    public static function activeAdobeLikeSubscriptions(array $subs): array
    {
        return array_values(array_filter($subs, function (array $s): bool {
            if (! self::isSubscriptionBillableActive($s)) {
                return false;
            }

            return self::nameContainsAdobe((string) ($s['name'] ?? ''));
        }));
    }

    /**
     * @param  array<int, array<string, mixed>>  $subs
     * @return list<array<string, mixed>>
     */
    public static function activeMusicSubscriptions(array $subs): array
    {
        return array_values(array_filter($subs, function (array $s): bool {
            if (! self::isSubscriptionBillableActive($s)) {
                return false;
            }
            $n = (string) ($s['name'] ?? '');

            return collect(self::MUSIC_BRANDS)->contains(fn (string $m) => str_contains($n, $m));
        }));
    }

    /**
     * Yearly savings if the user keeps only the cheapest active music subscription.
     *
     * @param  array<int, array<string, mixed>>  $subs
     */
    public static function duplicateMusicYearlySavings(array $subs): float
    {
        $music = self::activeMusicSubscriptions($subs);
        if (count($music) <= 1) {
            return 0.0;
        }
        $yearlies = array_map(fn (array $s) => self::subscriptionYearlyCost($s), $music);

        return max(0.0, array_sum($yearlies) - min($yearlies));
    }

    /**
     * Annual spend on Adobe-like subscriptions (upper bound for savings if cancelled or replaced).
     *
     * @param  array<int, array<string, mixed>>  $subs
     */
    public static function adobeAnnualSpend(array $subs): float
    {
        $adobe = self::activeAdobeLikeSubscriptions($subs);

        return (float) array_sum(array_map(fn (array $s) => self::subscriptionYearlyCost($s), $adobe));
    }

    /**
     * @param  array<int, array<string, mixed>>  $tx
     * @return array<int, array<string, mixed>>
     */
    public static function transactionsThisMonth(array $tx): array
    {
        $start = now()->startOfMonth();
        $end = now()->endOfMonth();

        return array_values(array_filter($tx, function (array $t) use ($start, $end): bool {
            try {
                $d = Carbon::parse((string) ($t['date'] ?? ''));

                return $d->betweenIncluded($start, $end);
            } catch (\Throwable) {
                return false;
            }
        }));
    }

    /**
     * @param  array<int, array<string, mixed>>  $tx
     * @return array{income: float, expenses: float, savingsRate: float|null}
     */
    public static function monthCashFlowFromTransactions(array $tx): array
    {
        $month = self::transactionsThisMonth($tx);
        $income = 0.0;
        $expenses = 0.0;
        foreach ($month as $t) {
            if (($t['type'] ?? '') === 'income') {
                $income += (float) ($t['amount'] ?? 0);
            } elseif (($t['type'] ?? '') === 'expense') {
                $expenses += abs((float) ($t['amount'] ?? 0));
            }
        }
        $net = $income - $expenses;
        $savingsRate = $income > 0.009 ? ($net / $income) * 100 : null;

        return ['income' => $income, 'expenses' => $expenses, 'savingsRate' => $savingsRate];
    }

    /**
     * @param  array<int, array<string, mixed>>  $tx
     * @param  array<int, array<string, mixed>>  $budgets
     * @param  array<int, array<string, mixed>>  $subs
     * @param  array<int, array<string, mixed>>  $debts
     * @param  array<int, array<string, mixed>>  $goals
     * @return list<string>
     */
    public static function dashboardTips(array $tx, array $budgets, array $subs, array $debts, array $goals): array
    {
        $tips = [];
        $zombies = array_values(array_filter($subs, fn ($s) => (($s['status'] ?? '') === 'zombie')));
        if (count($zombies) > 0) {
            $perMo = (float) collect($zombies)->sum(fn ($z) => (float) ($z['cost'] ?? 0));
            $tips[] = (string) __('budgets.dashboard_widgets.ai.tip_zombies', [
                'count' => count($zombies),
                'amount' => Money::formatEur($perMo),
            ]);
        }
        $musicSave = self::duplicateMusicYearlySavings($subs);
        if ($musicSave > 0.009) {
            $tips[] = (string) __('budgets.dashboard_widgets.ai.tip_duplicate_music', ['amount' => Money::formatEur($musicSave)]);
        }
        $adobeY = self::adobeAnnualSpend($subs);
        if ($adobeY > 0.009) {
            $tips[] = (string) __('budgets.dashboard_widgets.ai.tip_adobe_review', ['amount' => Money::formatEur($adobeY)]);
        }
        $highApr = collect($debts)
            ->filter(fn ($d) => (float) ($d['interestRate'] ?? 0) >= 12 && (float) ($d['balance'] ?? 0) > 0.009)
            ->sortByDesc(fn ($d) => (float) ($d['interestRate'] ?? 0))
            ->first();
        if (is_array($highApr)) {
            $tips[] = (string) __('budgets.dashboard_widgets.ai.tip_debt_extra', [
                'name' => (string) ($highApr['name'] ?? ''),
                'rate' => number_format((float) ($highApr['interestRate'] ?? 0), 1, '.', ''),
            ]);
        }
        foreach (FinanceListOrdering::budgetsByUrgency($budgets) as $b) {
            $spent = (float) ($b['spent'] ?? 0);
            $limit = (float) ($b['limit'] ?? 0);
            if ($limit <= 0) {
                continue;
            }
            if ($spent > $limit * 1.01) {
                $tips[] = (string) __('budgets.dashboard_widgets.ai.tip_budget_over', [
                    'name' => (string) ($b['name'] ?? ''),
                    'amount' => Money::formatEur($spent - $limit),
                ]);
                break;
            }
        }
        $emergency = collect($goals)->first(fn ($g) => str_contains(mb_strtolower((string) ($g['name'] ?? '')), 'emergency'));
        if (is_array($emergency) && (float) ($emergency['target'] ?? 0) > 0.009) {
            $pct = ((float) ($emergency['saved'] ?? 0) / (float) ($emergency['target'] ?? 1)) * 100;
            if ($pct < 99.5) {
                $tips[] = (string) __('budgets.dashboard_widgets.ai.tip_emergency', [
                    'pct' => number_format(min(100, $pct), 0, '.', ''),
                    'saved' => Money::formatEur((float) ($emergency['saved'] ?? 0)),
                    'target' => Money::formatEur((float) ($emergency['target'] ?? 0)),
                ]);
            }
        }

        return array_values(array_slice($tips, 0, 3));
    }

    public static function hasFinanceContext(FinanceRepository $finance): bool
    {
        return count($finance->transactions()) > 0
            || count($finance->subscriptions()) > 0
            || count($finance->debts()) > 0
            || count($finance->budgets()) > 0
            || count($finance->goals()) > 0;
    }

    /**
     * @return list<array{icon: string, title: string, text: string, type: string}>
     */
    public static function insightPageCards(FinanceRepository $finance): array
    {
        $subs = $finance->subscriptions();
        $debts = $finance->debts();
        $goals = $finance->goals();
        $budgets = $finance->budgets();
        $tx = $finance->transactions();

        $cards = [];

        $flow = self::monthCashFlowFromTransactions($tx);
        if ($flow['income'] > 0.009 && $flow['savingsRate'] !== null && $flow['savingsRate'] >= 5) {
            $cards[] = [
                'icon' => 'shield',
                'title' => (string) __('budgets.insights_page.card_savings_rate_title'),
                'text' => (string) __('budgets.insights_page.card_savings_rate_body', [
                    'pct' => number_format((float) $flow['savingsRate'], 0, '.', ''),
                    'income' => Money::formatEur($flow['income']),
                    'expenses' => Money::formatEur($flow['expenses']),
                ]),
                'type' => 'success',
            ];
        }

        $foodMonth = 0.0;
        foreach (self::transactionsThisMonth($tx) as $t) {
            if (($t['type'] ?? '') === 'expense' && ($t['categoryId'] ?? '') === 'food') {
                $foodMonth += abs((float) ($t['amount'] ?? 0));
            }
        }
        if ($foodMonth > 0.009) {
            $cards[] = [
                'icon' => 'trending-down',
                'title' => (string) __('budgets.insights_page.card_food_month_title'),
                'text' => (string) __('budgets.insights_page.card_food_month_body', ['amount' => Money::formatEur($foodMonth)]),
                'type' => 'warning',
            ];
        }

        $musicSave = self::duplicateMusicYearlySavings($subs);
        if ($musicSave > 0.009) {
            $names = collect(self::activeMusicSubscriptions($subs))->pluck('name')->join(', ');
            $cards[] = [
                'icon' => 'repeat',
                'title' => (string) __('budgets.insights_page.card_duplicate_music_title'),
                'text' => (string) __('budgets.insights_page.card_duplicate_music_body', [
                    'names' => $names,
                    'amount' => Money::formatEur($musicSave),
                ]),
                'type' => 'info',
            ];
        }

        $adobe = self::activeAdobeLikeSubscriptions($subs);
        $adobeY = self::adobeAnnualSpend($subs);
        if ($adobeY > 0.009) {
            $usage = null;
            foreach ($adobe as $row) {
                if (isset($row['usageScore']) && is_numeric($row['usageScore'])) {
                    $usage = (int) $row['usageScore'];
                    break;
                }
            }
            $cards[] = [
                'icon' => 'zap',
                'title' => (string) __('budgets.insights_page.card_adobe_title'),
                'text' => $usage !== null
                    ? (string) __('budgets.insights_page.card_adobe_body_usage', [
                        'amount' => Money::formatEur($adobeY),
                        'pct' => (string) $usage,
                    ])
                    : (string) __('budgets.insights_page.card_adobe_body', ['amount' => Money::formatEur($adobeY)]),
                'type' => 'info',
            ];
        }

        $zombies = array_values(array_filter($subs, fn ($s) => (($s['status'] ?? '') === 'zombie')));
        if (count($zombies) > 0) {
            $zombieYearly = (float) collect($zombies)->sum(fn ($z) => (float) ($z['cost'] ?? 0)) * 12;
            $cards[] = [
                'icon' => 'alert-circle',
                'title' => (string) __('budgets.insights_page.card_zombies_title'),
                'text' => (string) __('budgets.insights_page.card_zombies_body', [
                    'names' => collect($zombies)->pluck('name')->join(', '),
                    'amount' => Money::formatEur($zombieYearly),
                ]),
                'type' => 'warning',
            ];
        }

        $emergency = collect($goals)->first(fn ($g) => str_contains(mb_strtolower((string) ($g['name'] ?? '')), 'emergency'));
        if (is_array($emergency) && (float) ($emergency['target'] ?? 0) > 0.009) {
            $saved = (float) ($emergency['saved'] ?? 0);
            $target = (float) ($emergency['target'] ?? 0);
            $pct = ($saved / $target) * 100;
            $cards[] = [
                'icon' => 'trending-up',
                'title' => (string) __('budgets.insights_page.card_emergency_title'),
                'text' => (string) __('budgets.insights_page.card_emergency_body', [
                    'pct' => number_format(min(100, $pct), 0, '.', ''),
                    'saved' => Money::formatEur($saved),
                    'target' => Money::formatEur($target),
                ]),
                'type' => 'success',
            ];
        }

        foreach ($debts as $d) {
            if ((float) ($d['balance'] ?? 0) > 0.009 && (float) ($d['interestRate'] ?? 0) >= 10) {
                $cards[] = [
                    'icon' => 'credit-card',
                    'title' => (string) __('budgets.insights_page.card_debt_title'),
                    'text' => (string) __('budgets.insights_page.card_debt_body', [
                        'name' => (string) ($d['name'] ?? ''),
                        'rate' => number_format((float) ($d['interestRate'] ?? 0), 1, '.', ''),
                        'balance' => Money::formatEur((float) ($d['balance'] ?? 0)),
                    ]),
                    'type' => 'info',
                ];
                break;
            }
        }

        foreach ($budgets as $b) {
            $spent = (float) ($b['spent'] ?? 0);
            $limit = (float) ($b['limit'] ?? 0);
            if ($limit <= 0) {
                continue;
            }
            if ($spent > $limit * 1.01) {
                $cards[] = [
                    'icon' => 'piggy-bank',
                    'title' => (string) __('budgets.insights_page.card_budget_over_title'),
                    'text' => (string) __('budgets.insights_page.card_budget_over_body', [
                        'name' => (string) ($b['name'] ?? ''),
                        'amount' => Money::formatEur($spent - $limit),
                    ]),
                    'type' => 'warning',
                ];
                break;
            }
        }

        return $cards;
    }

    /**
     * @return array{show: bool, score: int|null, savingsPct: float|null, debtIncomePct: float|null, subLoadLabelKey: string|null}
     */
    public static function healthSnapshot(FinanceRepository $finance): array
    {
        if (! self::hasFinanceContext($finance)) {
            return ['show' => false, 'score' => null, 'savingsPct' => null, 'debtIncomePct' => null, 'subLoadLabelKey' => null];
        }

        $tx = $finance->transactions();
        $subs = $finance->subscriptions();
        $debts = $finance->debts();
        $flow = self::monthCashFlowFromTransactions($tx);

        $activeSubs = array_values(array_filter($subs, fn ($s) => in_array($s['status'] ?? '', ['active', 'trial'], true)));
        $totalMonthlySubs = 0.0;
        foreach ($activeSubs as $s) {
            $c = (float) ($s['cost'] ?? 0);
            $cycle = $s['billingCycle'] ?? 'monthly';
            $totalMonthlySubs += match ($cycle) {
                'yearly' => $c / 12,
                'quarterly' => $c / 3,
                default => $c,
            };
        }

        $monthIncome = $flow['income'];
        $savingsPct = $flow['savingsRate'];
        $debtBalance = (float) collect($debts)->sum(fn ($d) => (float) ($d['balance'] ?? 0));
        $debtIncomePct = null;
        if ($monthIncome > 0.009) {
            $debtIncomePct = min(999, ($debtBalance / max(1.0, $monthIncome * 12)) * 100);
        }

        $subLoadLabelKey = null;
        if ($monthIncome > 0.009) {
            $ratio = ($totalMonthlySubs / $monthIncome) * 100;
            if ($ratio < 8) {
                $subLoadLabelKey = 'low';
            } elseif ($ratio < 18) {
                $subLoadLabelKey = 'medium';
            } else {
                $subLoadLabelKey = 'high';
            }
        }

        $score = 45;
        if ($savingsPct !== null && $savingsPct > 0) {
            $score += (int) min(28, round($savingsPct / 2.5));
        }
        if ($debtIncomePct !== null && $debtIncomePct < 25) {
            $score += 12;
        } elseif ($debtIncomePct === null || $debtBalance < 0.01) {
            $score += 8;
        }
        if ($subLoadLabelKey === 'low' || $subLoadLabelKey === 'medium') {
            $score += 8;
        } elseif ($subLoadLabelKey === null && $totalMonthlySubs < 0.01) {
            $score += 5;
        }
        $score = max(1, min(100, $score));

        return [
            'show' => true,
            'score' => $score,
            'savingsPct' => $savingsPct,
            'debtIncomePct' => $debtIncomePct,
            'subLoadLabelKey' => $subLoadLabelKey,
        ];
    }
}
