<?php

namespace App\Console\Commands;

use App\Models\Budget;
use App\Models\Debt;
use App\Models\Goal;
use App\Models\SaasSubscription;
use App\Models\SubscriptionItem;
use App\Models\Transaction;
use App\Models\User;
use App\Services\NotificationService;
use App\Services\PreferencesStore;
use App\Support\BudgetSpentSync;
use App\Support\Money;
use Carbon\Carbon;
use Illuminate\Console\Command;

class ScanUpcomingNotifications extends Command
{
    protected $signature = 'notifications:scan-upcoming';

    protected $description = 'Scan upcoming payments (subscriptions, recurring transactions, debts), budgets, goals, and SaaS trials.';

    public function handle(NotificationService $notifications): int
    {
        $users = User::query()->get();
        if ($users->isEmpty()) {
            $this->warn('No users found — connect the expected database or run seeders/migrations, then try again.');

            return self::SUCCESS;
        }

        $emitted = 0;

        foreach ($users as $user) {
            auth()->setUser($user);
            PreferencesStore::forgetMemo();
            $prefs = PreferencesStore::all();
            $leadDays = (int) (($prefs['notifications']['reminderLeadDays'] ?? 3));
            $leadDays = max(1, min(7, $leadDays));

            $today = now()->startOfDay();
            $windowEnd = now()->copy()->addDays(14)->endOfDay();

            $subs = SubscriptionItem::query()
                ->where('user_id', $user->id)
                ->whereIn('status', ['active', 'trial'])
                ->whereNotNull('next_renewal')
                ->get();

            foreach ($subs as $sub) {
                $renewal = Carbon::parse($sub->next_renewal)->startOfDay();
                if ($renewal->lt($today) || $renewal->gt($windowEnd)) {
                    continue;
                }

                $daysUntil = (int) $today->diffInDays($renewal, false);
                if ($daysUntil < 0 || $daysUntil > 14) {
                    continue;
                }

                $isToday = $daysUntil === 0;
                $isSoon = ! $isToday && $daysUntil > 0 && $daysUntil <= $leadDays;
                if (! $isToday && ! $isSoon) {
                    continue;
                }

                $type = $isToday ? 'subscription.renewal_today' : 'subscription.renewal_soon';

                $result = $notifications->emit($user, $type, [
                    'title' => $isToday
                        ? __('notifications.types.subscription_renewal_today_title')
                        : __('notifications.types.subscription_renewal_soon_title'),
                    'message' => $isToday
                        ? __('notifications.types.subscription_renewal_today_message', ['name' => $sub->name])
                        : __('notifications.types.subscription_renewal_soon_message', [
                            'name' => $sub->name,
                            'days' => $daysUntil,
                        ]),
                    'entity_type' => 'subscription',
                    'entity_id' => $sub->external_id,
                    'dedupe_key' => 'subscription:'.$sub->id.':'.$type.':'.$renewal->toDateString(),
                ]);
                if ($result !== null) {
                    $emitted++;
                }
            }

            $recurring = Transaction::query()
                ->where('user_id', $user->id)
                ->where('is_recurring', true)
                ->whereIn('type', ['expense', 'transfer'])
                ->get();

            foreach ($recurring as $tx) {
                $anchor = Carbon::parse($tx->recurring_start_date ?? $tx->date)->startOfDay();
                $next = $this->nextRecurringOccurrence($anchor, (string) ($tx->recurring_frequency ?? 'monthly'), $today);
                if ($next === null || $next->lt($today) || $next->gt($windowEnd)) {
                    continue;
                }

                $daysUntil = (int) $today->diffInDays($next, false);
                if ($daysUntil < 0 || $daysUntil > 14) {
                    continue;
                }

                $isToday = $daysUntil === 0;
                $isSoon = ! $isToday && $daysUntil > 0 && $daysUntil <= $leadDays;
                if (! $isToday && ! $isSoon) {
                    continue;
                }

                $type = $isToday ? 'transaction.recurring_today' : 'transaction.recurring_upcoming';
                $amount = Money::formatEur(abs((float) $tx->amount));

                $result = $notifications->emit($user, $type, [
                    'title' => $isToday
                        ? __('notifications.types.recurring_payment_today_title')
                        : __('notifications.types.recurring_payment_soon_title'),
                    'message' => $isToday
                        ? __('notifications.types.recurring_payment_today_message', ['name' => $tx->name, 'amount' => $amount])
                        : __('notifications.types.recurring_payment_soon_message', [
                            'name' => $tx->name,
                            'amount' => $amount,
                            'days' => $daysUntil,
                        ]),
                    'entity_type' => 'transaction',
                    'entity_id' => $tx->external_id,
                    'dedupe_key' => 'transaction:'.$tx->id.':'.$type.':'.$next->toDateString(),
                ]);
                if ($result !== null) {
                    $emitted++;
                }
            }

            $debts = Debt::query()->where('user_id', $user->id)->whereNotNull('due_day')->get();
            foreach ($debts as $debt) {
                $dueDay = (int) $debt->due_day;
                if ($dueDay < 1 || $dueDay > 31) {
                    continue;
                }

                $dueThisMonth = $today->copy()->day(min($dueDay, $today->daysInMonth))->startOfDay();
                $dueNextMonth = $today->copy()->addMonthNoOverflow()->day(min($dueDay, $today->copy()->addMonthNoOverflow()->daysInMonth))->startOfDay();

                $dueDate = $dueThisMonth->gte($today) ? $dueThisMonth : $dueNextMonth;

                if ($dueDate->gt($windowEnd)) {
                    continue;
                }

                $daysUntil = (int) $today->diffInDays($dueDate, false);
                if ($daysUntil < 0 || $daysUntil > 14) {
                    continue;
                }

                $isToday = $daysUntil === 0;
                $isSoon = ! $isToday && $daysUntil > 0 && $daysUntil <= $leadDays;
                if (! $isToday && ! $isSoon) {
                    continue;
                }

                $type = $isToday ? 'debt.due_today' : 'debt.due_soon';

                $result = $notifications->emit($user, $type, [
                    'title' => $isToday
                        ? __('notifications.types.debt_due_today_title')
                        : __('notifications.types.debt_due_soon_title'),
                    'message' => $isToday
                        ? __('notifications.types.debt_due_today_message', ['name' => $debt->name])
                        : __('notifications.types.debt_due_soon_message', [
                            'name' => $debt->name,
                            'days' => $daysUntil,
                        ]),
                    'entity_type' => 'debt',
                    'entity_id' => $debt->external_id,
                    'dedupe_key' => 'debt:'.$debt->id.':'.$type.':'.$dueDate->toDateString(),
                ]);
                if ($result !== null) {
                    $emitted++;
                }
            }

            BudgetSpentSync::syncSpentForUser((int) $user->id);

            $budgets = Budget::query()->where('user_id', $user->id)->get();
            foreach ($budgets as $budget) {
                $limit = (float) $budget->limit_amount;
                if ($limit <= 0.0) {
                    continue;
                }

                $spent = (float) $budget->spent_amount;
                $pct = ($spent / $limit) * 100.0;
                if ($pct < 85.0) {
                    continue;
                }

                $rounded = (int) round($pct);
                if ($pct >= 100.0) {
                    $type = 'budget.over_limit';
                    $title = __('notifications.types.budget_over_limit_title');
                    $message = __('notifications.types.budget_over_limit_message', [
                        'name' => $budget->name,
                        'pct' => $rounded,
                    ]);
                    $dedupe = 'budget:'.$budget->id.':over_limit:'.$today->toDateString();
                } elseif ($pct >= 90.0) {
                    $type = 'budget.warning';
                    $title = __('notifications.types.budget_warning_title');
                    $message = __('notifications.types.budget_warning_message', [
                        'name' => $budget->name,
                        'pct' => $rounded,
                    ]);
                    $dedupe = 'budget:'.$budget->id.':warning:'.$today->toDateString();
                } else {
                    $type = 'budget.near_limit';
                    $title = __('notifications.types.budget_near_limit_title');
                    $message = __('notifications.types.budget_near_limit_message', [
                        'name' => $budget->name,
                        'pct' => $rounded,
                    ]);
                    $dedupe = 'budget:'.$budget->id.':near_limit:'.$today->toDateString();
                }

                $result = $notifications->emit($user, $type, [
                    'title' => $title,
                    'message' => $message,
                    'entity_type' => 'budget',
                    'entity_id' => $budget->external_id,
                    'dedupe_key' => $dedupe,
                ]);
                if ($result !== null) {
                    $emitted++;
                }
            }

            $goals = Goal::query()->where('user_id', $user->id)->whereNotNull('deadline')->get();
            foreach ($goals as $goal) {
                $deadline = Carbon::parse($goal->deadline)->startOfDay();
                if ($deadline->lt($today) || $deadline->gt($windowEnd->copy()->startOfDay())) {
                    continue;
                }

                $daysUntil = (int) $today->diffInDays($deadline, false);
                if ($daysUntil < 0 || $daysUntil > 14) {
                    continue;
                }

                $isToday = $daysUntil === 0;
                $isSoon = ! $isToday && $daysUntil > 0 && $daysUntil <= $leadDays;
                if (! $isToday && ! $isSoon) {
                    continue;
                }

                $type = $isToday ? 'goal.deadline_today' : 'goal.deadline_soon';
                $result = $notifications->emit($user, $type, [
                    'title' => $isToday
                        ? __('notifications.types.goal_deadline_today_title')
                        : __('notifications.types.goal_deadline_soon_title'),
                    'message' => $isToday
                        ? __('notifications.types.goal_deadline_today_message', ['name' => $goal->name])
                        : __('notifications.types.goal_deadline_soon_message', [
                            'name' => $goal->name,
                            'days' => $daysUntil,
                        ]),
                    'entity_type' => 'goal',
                    'entity_id' => $goal->external_id,
                    'dedupe_key' => 'goal:'.$goal->id.':'.$type.':'.$deadline->toDateString(),
                ]);
                if ($result !== null) {
                    $emitted++;
                }
            }

            $trials = SaasSubscription::query()
                ->where('user_id', $user->id)
                ->where('status', 'trialing')
                ->whereNotNull('trial_ends_at')
                ->get();

            foreach ($trials as $trial) {
                $trialEnds = Carbon::parse($trial->trial_ends_at)->startOfDay();
                if ($trialEnds->lt($today) || $trialEnds->gt($windowEnd)) {
                    continue;
                }

                $daysUntil = (int) $today->diffInDays($trialEnds, false);
                if ($daysUntil < 0 || $daysUntil > 14) {
                    continue;
                }

                $isToday = $daysUntil === 0;
                $isSoon = ! $isToday && $daysUntil > 0 && $daysUntil <= $leadDays;
                if (! $isToday && ! $isSoon) {
                    continue;
                }

                $result = $notifications->emit($user, 'saas.trial_ending', [
                    'title' => __('notifications.types.saas_trial_ending_title'),
                    'message' => __('notifications.types.saas_trial_ending_message', ['date' => $trialEnds->toFormattedDateString()]),
                    'entity_type' => 'saas_subscription',
                    'entity_id' => $trial->id,
                    'dedupe_key' => 'saas:trial:'.$trial->id.':'.$trialEnds->toDateString(),
                ]);
                if ($result !== null) {
                    $emitted++;
                }
            }
        }

        auth()->logout();
        PreferencesStore::forgetMemo();
        $this->info('Emitted '.$emitted.' contextual notifications.');

        return self::SUCCESS;
    }

    private function nextRecurringOccurrence(Carbon $anchor, string $frequency, Carbon $today): ?Carbon
    {
        $frequency = strtolower(trim($frequency));
        if ($frequency === '') {
            $frequency = 'monthly';
        }

        $cursor = $anchor->copy();
        $guard = 0;

        while ($cursor->lt($today) && $guard < 366 * 5) {
            $cursor = $this->stepRecurring($cursor, $frequency);
            $guard++;
        }

        if ($cursor->lt($today)) {
            return null;
        }

        return $cursor->startOfDay();
    }

    private function stepRecurring(Carbon $from, string $frequency): Carbon
    {
        return match ($frequency) {
            'daily' => $from->copy()->addDay(),
            'weekly' => $from->copy()->addWeek(),
            'biweekly' => $from->copy()->addWeeks(2),
            'quarterly' => $from->copy()->addMonthsNoOverflow(3),
            'yearly' => $from->copy()->addYearNoOverflow(),
            default => $from->copy()->addMonthNoOverflow(),
        };
    }
}
