<?php

namespace App\Console\Commands;

use App\Core\Hook\HookManager;
use App\Jobs\ProcessProductSuspendJob;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Setting;
use App\Services\InvoiceService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Process overdue invoices and apply suspensions based on client group settings
 *
 * This command demonstrates how client group features are used:
 * - auto_suspend_disabled: Groups with this setting will not have services suspended
 * - payment_terms_days: Custom payment terms extend the due date before suspension
 *
 * Usage:
 * php artisan invoices:process-overdue [--dry-run] [--send-reminders]
 *
 * Schedule in app/Console/Kernel.php (PRC-40):
 * $schedule->command('invoices:process-overdue --send-reminders')->dailyAt('07:30');
 *
 * --send-reminders triggers the named tier reminder fan-out
 * (InvoiceService::processInvoiceReminders), idempotent via invoice_reminder_logs.
 */
class ProcessOverdueInvoicesCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'invoices:process-overdue
                            {--dry-run : Run without making changes}
                            {--send-reminders : Send reminder emails}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Process overdue invoices and suspend services based on client group settings';

    /**
     * Execute the console command.
     */
    public function handle(): int
    {
        $dryRun = $this->option('dry-run');
        $sendReminders = $this->option('send-reminders');

        $this->info('Processing overdue invoices...');

        if ($dryRun) {
            $this->warn('DRY RUN MODE - No changes will be made');
        }

        try {
            // No DB::beginTransaction — job dispatches must be visible to queue workers immediately
            // Each iteration is independent, no need for atomic batch

            // Reminder fan-out: named tier flow (PRC-40) — first/second/third/final
            // Settings: invoice_reminder_{first,second,third,final}_days + enable_invoice_reminder_emails
            // Templates: invoice_overdue_{first,second,third,final}_reminder
            // Idempotent via invoice_reminder_logs (unique invoice_id+reminder_type)
            $reminderResults = ['sent' => 0, 'failed' => 0, 'skipped' => 0];
            if ($sendReminders && ! $dryRun) {
                $reminderResults = app(InvoiceService::class)->processInvoiceReminders();
            }

            // Get unpaid invoices
            $overdueInvoices = Invoice::where('status', 'unpaid')
                ->where('due_date', '<', now())
                ->with(['client.group', 'items.clientProduct.product', 'items.clientDomain'])
                ->get();

            $this->info("Found {$overdueInvoices->count()} overdue invoices");

            $stats = [
                'total' => $overdueInvoices->count(),
                'suspended' => 0,
                'skipped_group_protected' => 0,
                'skipped_payment_terms' => 0,
                'reminders_sent' => $reminderResults['sent'],
                'reminders_failed' => $reminderResults['failed'],
                'reminders_skipped' => $reminderResults['skipped'],
            ];

            foreach ($overdueInvoices as $invoice) {
                $client = $invoice->client;

                if (!$client) {
                    $this->warn("Invoice #{$invoice->id} has no client, skipping");
                    continue;
                }

                // Calculate effective due date considering group payment terms
                $effectiveDueDate = $this->getEffectiveDueDate($invoice, $client);
                $isReallyOverdue = now()->isAfter($effectiveDueDate);

                $this->line("Processing Invoice #{$invoice->id} for {$client->first_name} {$client->last_name}");

                // Check if still within extended payment terms
                if (!$isReallyOverdue) {
                    $daysRemaining = now()->diffInDays($effectiveDueDate, false);
                    $this->info("  → Payment terms grace period: {$daysRemaining} days remaining");
                    $stats['skipped_payment_terms']++;
                    continue;
                }

                // Check if client group has auto-suspend disabled or suspend override
                if ($client->group_id && $client->group) {
                    $group = $client->group;

                    // override_suspend = true means NEVER suspend (VIP protection)
                    if ($group->hasSuspendOverride()) {
                        $this->info("  → Suspend override active for group: {$group->group_name} (never suspend)");
                        $stats['skipped_group_protected']++;
                        continue;
                    }

                    // auto_suspend_disabled = true means skip automatic suspension for overdue invoices
                    if ($group->isAutoSuspendDisabled()) {
                        $this->info("  → Auto-suspend disabled for group: {$group->group_name}");
                        $stats['skipped_group_protected']++;
                        continue;
                    }
                }

                // Suspend services related to this invoice
                if (!$dryRun) {
                    $suspended = $this->suspendInvoiceServices($invoice);

                    if ($suspended > 0) {
                        $this->warn("  ✗ Suspended {$suspended} service(s)");
                        $stats['suspended']++;

                        // Log the suspension
                        Log::info('Services suspended due to overdue invoice', [
                            'invoice_id' => $invoice->id,
                            'client_id' => $client->id,
                            'services_suspended' => $suspended,
                            'overdue_days' => (int) abs(now()->diffInDays($invoice->due_date)),
                        ]);
                    }
                } else {
                    $this->warn("  ✗ Would suspend services (dry-run)");
                    $stats['suspended']++;
                }
            }

            // No transaction commit/rollback needed (removed transaction wrapper)

            // Display summary
            $this->newLine();
            $this->info('Summary:');
            $this->table(
                ['Metric', 'Count'],
                [
                    ['Total Overdue Invoices', $stats['total']],
                    ['Services Suspended', $stats['suspended']],
                    ['Skipped (Group Protection)', $stats['skipped_group_protected']],
                    ['Skipped (Payment Terms)', $stats['skipped_payment_terms']],
                    ['Reminders Sent', $stats['reminders_sent']],
                    ['Reminders Failed', $stats['reminders_failed']],
                    ['Reminders Skipped (already sent / no tier)', $stats['reminders_skipped']],
                ]
            );

            // DailyCronJobPreEmail — filter hook: a handler returning true suppresses
            // any daily report email. PROCP currently has no daily summary report email,
            // so this is a no-op hook (fires but nothing to suppress). Third-party hooks
            // may still listen to know the daily cron is about to wrap up.
            app(HookManager::class)->filter('DailyCronJobPreEmail', false);

            // DailyCronJob — signals end of primary daily automation cycle.
            app(HookManager::class)->action('DailyCronJob');

            return Command::SUCCESS;

        } catch (\Exception $e) {
            $this->error('Error processing overdue invoices: ' . $e->getMessage());
            Log::error('ProcessOverdueInvoices command failed', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return Command::FAILURE;
        }
    }

    /**
     * Get effective due date considering client group payment terms
     *
     * @param Invoice $invoice
     * @param Client $client
     * @return \Carbon\Carbon
     */
    protected function getEffectiveDueDate(Invoice $invoice, Client $client): \Carbon\Carbon
    {
        $baseDueDate = $invoice->due_date;

        // Apply global grace period from settings
        $graceDays = (int) \App\Models\Setting::get('order_days_grace', 0);
        if ($graceDays > 0) {
            $baseDueDate = $baseDueDate->copy()->addDays($graceDays);
        }

        // Check if client belongs to a group with custom payment terms
        if ($client->group_id && $client->group) {
            $paymentTerms = $client->group->getPaymentTerms();

            if ($paymentTerms !== null && $paymentTerms > 0) {
                // Extend due date by group payment terms
                return $baseDueDate->copy()->addDays($paymentTerms);
            }
        }

        return $baseDueDate;
    }

    /**
     * Suspend services related to an invoice
     *
     * @param Invoice $invoice
     * @return int Number of services suspended
     */
    protected function suspendInvoiceServices(Invoice $invoice): int
    {
        $suspendedCount = 0;
        // Carbon 3: signed diffInDays — past dates yield negative. Without abs(),
        // overdueDays is always negative → never crosses suspension threshold and
        // suspend is never triggered.
        $overdueDays = (int) abs(now()->diffInDays($invoice->due_date));

        foreach ($invoice->items as $item) {
            // Suspend hosting/service products
            if ($item->clientProduct && $item->clientProduct->status === 'active') {
                $clientProduct = $item->clientProduct;

                // Respect per-product override_auto_suspend flag
                if ($clientProduct->override_auto_suspend) {
                    // Check if override_suspend_until has passed
                    if ($clientProduct->override_suspend_until && now()->isAfter($clientProduct->override_suspend_until)) {
                        // Override period expired, proceed with suspension
                    } else {
                        $this->info("  → Skipping product #{$clientProduct->id}: override_auto_suspend is active");
                        continue;
                    }
                }

                // PRC-641: suspension threshold tamamen ürün-bazlı (global ayar
                // kaldırıldı). Ürün değeri yoksa o ürün için auto-suspend olmaz.
                $suspensionDays = (int) ($clientProduct->product->auto_suspension_days ?? 0);

                // If suspension days is 0, skip - suspension not configured for this product
                if ($suspensionDays === 0) {
                    $this->info("  → Skipping product #{$clientProduct->id}: no auto_suspension_days configured");
                    continue;
                }

                // Only suspend if overdue days >= suspension threshold
                if ($overdueDays < $suspensionDays) {
                    $this->info("  → Skipping product #{$clientProduct->id}: overdue {$overdueDays} days < threshold {$suspensionDays} days");
                    continue;
                }

                // Skip if failed_suspend_count > 3 (retry limit, when field exists)
                if (
                    array_key_exists('failed_suspend_count', $clientProduct->getAttributes())
                    && (int) $clientProduct->failed_suspend_count > 3
                ) {
                    $this->warn("  → Skipping product #{$clientProduct->id}: exceeded suspend retry limit ({$clientProduct->failed_suspend_count} failures)");
                    continue;
                }

                $reason = 'Overdue invoice #' . $invoice->id;

                // Products WITH a module: dispatch the suspend job (calls the server module)
                if (!empty($clientProduct->module)) {
                    $clientProduct->update(['suspend_reason' => 'overdue']);
                    ProcessProductSuspendJob::dispatch($clientProduct->id, null, $reason);
                    $this->info("  → Dispatched suspend job for product #{$clientProduct->id} (module: {$clientProduct->module})");
                } else {
                    // Products WITHOUT a module: update status directly
                    $clientProduct->update([
                        'status' => 'suspended',
                        'suspend_reason' => 'overdue',
                        'suspend_date' => now(),
                    ]);
                    $this->info("  → Suspended product #{$clientProduct->id} directly (no module)");
                }

                // Log the auto-suspension event
                \App\Models\Log::create([
                    'type' => \App\Models\Log::TYPE_SYSTEM,
                    'observer' => 'ProcessOverdueInvoicesCommand',
                    'module' => 'ClientProduct',
                    'method' => 'AutoSuspension',
                    'description' => "Auto-suspension triggered for client product #{$clientProduct->id} - {$reason}",
                    'client_id' => $invoice->client_id,
                    'ip_address' => '0.0.0.0',
                ]);

                $suspendedCount++;
            }

            // Suspend domains (rare, but possible) - keep existing behavior
            if ($item->clientDomain && $item->clientDomain->status === 'active') {
                $item->clientDomain->update(['status' => 'suspended']);
                $suspendedCount++;
            }
        }

        return $suspendedCount;
    }

}
