<?php

namespace App\Console\Commands;

use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Enums\EnrollmentStatus;
use App\Domain\Training\Enums\RenewalPolicy;
use App\Domain\Training\Models\Enrollment;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;

class GenerateRenewalInvoices extends Command
{
    protected $signature = 'enrollments:generate-renewals {--dry-run : Show what would be billed without creating invoices}';
    protected $description = 'Generate renewal invoices for active enrollments with billing due today or earlier';

    public function handle(InvoiceService $invoiceService, PricingService $pricingService): int
    {
        $dryRun = $this->option('dry-run');

        $enrollments = Enrollment::where('status', EnrollmentStatus::Active)
            ->whereNotNull('next_billing_date')
            ->where('next_billing_date', '<=', now()->toDateString())
            ->whereHas('program', function ($q) {
                $q->whereIn('renewal_policy', [
                    RenewalPolicy::AutoRenew->value,
                    RenewalPolicy::ManualRenew->value,
                ])
                ->whereNotNull('billing_cycle');
            })
            ->with(['program', 'participant.person', 'group'])
            ->get();

        if ($enrollments->isEmpty()) {
            $this->info('No renewal invoices due today.');
            return self::SUCCESS;
        }

        $this->info("Found {$enrollments->count()} enrollment(s) due for renewal.");

        $created = 0;
        $failed = 0;

        foreach ($enrollments as $enrollment) {
            $participant = $enrollment->participant;
            $program = $enrollment->program;
            $group = $enrollment->group;

            if (!$participant || !$program) {
                $failed++;
                continue;
            }

            if ($dryRun) {
                $this->line("  [DRY] {$participant->person?->name_ar} — {$program->name_ar} (due: {$enrollment->next_billing_date->format('Y-m-d')})");
                $created++;
                continue;
            }

            try {
                $priceResult = $pricingService->calculate(
                    priceable: $program,
                    participant: $participant,
                    branchId: $group?->branch_id ?? $program->branch_id,
                );

                if ($priceResult->finalAmount <= 0) {
                    $this->advanceNextBillingDate($enrollment);
                    continue;
                }

                $systemUser = User::where('email', 'system@oc-sport.com')->first()
                    ?? User::first();

                $invoice = $invoiceService->create([
                    'academy_id' => $enrollment->academy_id ?? $program->academy_id,
                    'branch_id' => $group?->branch_id ?? $program->branch_id,
                    'billable_type' => $participant->getMorphClass(),
                    'billable_id' => $participant->id,
                    'number' => $invoiceService->generateNumber($program->academy_id),
                    'total_amount' => $priceResult->finalAmount,
                    'subtotal_amount' => $priceResult->baseAmount,
                    'discount_amount' => $priceResult->totalDiscount,
                    'tax_amount' => 0,
                    'due_date' => now()->addDays(7)->toDateString(),
                    'contact_name' => $participant->person?->name_ar ?? $participant->person?->name,
                    'notes' => 'تجديد تلقائي — ' . $program->name_ar,
                ], [
                    [
                        'description' => "تجديد اشتراك: {$program->name_ar}",
                        'quantity' => 1,
                        'unit_price' => $priceResult->baseAmount,
                        'discount_amount' => $priceResult->totalDiscount,
                        'tax_amount' => 0,
                    ],
                ], $systemUser);

                // Mark as sent immediately so it appears in payment wizard
                $invoice->update(['status' => \App\Domain\Financial\Enums\InvoiceStatus::Sent]);

                $this->advanceNextBillingDate($enrollment);

                $enrollment->update([
                    'last_billed_at' => now()->toDateString(),
                    'payment_status' => 'pending',
                ]);

                $created++;
            } catch (DomainException $e) {
                Log::warning('Renewal invoice skipped', [
                    'enrollment_id' => $enrollment->id,
                    'reason' => $e->getMessage(),
                ]);
                $failed++;
            } catch (\Throwable $e) {
                Log::error('Renewal invoice failed', [
                    'enrollment_id' => $enrollment->id,
                    'error' => $e->getMessage(),
                ]);
                $failed++;
            }
        }

        $label = $dryRun ? 'Would create' : 'Created';
        $this->info("{$label} {$created} renewal invoice(s). Failed: {$failed}.");

        return self::SUCCESS;
    }

    private function advanceNextBillingDate(Enrollment $enrollment): void
    {
        $program = $enrollment->program;
        $current = $enrollment->next_billing_date;

        $next = match ($program->billing_cycle) {
            'monthly' => $this->advanceMonthly($current, $program->billing_day),
            'quarterly' => $current->copy()->addMonths(3),
            'semi_annual' => $current->copy()->addMonths(6),
            'annual' => $current->copy()->addYear(),
            'per_duration' => $current->copy()->addWeeks($program->program_duration_weeks ?? 4),
            default => $current->copy()->addMonth(),
        };

        $enrollment->update(['next_billing_date' => $next->toDateString()]);
    }

    private function advanceMonthly(Carbon $current, ?int $billingDay): Carbon
    {
        $next = $current->copy()->addMonth();

        if ($billingDay) {
            $maxDay = $next->daysInMonth;
            $next->day = min($billingDay, $maxDay);
        }

        return $next;
    }
}
