<?php

namespace App\Console\Commands;

use App\Jobs\SendVerificationEmailJob;
use App\Mail\RegistrationVerificationMail;
use App\Models\EmailSendEvent;
use App\Models\User;
use App\Services\Auth\RegistrationEmailQuotaService;
use App\Services\Auth\RegistrationVerificationTokenService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\RateLimiter;

class SendUserVerificationEmailCommand extends Command
{
    protected $signature = 'user:send-verification-email
        {userId : The user ID that should receive the verification email}
        {--now : Send immediately instead of queueing the existing verification job}
        {--force : Allow sending even if the user is already verified}';

    protected $description = 'Send the registration verification email to a specific user ID.';

    public function __construct(
        private readonly RegistrationVerificationTokenService $tokenService,
        private readonly RegistrationEmailQuotaService $quotaService,
    ) {
        parent::__construct();
    }

    public function handle(): int
    {
        $userId = (int) $this->argument('userId');

        if ($userId < 1) {
            $this->error('The user ID must be a positive integer.');

            return self::FAILURE;
        }

        $user = User::query()->find($userId);

        if (! $user) {
            $this->error("User {$userId} was not found.");

            return self::FAILURE;
        }

        $email = strtolower(trim((string) $user->email));

        if ($email === '') {
            $this->error("User {$userId} does not have an email address.");

            return self::FAILURE;
        }

        if ($user->email_verified_at !== null && ! $this->option('force')) {
            $this->error("User {$userId} already has a verified email address. Use --force to send anyway.");

            return self::FAILURE;
        }

        $token = $this->tokenService->createForUser($userId);

        $event = EmailSendEvent::query()->create([
            'type' => 'verify_email',
            'email' => $email,
            'ip' => null,
            'user_id' => $userId,
            'status' => $this->option('now') ? 'pending' : 'queued',
            'reason' => null,
            'created_at' => now(),
        ]);

        if ($this->option('now')) {
            return $this->sendNow($user, $event, $token);
        }

        SendVerificationEmailJob::dispatch(
            emailEventId: (int) $event->id,
            email: $email,
            token: $token,
            userId: $userId,
            ip: null,
        );

        $this->markVerificationEmailSent($user);

        $this->info("Queued verification email for user {$userId} <{$email}>.");

        return self::SUCCESS;
    }

    private function sendNow(User $user, EmailSendEvent $event, string $token): int
    {
        if (! $this->acquireGlobalSendSlot()) {
            $this->updateEvent($event, 'blocked', 'rate_limited');
            $this->error('The global verification email rate limit is currently exhausted. Try again in a minute.');

            return self::FAILURE;
        }

        if ($this->quotaService->isExceeded()) {
            $this->updateEvent($event, 'blocked', 'quota');
            $this->error('The monthly registration email quota is exceeded.');

            return self::FAILURE;
        }

        try {
            Mail::to($user->email)->send(new RegistrationVerificationMail($token));
        } catch (\Throwable $exception) {
            $this->updateEvent($event, 'failed', 'send_error');
            $this->error('Failed to send the verification email: ' . $exception->getMessage());

            return self::FAILURE;
        }

        $this->quotaService->incrementSentCount();
        $this->updateEvent($event, 'sent', null);
        $this->markVerificationEmailSent($user);

        $email = strtolower(trim((string) $user->email));
        $this->info("Sent verification email to user {$user->id} <{$email}>.");

        return self::SUCCESS;
    }

    private function acquireGlobalSendSlot(): bool
    {
        $key = 'registration:verification-email:global';
        $maxPerMinute = max(1, (int) config('registration.email_global_send_per_minute', 30));

        return RateLimiter::attempt($key, $maxPerMinute, static fn () => true, 60);
    }

    private function updateEvent(EmailSendEvent $event, string $status, ?string $reason): void
    {
        EmailSendEvent::query()
            ->whereKey($event->getKey())
            ->update([
                'status' => $status,
                'reason' => $reason,
            ]);
    }

    private function markVerificationEmailSent(User $user): void
    {
        $now = now();

        $windowStartedAt = $user->verification_send_window_started_at;
        if (! $windowStartedAt || $windowStartedAt->lt($now->copy()->subDay())) {
            $user->verification_send_window_started_at = $now;
            $user->verification_send_count_24h = 1;
        } else {
            $user->verification_send_count_24h = ((int) $user->verification_send_count_24h) + 1;
        }

        $user->last_verification_sent_at = $now;
        $user->save();
    }
}