<?php

namespace App\Console\Commands;

use App\Models\ActivityLog;
use App\Models\Customer;
use App\Models\InvestmentAccount;
use App\Models\LoanRepaymentSchedule;
use App\Models\SavingsAccount;
use App\Models\Tenant;
use App\Services\MemberNotificationAutomationService;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;

class SendMemberReminders extends Command
{
    protected $signature = 'notifications:member-reminders
        {--dry-run : Show reminders without sending them}
        {--due-days=3 : How many days ahead to check loan and savings due reminders}
        {--maturity-days=7 : How many days ahead to check savings and investment maturity reminders}';

    protected $description = 'Send automatic member reminders for due loans, savings contributions, investment maturity, and KYC.';

    public function handle(MemberNotificationAutomationService $notifications): int
    {
        $dueDays = max(0, (int) $this->option('due-days'));
        $maturityDays = max(0, (int) $this->option('maturity-days'));
        $dryRun = (bool) $this->option('dry-run');
        $sent = 0;

        Tenant::query()
            ->where('status', 'active')
            ->orderBy('name')
            ->chunkById(50, function ($tenants) use ($notifications, $dueDays, $maturityDays, $dryRun, &$sent): void {
                foreach ($tenants as $tenant) {
                    $sent += $this->loanDueReminders($tenant, $notifications, $dueDays, $dryRun);
                    $sent += $this->loanOverdueReminders($tenant, $notifications, $dryRun);
                    $sent += $this->savingsContributionReminders($tenant, $notifications, $dueDays, $dryRun);
                    $sent += $this->savingsMaturityReminders($tenant, $notifications, $maturityDays, $dryRun);
                    $sent += $this->investmentMaturityReminders($tenant, $notifications, $maturityDays, $dryRun);
                    $sent += $this->kycReminders($tenant, $notifications, $dryRun);
                }
            });

        $this->info($dryRun
            ? 'Dry run complete. '.$sent.' reminder(s) would be sent.'
            : 'Member reminders complete. '.$sent.' reminder(s) sent.'
        );

        return self::SUCCESS;
    }

    private function loanDueReminders(Tenant $tenant, MemberNotificationAutomationService $notifications, int $dueDays, bool $dryRun): int
    {
        if (! $notifications->isEnabled($tenant, 'loan_repayment_due_reminder')) {
            return 0;
        }

        $count = 0;
        $schedules = LoanRepaymentSchedule::withoutGlobalScopes()
            ->with(['loan.customer'])
            ->where('tenant_id', $tenant->id)
            ->where('is_current', true)
            ->whereIn('status', ['pending', 'partial'])
            ->whereBetween('due_date', [today(), today()->addDays($dueDays)])
            ->orderBy('due_date')
            ->get();

        foreach ($schedules as $schedule) {
            $customer = $schedule->loan?->customer;
            $dueAmount = max(0, (float) $schedule->total_due - (float) $schedule->paid_amount);

            if (! $customer || $dueAmount <= 0) {
                continue;
            }

            $reference = 'loan-due-'.$schedule->id.'-'.$schedule->due_date?->toDateString();
            $count += $this->sendReminder($tenant, $customer, 'loan_repayment_due_reminder', $reference, 1, [
                'loan_number' => $schedule->loan?->loan_number,
                'due_amount' => $dueAmount,
                'amount' => $dueAmount,
                'due_date' => $schedule->due_date,
                'days_left' => max(0, (int) today()->diffInDays($schedule->due_date, false)),
            ], $notifications, $dryRun);
        }

        return $count;
    }

    private function loanOverdueReminders(Tenant $tenant, MemberNotificationAutomationService $notifications, bool $dryRun): int
    {
        if (! $notifications->isEnabled($tenant, 'loan_repayment_overdue_reminder')) {
            return 0;
        }

        $count = 0;
        $schedules = LoanRepaymentSchedule::withoutGlobalScopes()
            ->with(['loan.customer'])
            ->where('tenant_id', $tenant->id)
            ->where('is_current', true)
            ->whereIn('status', ['pending', 'partial', 'overdue'])
            ->whereDate('due_date', '<', today())
            ->orderBy('due_date')
            ->get();

        foreach ($schedules as $schedule) {
            $customer = $schedule->loan?->customer;
            $dueAmount = max(0, (float) $schedule->total_due - (float) $schedule->paid_amount);

            if (! $customer || $dueAmount <= 0) {
                continue;
            }

            $reference = 'loan-overdue-'.$schedule->id.'-'.$schedule->due_date?->toDateString();
            $count += $this->sendReminder($tenant, $customer, 'loan_repayment_overdue_reminder', $reference, 7, [
                'loan_number' => $schedule->loan?->loan_number,
                'due_amount' => $dueAmount,
                'amount' => $dueAmount,
                'due_date' => $schedule->due_date,
                'days_overdue' => max(1, (int) $schedule->due_date?->diffInDays(today())),
            ], $notifications, $dryRun);
        }

        return $count;
    }

    private function savingsContributionReminders(Tenant $tenant, MemberNotificationAutomationService $notifications, int $dueDays, bool $dryRun): int
    {
        if (! $notifications->isEnabled($tenant, 'savings_contribution_due_reminder')) {
            return 0;
        }

        $count = 0;
        $accounts = SavingsAccount::withoutGlobalScopes()
            ->with('customer')
            ->where('tenant_id', $tenant->id)
            ->where('status', 'active')
            ->whereNotNull('start_date')
            ->whereNotNull('contribution_frequency')
            ->whereNotNull('contribution_periods')
            ->where('contribution_amount', '>', 0)
            ->get();

        foreach ($accounts as $account) {
            $due = $this->nextDueSavingsContribution($account, $dueDays);

            if (! $due || ! $account->customer) {
                continue;
            }

            $reference = 'savings-contribution-'.$account->id.'-'.$due['period'].'-'.$due['due_date']->toDateString();
            $count += $this->sendReminder($tenant, $account->customer, 'savings_contribution_due_reminder', $reference, 1, [
                'account_number' => $account->account_number,
                'product_name' => $account->product_name,
                'due_amount' => $due['amount'],
                'amount' => $due['amount'],
                'due_date' => $due['due_date'],
                'days_left' => max(0, (int) today()->diffInDays($due['due_date'], false)),
            ], $notifications, $dryRun);
        }

        return $count;
    }

    private function savingsMaturityReminders(Tenant $tenant, MemberNotificationAutomationService $notifications, int $maturityDays, bool $dryRun): int
    {
        if (! $notifications->isEnabled($tenant, 'savings_maturity_reminder')) {
            return 0;
        }

        $count = 0;
        $accounts = SavingsAccount::withoutGlobalScopes()
            ->with('customer')
            ->where('tenant_id', $tenant->id)
            ->where('status', 'active')
            ->whereBetween('maturity_date', [today(), today()->addDays($maturityDays)])
            ->orderBy('maturity_date')
            ->get();

        foreach ($accounts as $account) {
            if (! $account->customer) {
                continue;
            }

            $reference = 'savings-maturity-'.$account->id.'-'.$account->maturity_date?->toDateString();
            $count += $this->sendReminder($tenant, $account->customer, 'savings_maturity_reminder', $reference, 1, [
                'account_number' => $account->account_number,
                'product_name' => $account->product_name,
                'maturity_date' => $account->maturity_date,
                'days_left' => max(0, (int) today()->diffInDays($account->maturity_date, false)),
                'amount' => (float) ($account->planned_total_amount ?: $account->target_amount ?: $account->balance),
            ], $notifications, $dryRun);
        }

        return $count;
    }

    private function investmentMaturityReminders(Tenant $tenant, MemberNotificationAutomationService $notifications, int $maturityDays, bool $dryRun): int
    {
        if (! $notifications->isEnabled($tenant, 'investment_maturity_reminder')) {
            return 0;
        }

        $count = 0;
        $accounts = InvestmentAccount::withoutGlobalScopes()
            ->with('customer')
            ->where('tenant_id', $tenant->id)
            ->where('status', 'active')
            ->whereBetween('maturity_date', [today(), today()->addDays($maturityDays)])
            ->orderBy('maturity_date')
            ->get();

        foreach ($accounts as $account) {
            if (! $account->customer) {
                continue;
            }

            $reference = 'investment-maturity-'.$account->id.'-'.$account->maturity_date?->toDateString();
            $count += $this->sendReminder($tenant, $account->customer, 'investment_maturity_reminder', $reference, 1, [
                'account_number' => $account->account_number,
                'product_name' => $account->product_name,
                'maturity_date' => $account->maturity_date,
                'days_left' => max(0, (int) today()->diffInDays($account->maturity_date, false)),
                'amount' => (float) $account->amount + (float) $account->expected_return,
            ], $notifications, $dryRun);
        }

        return $count;
    }

    private function kycReminders(Tenant $tenant, MemberNotificationAutomationService $notifications, bool $dryRun): int
    {
        if (! $notifications->isEnabled($tenant, 'kyc_pending_reminder')) {
            return 0;
        }

        $count = 0;
        $customers = Customer::withoutGlobalScopes()
            ->where('tenant_id', $tenant->id)
            ->where('status', 'active')
            ->whereIn('kyc_status', ['pending', 'rejected'])
            ->orderBy('created_at')
            ->get();

        foreach ($customers as $customer) {
            $reference = 'kyc-'.$customer->id.'-'.$customer->kyc_status;
            $count += $this->sendReminder($tenant, $customer, 'kyc_pending_reminder', $reference, 7, [
                'kyc_status' => $customer->kyc_status,
            ], $notifications, $dryRun);
        }

        return $count;
    }

    private function sendReminder(Tenant $tenant, Customer $customer, string $event, string $reference, int $repeatAfterDays, array $context, MemberNotificationAutomationService $notifications, bool $dryRun): int
    {
        if ($this->wasSentRecently($tenant, $customer, $event, $reference, $repeatAfterDays)) {
            return 0;
        }

        $this->line(($dryRun ? 'Would send ' : 'Sending ').$event.' to '.$customer->fullName().' ('.$tenant->name.')');

        if (! $dryRun) {
            $notifications->send($customer, $event, $context + ['reference' => $reference]);

            ActivityLog::withoutGlobalScopes()->create([
                'tenant_id' => $tenant->id,
                'user_id' => null,
                'action' => 'member_reminder_sent',
                'description' => 'Automatic member reminder sent: '.$event,
                'subject_type' => Customer::class,
                'subject_id' => $customer->id,
                'properties' => [
                    'event' => $event,
                    'reference' => $reference,
                ],
            ]);
        }

        return 1;
    }

    private function wasSentRecently(Tenant $tenant, Customer $customer, string $event, string $reference, int $days): bool
    {
        return ActivityLog::withoutGlobalScopes()
            ->where('tenant_id', $tenant->id)
            ->where('subject_type', Customer::class)
            ->where('subject_id', $customer->id)
            ->where('action', 'member_reminder_sent')
            ->where('created_at', '>=', now()->subDays($days))
            ->where('properties->event', $event)
            ->where('properties->reference', $reference)
            ->exists();
    }

    private function nextDueSavingsContribution(SavingsAccount $account, int $dueDays): ?array
    {
        $startDate = $account->start_date instanceof Carbon ? $account->start_date : Carbon::parse($account->start_date);
        $contribution = (float) $account->contribution_amount;
        $periods = (int) $account->contribution_periods;

        for ($period = 1; $period <= $periods; $period++) {
            $dueDate = $this->addSavingsPeriods($startDate->copy(), (string) $account->contribution_frequency, $period);

            if ($dueDate->lt(today()) || $dueDate->gt(today()->addDays($dueDays))) {
                continue;
            }

            $expectedByNow = $contribution * $period;

            if ((float) $account->balance < $expectedByNow) {
                return [
                    'period' => $period,
                    'due_date' => $dueDate,
                    'amount' => $contribution,
                ];
            }
        }

        return null;
    }

    private function addSavingsPeriods(Carbon $date, string $frequency, int $periods): Carbon
    {
        match ($frequency) {
            'daily' => $date->addDays($periods),
            'weekly' => $date->addWeeks($periods),
            'biweekly' => $date->addWeeks($periods * 2),
            'quarterly' => $date->addMonthsNoOverflow($periods * 3),
            'yearly' => $date->addYears($periods),
            default => $date->addMonthsNoOverflow($periods),
        };

        return $date;
    }
}
