<?php

namespace App\Console\Commands;

use App\Jobs\SendEmailJob;
use App\Models\ClientDomain;
use App\Models\DomainRenewalAlert;
use App\Models\Setting;
use Illuminate\Console\Command;

class SendDomainRenewalAlertsCommand extends Command
{
    protected $signature = 'domain:renewal-alerts
                            {--dry-run : Show what would be sent without actually sending}';

    protected $description = 'Send renewal reminder emails for expiring domains';

    private const REMINDER_TIERS = [
        'first' => ['setting' => 'domain_reminder_first_days', 'default' => 30, 'template' => 'Domain Renewal First Reminder', 'number' => 1],
        'second' => ['setting' => 'domain_reminder_second_days', 'default' => 14, 'template' => 'Domain Renewal Second Reminder', 'number' => 2],
        'third' => ['setting' => 'domain_reminder_third_days', 'default' => 7, 'template' => 'Domain Renewal Third Reminder', 'number' => 3],
        'final' => ['setting' => 'domain_reminder_final_days', 'default' => 1, 'template' => 'Domain Renewal Final Reminder', 'number' => 4],
    ];

    public function handle(): int
    {
        // PRC-316 review — master gate: don't email a renew CTA when
        // renewal flow is disabled (the link would 403 anyway).
        if (! \App\Services\Domain\DomainPolicyGuard::renewalEnabled()) {
            $this->info('Domain renewals master gate is off (enable_renewal_orders=false); skipping alerts.');

            return Command::SUCCESS;
        }

        $dryRun = $this->option('dry-run');
        $totalReminders = count(self::REMINDER_TIERS);

        $this->info('Starting domain renewal alerts process...');
        if ($dryRun) {
            $this->info('DRY RUN MODE - No emails will be sent');
        }

        $sentCount = 0;
        $skippedCount = 0;

        // PRC-653 follow-up — run-içi cross-tier guard: band'ler doğru config'de
        // ayrık olsa da, admin iki tier'a aynı/çakışan gün değeri verirse bir
        // domain birden çok banda düşüp aynı çalışmada birden çok reminder
        // alabilir. Bir domain bu çalışmada herhangi bir tier reminder'ı aldıysa
        // sonraki tier'larda atlanır (ilk eşleşen tier kazanır; iterasyon
        // first→final sırasında). Doğru config'de no-op.
        $alertedDomainIdsThisRun = [];

        // PRC-653 — tier-band için tüm etkin tier eşiklerini önceden topla.
        // Bir tier'ın alt sınırı, kendisinden küçük en yakın etkin tier'dır.
        $enabledTierDays = [];
        foreach (self::REMINDER_TIERS as $tn => $t) {
            $d = (int) Setting::getValue($t['setting'], $t['default']);
            if ($d > 0) {
                $enabledTierDays[$tn] = $d;
            }
        }

        foreach (self::REMINDER_TIERS as $tierName => $tier) {
            $days = (int) Setting::getValue($tier['setting'], $tier['default']);

            if ($days <= 0) {
                $this->line("Skipping {$tierName} reminder (disabled, days = 0)");
                continue;
            }

            // PRC-653 — bu tier'ın alt sınırı: kendisinden küçük en yakın etkin
            // tier eşiği (yoksa 0 = en sıkı tier). Böylece tier'lar çakışmaz ve
            // her domain tam olarak bir banda düşer:
            //   final(1): [bugün, +1] / third(7): (+1, +7] / second(14): (+7, +14] / first(30): (+14, +30]
            $lowerDays = 0;
            foreach ($enabledTierDays as $d) {
                if ($d < $days && $d > $lowerDays) {
                    $lowerDays = $d;
                }
            }

            $upperDate = now()->addDays($days)->toDateString();

            // PRC-324 / B-16 — Idempotency `for_expire_date` ile mevcut yenileme
            // döngüsüne kapsanır (önceki döngü alert'leri yeni döngüyü bastırmaz).
            // PRC-653 — exact-day yerine tier-band: cron bir/iki gün (band genişliği
            // kadar) kaçırsa bile domain aynı bandda kaldığından sıradaki çalışmada
            // (deduplike) gönderilir. (Tüm bandı aşan uzun kesintide o tier atlanır;
            // domain bir sonraki sıkı banddan yine de uyarı alır.) Dedup domain'in
            // KENDİ expire_date'ine korele (whereColumn) — band içindeki her domain
            // farklı expire_date taşır.
            $query = ClientDomain::where('status', 'active')
                ->whereDate('expire_date', '<=', $upperDate)
                ->where(function ($q) {
                    $q->where('disable_reminders', false)
                        ->orWhereNull('disable_reminders');
                })
                ->whereDoesntHave('renewalAlerts', function ($q) use ($tierName) {
                    $q->where('reminder_type', $tierName)
                        ->whereColumn('for_expire_date', 'client_domains.expire_date');
                })
                ->with('client');

            if ($lowerDays > 0) {
                $query->whereDate('expire_date', '>', now()->addDays($lowerDays)->toDateString());
            } else {
                // En sıkı tier: bugün ve sonrası (süresi geçmişleri uyarma).
                $query->whereDate('expire_date', '>=', now()->toDateString());
            }

            $domains = $query->get();

            if ($domains->isEmpty()) {
                $this->line("No domains in {$tierName} band ((+{$lowerDays}, +{$days}] days).");
                continue;
            }

            $this->info("Found {$domains->count()} domains for {$tierName} reminder band ((+{$lowerDays}, +{$days}] days).");

            foreach ($domains as $domain) {
                if (!$domain->client) {
                    $this->warn("Skipped: {$domain->domain} - no associated client");
                    $skippedCount++;
                    continue;
                }

                // PRC-653 follow-up — bu domain bu çalışmada zaten bir tier
                // reminder'ı aldıysa atla (çakışan tier config'inde çift gönderim
                // önlemi). Doğru/ayrık config'de hiçbir domain ikinci kez gelmez.
                if (isset($alertedDomainIdsThisRun[$domain->id])) {
                    continue;
                }

                // PRC-653 — band içinde gerçek kalan gün domain'e göre değişir;
                // tier nominal eşiği ($days) yerine bunu kullan (negatif olmasın).
                $daysRemaining = max(0, now()->startOfDay()->diffInDays(
                    \Carbon\Carbon::parse($domain->expire_date)->startOfDay(),
                    false
                ));

                if ($dryRun) {
                    $this->line("Would send {$tierName} reminder for {$domain->domain} to {$domain->client->email} ({$daysRemaining} days remaining)");
                    $alertedDomainIdsThisRun[$domain->id] = true;
                    $sentCount++;
                    continue;
                }

                try {
                    SendEmailJob::dispatch(
                        $tier['template'],
                        'client_domain',
                        $domain->id,
                        [
                            'DOMAIN' => $domain->domain,
                            'EXPIRY_DATE' => $domain->expire_date->format('d/m/Y'),
                            'DAYS_REMAINING' => $daysRemaining,
                            'CLIENT_NAME' => $domain->client->first_name . ' ' . $domain->client->last_name,
                            'RENEW_URL' => route('store.auth.domains.renew', $domain->id),
                            'REMINDER_NUMBER' => $tier['number'],
                            'TOTAL_REMINDERS' => $totalReminders,
                            'REMINDER_LABEL' => $tierName,
                            'IS_FINAL' => $tierName === 'final' ? 'yes' : 'no',
                        ]
                    );

                    // PRC-324 / B-16 — `for_expire_date` records the
                    // renewal cycle this alert belongs to; the unique
                    // index (client_domain_id, reminder_type, for_expire_date)
                    // gives per-cycle idempotency.
                    DomainRenewalAlert::create([
                        'client_domain_id' => $domain->id,
                        'days_before_expiry' => $daysRemaining,
                        'reminder_type' => $tierName,
                        'for_expire_date' => $domain->expire_date->toDateString(),
                        'sent_at' => now(),
                    ]);

                    app(\App\Core\Hook\HookManager::class)->action('DomainRenewalAlertSent', [
                        'domain' => $domain,
                        'domain_id' => $domain->id,
                        'client_id' => $domain->client_id,
                        'tier' => $tierName ?? null,
                        'days_remaining' => $daysRemaining,
                    ]);

                    $alertedDomainIdsThisRun[$domain->id] = true;
                    $this->info("Sent {$tierName} reminder: {$domain->domain} ({$daysRemaining} days before expiry)");
                    $sentCount++;
                } catch (\Exception $e) {
                    $this->error("Failed: {$domain->domain} - {$e->getMessage()}");
                    $skippedCount++;
                }
            }
        }

        $this->newLine();
        $this->info('Domain renewal alerts completed:');
        $this->info("- Alerts sent/queued: {$sentCount}");
        $this->info("- Skipped/failed: {$skippedCount}");

        return Command::SUCCESS;
    }
}
