<?php

namespace App\Console\Commands;

use App\Models\JobAlertEmailLog;
use App\Models\User;
use App\Services\JobMatchingService;
use App\Services\Notifications\DailyJobAlertMailer;
use App\Services\Settings\SettingsService;
use Illuminate\Console\Command;
use Throwable;

class SendDailyJobAlerts extends Command
{
    protected $signature = 'jobs:send-daily-alerts {--user= : Send for one user ID or email} {--dry-run : Show counts without sending email}';

    protected $description = 'Send daily email alerts for newly matched remote jobs.';

    public function handle(
        SettingsService $settings,
        JobMatchingService $matchingService,
        DailyJobAlertMailer $mailer
    ): int {
        if (! $settings->isEnabled('notifications.new_matched_jobs_email_enabled')) {
            $this->warn('New matched jobs email alerts are disabled in settings.');

            return self::SUCCESS;
        }

        $query = User::query()
            ->where('role', 'user')
            ->whereNotNull('email')
            ->whereNotNull('questionnaire_completed_at')
            ->whereNotNull('access_unlocked_at')
            ->whereNotNull('resume_analyzed_at')
            ->whereNotNull('profile_completed_at')
            ->with(['profile', 'questionnaireResponse', 'resumeReport', 'jobAlertPreference']);

        if (filled($this->option('user'))) {
            $target = (string) $this->option('user');
            $query->where(fn ($query) => $query
                ->where('id', ctype_digit($target) ? (int) $target : 0)
                ->orWhere('email', $target));
        }

        $sent = 0;
        $skipped = 0;
        $failed = 0;

        $query->chunkById(100, function ($users) use ($matchingService, $mailer, &$sent, &$skipped, &$failed): void {
            foreach ($users as $user) {
                $preference = $user->jobAlertPreference()->firstOrCreate([], [
                    'enabled' => true,
                    'frequency' => 'daily',
                    'minimum_match_score' => 45,
                ]);

                if (! $preference->isSubscribed()) {
                    $skipped++;

                    continue;
                }

                if (! $this->isDue($preference)) {
                    $skipped++;

                    continue;
                }

                $since = $preference->last_sent_at ?: now()->subDays(7);
                $jobs = $matchingService
                    ->rankedJobsFor($user)
                    ->filter(fn ($job): bool => $this->isNewEnough($job, $since)
                        && (int) $job->match_score >= (int) $preference->minimum_match_score
                        && $this->matchesPreferenceFilters($job, $preference))
                    ->take(8)
                    ->values();

                if ($jobs->isEmpty()) {
                    if ($this->option('dry-run')) {
                        $this->line("No alertable jobs for {$user->email}.");
                        $skipped++;

                        continue;
                    }

                    $preference->forceFill(['last_sent_at' => now()])->save();
                    $skipped++;

                    continue;
                }

                if ($this->option('dry-run')) {
                    $this->line("Would send {$jobs->count()} job(s) to {$user->email}.");
                    $skipped++;

                    continue;
                }

                try {
                    $deliveryLog = $mailer->send($user, $preference, $jobs);

                    $preference->forceFill(['last_sent_at' => now()])->save();
                    $user->jobAlertEmailLogs()->create([
                        'email_delivery_log_id' => $deliveryLog->id,
                        'status' => JobAlertEmailLog::STATUS_SENT,
                        'provider_message_id' => $deliveryLog->provider_message_id,
                        'brevo_message_id' => $deliveryLog->brevo_message_id,
                        'job_count' => $jobs->count(),
                        'imported_job_ids' => $jobs->pluck('id')->values()->all(),
                        'sent_at' => now(),
                    ]);

                    $sent++;
                } catch (Throwable $exception) {
                    report($exception);

                    $user->jobAlertEmailLogs()->create([
                        'status' => JobAlertEmailLog::STATUS_FAILED,
                        'job_count' => $jobs->count(),
                        'imported_job_ids' => $jobs->pluck('id')->values()->all(),
                        'failure_reason' => $exception->getMessage(),
                    ]);

                    $failed++;
                }
            }
        });

        $this->info("Daily job alerts complete. Sent: {$sent}. Skipped: {$skipped}. Failed: {$failed}.");

        return $failed > 0 ? self::FAILURE : self::SUCCESS;
    }

    private function isNewEnough($job, $since): bool
    {
        return $job->created_at?->gt($since)
            || $job->posted_at?->gt($since)
            || $job->last_seen_at?->gt($since);
    }

    private function isDue($preference): bool
    {
        if (! $preference->last_sent_at) {
            return true;
        }

        return match ($preference->frequency) {
            'weekly' => $preference->last_sent_at->lte(now()->subWeek()),
            default => $preference->last_sent_at->lte(now()->subDay()),
        };
    }

    private function matchesPreferenceFilters($job, $preference): bool
    {
        $categories = collect($preference->categories ?? [])
            ->map(fn (string $category): string => str($category)->lower()->toString())
            ->filter()
            ->values();

        if ($categories->isNotEmpty() && ! $categories->contains(strtolower((string) $job->category))) {
            return false;
        }

        $keywords = collect($preference->keywords ?? [])
            ->map(fn (string $keyword): string => trim(strtolower($keyword)))
            ->filter()
            ->values();

        if ($keywords->isEmpty()) {
            return true;
        }

        $haystack = strtolower($job->title.' '.$job->company.' '.$job->summary.' '.$job->description.' '.implode(' ', $job->skills ?? []).' '.implode(' ', $job->tags ?? []));

        return $keywords->contains(fn (string $keyword): bool => str_contains($haystack, $keyword));
    }
}
