<?php

namespace App\Console\Commands;

use App\Mail\DuePaymentsAlert;
use App\Models\User;
use App\Models\UserPreference;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;

class SendDuePaymentsAlert extends Command
{
    protected $signature = 'notify:due-payments {--user= : Send to specific user ID} {--force : Send even if disabled}';
    protected $description = 'Send email alerts about overdue and upcoming payments';

    // Exchange rates cache (same as ReportController)
    private array $exchangeRates = [
        'EUR' => 1.0,
        'USD' => 0.92,
        'BRL' => 0.17,
        'GBP' => 1.16,
    ];

    public function handle(): void
    {
        $currentTime = now()->format('H:i');
        $this->info("Running due payments notification at {$currentTime}");

        // Get users with notification enabled
        $query = UserPreference::where('notify_due_payments', true);
        
        if ($userId = $this->option('user')) {
            $query->where('user_id', $userId);
        }

        // Filter by time (within 5 minute window) unless forced
        if (!$this->option('force')) {
            $query->whereRaw("TIME_FORMAT(notify_due_payments_time, '%H:%i') = ?", [$currentTime]);
        }

        $preferences = $query->with('user')->get();

        if ($preferences->isEmpty()) {
            $this->info('No users to notify at this time.');
            return;
        }

        foreach ($preferences as $preference) {
            $this->processUser($preference);
        }

        $this->info('Done!');
    }

    private function processUser(UserPreference $preference): void
    {
        $user = $preference->user;
        $this->info("Processing user: {$user->name} ({$user->email})");

        $today = now()->format('Y-m-d');
        $tomorrow = now()->addDay()->format('Y-m-d');

        // 1. Get all account balances
        $accountBalances = $this->getAccountBalances($user->id);
        $totalAvailable = array_sum(array_column($accountBalances, 'balance_converted'));

        // 2. Get overdue items (date < today)
        $overdueItems = $this->getOverdueItems($user->id, $today);

        // 3. Get tomorrow items (date = tomorrow)
        $tomorrowItems = $this->getTomorrowItems($user->id, $tomorrow);

        // If no items, skip
        if (empty($overdueItems) && empty($tomorrowItems)) {
            $this->info("  No overdue or tomorrow items for {$user->name}");
            return;
        }

        // 4. Calculate totals
        $allItems = array_merge($overdueItems, $tomorrowItems);
        $totalDue = array_sum(array_column($allItems, 'amount_converted'));
        $shortage = max(0, $totalDue - $totalAvailable);

        // 5. Determine which items can be paid (priority: most overdue first)
        usort($allItems, fn($a, $b) => $b['days_overdue'] <=> $a['days_overdue']);
        
        $payableItems = [];
        $unpayableItems = [];
        $remainingBalance = $totalAvailable;

        foreach ($allItems as $item) {
            if ($remainingBalance >= $item['amount_converted']) {
                $item['can_pay'] = true;
                $payableItems[] = $item;
                $remainingBalance -= $item['amount_converted'];
            } else {
                $item['can_pay'] = false;
                $item['partial_available'] = $remainingBalance > 0 ? $remainingBalance : 0;
                $unpayableItems[] = $item;
            }
        }

        // 6. Suggest transfers if needed
        $transferSuggestions = $this->suggestTransfers($accountBalances, $payableItems);

        // 7. Get primary currency
        $primaryCurrency = $this->getPrimaryCurrency($user->id);

        // 8. Send email
        $email = $preference->getNotificationEmail();
        
        try {
            Mail::to($email)->send(new DuePaymentsAlert(
                userName: $user->name,
                overdueItems: $overdueItems,
                tomorrowItems: $tomorrowItems,
                accountBalances: $accountBalances,
                totalAvailable: round($totalAvailable, 2),
                totalDue: round($totalDue, 2),
                shortage: round($shortage, 2),
                payableItems: $payableItems,
                unpayableItems: $unpayableItems,
                transferSuggestions: $transferSuggestions,
                currency: $primaryCurrency
            ));
            
            $this->info("  ✓ Email sent to {$email}");
        } catch (\Exception $e) {
            $this->error("  ✗ Failed to send email: " . $e->getMessage());
            \Log::error("DuePaymentsAlert failed for user {$user->id}: " . $e->getMessage());
        }
    }

    private function getAccountBalances(int $userId): array
    {
        $accounts = DB::select("
            SELECT 
                a.id,
                a.name,
                a.currency,
                COALESCE(
                    (SELECT SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE -t.amount END)
                     FROM transactions t 
                     WHERE t.account_id = a.id 
                     AND t.status = 'completed' 
                     AND t.deleted_at IS NULL),
                    0
                ) + COALESCE(a.initial_balance, 0) as balance
            FROM accounts a
            WHERE a.user_id = ?
            AND a.deleted_at IS NULL
            ORDER BY a.name
        ", [$userId]);

        $result = [];
        foreach ($accounts as $account) {
            $balanceConverted = $this->convertToEUR($account->balance, $account->currency);
            $result[] = [
                'id' => $account->id,
                'name' => $account->name,
                'currency' => $account->currency,
                'balance' => round($account->balance, 2),
                'balance_converted' => round($balanceConverted, 2),
            ];
        }

        return $result;
    }

    private function getOverdueItems(int $userId, string $today): array
    {
        $items = [];

        // 1. Transactions pending/scheduled with planned_date < today
        $transactions = DB::select("
            SELECT 
                t.id,
                t.description,
                COALESCE(t.planned_amount, t.amount) as amount,
                t.planned_date as due_date,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                a.id as account_id,
                DATEDIFF(?, t.planned_date) as days_overdue,
                'transaction' as source_type
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.status IN ('pending', 'scheduled')
            AND t.type = 'debit'
            AND t.planned_date < ?
            AND t.deleted_at IS NULL
            AND t.is_transfer = 0
            ORDER BY t.planned_date ASC
        ", [$today, $userId, $today]);

        foreach ($transactions as $t) {
            $items[] = $this->formatItem($t, 'overdue');
        }

        // 2. Liability installments with due_date < today
        $installments = DB::select("
            SELECT 
                li.id,
                CONCAT(la.name, ' (Parcela)') as description,
                li.installment_amount as amount,
                li.due_date,
                la.currency,
                a.name as account_name,
                la.account_id,
                DATEDIFF(?, li.due_date) as days_overdue,
                'liability_installment' as source_type
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            LEFT JOIN accounts a ON la.account_id = a.id
            WHERE la.user_id = ?
            AND li.status = 'pending'
            AND li.due_date < ?
            AND li.deleted_at IS NULL
            ORDER BY li.due_date ASC
        ", [$today, $userId, $today]);

        foreach ($installments as $i) {
            $items[] = $this->formatItem($i, 'overdue');
        }

        // 3. Recurring instances pending with due_date < today
        $recurring = DB::select("
            SELECT 
                ri.id,
                CONCAT(rt.name, ' (Recorrente)') as description,
                ri.planned_amount as amount,
                ri.due_date,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                rt.account_id,
                DATEDIFF(?, ri.due_date) as days_overdue,
                'recurring_instance' as source_type
            FROM recurring_instances ri
            JOIN recurring_templates rt ON ri.recurring_template_id = rt.id
            LEFT JOIN accounts a ON rt.account_id = a.id
            WHERE ri.user_id = ?
            AND ri.status = 'pending'
            AND rt.type = 'debit'
            AND ri.due_date < ?
            AND ri.deleted_at IS NULL
            ORDER BY ri.due_date ASC
        ", [$today, $userId, $today]);

        foreach ($recurring as $r) {
            $items[] = $this->formatItem($r, 'overdue');
        }

        // Sort by days overdue (most overdue first)
        usort($items, fn($a, $b) => $b['days_overdue'] <=> $a['days_overdue']);

        return $items;
    }

    private function getTomorrowItems(int $userId, string $tomorrow): array
    {
        $items = [];

        // 1. Transactions pending/scheduled for tomorrow
        $transactions = DB::select("
            SELECT 
                t.id,
                t.description,
                COALESCE(t.planned_amount, t.amount) as amount,
                t.planned_date as due_date,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                a.id as account_id,
                0 as days_overdue,
                'transaction' as source_type
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.status IN ('pending', 'scheduled')
            AND t.type = 'debit'
            AND t.planned_date = ?
            AND t.deleted_at IS NULL
            AND t.is_transfer = 0
        ", [$userId, $tomorrow]);

        foreach ($transactions as $t) {
            $items[] = $this->formatItem($t, 'tomorrow');
        }

        // 2. Liability installments for tomorrow
        $installments = DB::select("
            SELECT 
                li.id,
                CONCAT(la.name, ' (Parcela)') as description,
                li.installment_amount as amount,
                li.due_date,
                la.currency,
                a.name as account_name,
                la.account_id,
                0 as days_overdue,
                'liability_installment' as source_type
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            LEFT JOIN accounts a ON la.account_id = a.id
            WHERE la.user_id = ?
            AND li.status = 'pending'
            AND li.due_date = ?
            AND li.deleted_at IS NULL
        ", [$userId, $tomorrow]);

        foreach ($installments as $i) {
            $items[] = $this->formatItem($i, 'tomorrow');
        }

        // 3. Recurring instances for tomorrow
        $recurring = DB::select("
            SELECT 
                ri.id,
                CONCAT(rt.name, ' (Recorrente)') as description,
                ri.planned_amount as amount,
                ri.due_date,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                rt.account_id,
                0 as days_overdue,
                'recurring_instance' as source_type
            FROM recurring_instances ri
            JOIN recurring_templates rt ON ri.recurring_template_id = rt.id
            LEFT JOIN accounts a ON rt.account_id = a.id
            WHERE ri.user_id = ?
            AND ri.status = 'pending'
            AND rt.type = 'debit'
            AND ri.due_date = ?
            AND ri.deleted_at IS NULL
        ", [$userId, $tomorrow]);

        foreach ($recurring as $r) {
            $items[] = $this->formatItem($r, 'tomorrow');
        }

        return $items;
    }

    private function formatItem($row, string $status): array
    {
        $amount = abs($row->amount);
        $amountConverted = $this->convertToEUR($amount, $row->currency);

        return [
            'id' => $row->id,
            'description' => $row->description,
            'amount' => round($amount, 2),
            'amount_converted' => round($amountConverted, 2),
            'currency' => $row->currency,
            'due_date' => $row->due_date,
            'days_overdue' => (int) $row->days_overdue,
            'account_name' => $row->account_name ?? 'Sem conta',
            'account_id' => $row->account_id ?? null,
            'source_type' => $row->source_type,
            'status' => $status, // 'overdue' or 'tomorrow'
        ];
    }

    private function suggestTransfers(array $accountBalances, array $payableItems): array
    {
        $suggestions = [];
        
        // Group payable items by account
        $itemsByAccount = [];
        foreach ($payableItems as $item) {
            $accountId = $item['account_id'] ?? 0;
            if (!isset($itemsByAccount[$accountId])) {
                $itemsByAccount[$accountId] = 0;
            }
            $itemsByAccount[$accountId] += $item['amount_converted'];
        }

        // Check each account balance vs required
        $accountBalanceMap = [];
        foreach ($accountBalances as $account) {
            $accountBalanceMap[$account['id']] = $account;
        }

        foreach ($itemsByAccount as $accountId => $required) {
            if ($accountId === 0) continue;
            
            $account = $accountBalanceMap[$accountId] ?? null;
            if (!$account) continue;

            $balance = $account['balance_converted'];
            
            if ($balance < $required) {
                $deficit = $required - $balance;
                
                // Find accounts with surplus to transfer from
                foreach ($accountBalances as $sourceAccount) {
                    if ($sourceAccount['id'] === $accountId) continue;
                    if ($sourceAccount['balance_converted'] <= 0) continue;
                    
                    $availableToTransfer = $sourceAccount['balance_converted'];
                    $transferAmount = min($deficit, $availableToTransfer);
                    
                    if ($transferAmount > 10) { // Only suggest transfers > 10 EUR
                        $suggestions[] = [
                            'from_account' => $sourceAccount['name'],
                            'from_account_id' => $sourceAccount['id'],
                            'to_account' => $account['name'],
                            'to_account_id' => $accountId,
                            'amount' => round($transferAmount, 2),
                            'reason' => "Para cobrir pagamentos pendentes",
                        ];
                        $deficit -= $transferAmount;
                    }
                    
                    if ($deficit <= 0) break;
                }
            }
        }

        return $suggestions;
    }

    private function convertToEUR(float $amount, string $currency): float
    {
        $rate = $this->exchangeRates[$currency] ?? 1.0;
        return $amount * $rate;
    }

    private function getPrimaryCurrency(int $userId): string
    {
        $result = DB::selectOne("
            SELECT currency, COUNT(*) as cnt
            FROM accounts
            WHERE user_id = ? AND deleted_at IS NULL
            GROUP BY currency
            ORDER BY cnt DESC
            LIMIT 1
        ", [$userId]);

        return $result->currency ?? 'EUR';
    }
}
