<?php

namespace App\Console\Commands;

use App\Mail\ApiCredentialsExpiringMail;
use App\Master\ApiUserRepository;
use App\Master\Models\ApiToken;
use App\Master\Models\ApiUser;
use App\Models\SystemAlert;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;

class CheckExpiringApiCredentials extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'check:expiring-api-credentials';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Check for expiring API credentials and send alerts to developers';

    /**
     * Alert thresholds in days before expiry.
     */
    private const ALERT_THRESHOLDS = [30, 7, 0];

    /**
     * Execute the console command.
     */
    public function handle(ApiUserRepository $repository): int
    {
        if ($this->shouldSkipTask()) {
            return self::SUCCESS;
        }

        $this->purgeOldAlerts();
        $this->cleanupExpiredPreviousCredentials($repository);

        foreach (self::ALERT_THRESHOLDS as $daysUntilExpiry) {
            $this->checkAndAlertForThreshold($daysUntilExpiry);
        }

        return self::SUCCESS;
    }

    /**
     * Check for expiring credentials at a specific threshold and send alerts.
     */
    private function checkAndAlertForThreshold(int $daysUntilExpiry): void
    {
        $targetDate = Carbon::today()->addDays($daysUntilExpiry);

        $oauthUsers = $this->getExpiringOAuthUsers($targetDate);
        $persistentTokens = $this->getExpiringPersistentTokens($targetDate);

        if ($oauthUsers->isEmpty() && $persistentTokens->isEmpty()) {
            $this->info("No credentials expiring in {$daysUntilExpiry} days");
            return;
        }

        $this->info("Found {$oauthUsers->count()} OAuth users and {$persistentTokens->count()} '
            . 'persistent tokens expiring in {$daysUntilExpiry} days");

        $this->sendAlerts($oauthUsers, $persistentTokens, $daysUntilExpiry);
    }

    /**
     * Get OAuth users with credentials expiring on the target date.
     */
    private function getExpiringOAuthUsers(Carbon $targetDate): Collection
    {
        return ApiUser::query()
            ->where('is_active', true)
            ->where('token_type', ApiToken::TYPE_OAUTH)
            ->whereNotNull('credentials_due_for_update_at')
            ->whereDate('credentials_due_for_update_at', $targetDate)
            ->get();
    }

    /**
     * Get persistent tokens expiring on the target date.
     */
    private function getExpiringPersistentTokens(Carbon $targetDate): Collection
    {
        return ApiToken::query()
            ->where('token_type', ApiToken::TYPE_PERSISTENT)
            ->whereDate('expires_at', $targetDate)
            ->with('apiUser')
            ->get();
    }

    /**
     * Send alert emails to developers.
     */
    private function sendAlerts(Collection $oauthUsers, Collection $persistentTokens, int $daysUntilExpiry): void
    {
        $alertType = "api_credentials_expiring_{$daysUntilExpiry}";

        if ($this->recentAlertExists($alertType)) {
            $this->info("Alert for {$daysUntilExpiry} days suppressed - already sent today");
            return;
        }

        $developerEmails = config('mail.developer_alerts');

        if (empty($developerEmails)) {
            $this->warn('No developer alert emails configured');
            return;
        }

        foreach ($developerEmails as $email) {
            try {
                Mail::to($email)->send(new ApiCredentialsExpiringMail(
                    $oauthUsers,
                    $persistentTokens,
                    $daysUntilExpiry
                ));
                $this->info("Alert sent to {$email}");
            } catch (\Exception $e) {
                $this->error("Failed to send alert to {$email}: {$e->getMessage()}");
            }
        }

        $this->logAlert($alertType);
    }

    /**
     * Check if a recent alert of this type already exists today.
     */
    private function recentAlertExists(string $alertType): bool
    {
        return SystemAlert::query()
            ->where('alert_type', $alertType)
            ->whereDate('last_sent_at', Carbon::today())
            ->exists();
    }

    /**
     * Log the alert to prevent duplicates.
     */
    private function logAlert(string $alertType): void
    {
        SystemAlert::query()->create([
            'alert_type' => $alertType,
            'last_sent_at' => Carbon::now(),
        ]);
    }

    /**
     * Purge old alert records.
     */
    private function purgeOldAlerts(): void
    {
        $deleted = SystemAlert::query()
            ->where('alert_type', 'like', 'api_credentials_expiring_%')
            ->where('last_sent_at', '<', Carbon::now()->subMonths(3))
            ->delete();

        if ($deleted > 0) {
            $this->info("Purged {$deleted} old alert records");
        }
    }

    /**
     * Clean up expired previous credentials from API users.
     */
    private function cleanupExpiredPreviousCredentials(ApiUserRepository $repository): void
    {
        $cleared = $repository->clearExpiredPreviousCredentials();

        if ($cleared > 0) {
            $this->info("Cleared {$cleared} expired previous credential(s)");
        }
    }

    /**
     * Determine if the task should be skipped based on the environment.
     */
    private function shouldSkipTask(): bool
    {
        return in_array(app()->environment(), ['local', 'production']) === false
            && app()->runningUnitTests() === false;
    }
}
