<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Mail\SchedulerHeartbeatMissingMail;
use App\Services\SchedulerHeartbeatTracker;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;

/**
 * Hole #6: проверяет пульс всех зарегистрированных cron-задач.
 *
 * Критерии алерта (для каждой команды в scheduler_heartbeats):
 *   1. last_run_at IS NULL ИЛИ отсутствует > 2× ожидаемого интервала.
 *   2. consecutive_failures >= 3.
 *
 * При обнаружении:
 *   • Создаёт инцидент в incidents_log (type=other, severity=high).
 *   • Отправляет SchedulerHeartbeatMissingMail на kdv1@bk.ru.
 *   • Дедупликация: не создаёт повторный инцидент если открытый уже есть
 *     с той же командой в последние 60 минут.
 *
 * Запускается hourly через routes/console.php.
 */
final class SchedulerCheckHeartbeats extends Command
{
    private const DB_CONNECTION = 'pgsql_supplier';

    private const ALERT_EMAIL = 'kdv1@bk.ru';

    private const DEDUP_MINUTES = 60;

    private const FAILURE_THRESHOLD = 3;

    protected $signature = 'scheduler:check-heartbeats';

    protected $description = 'Проверяет пульс cron-задач и алертит при пропавшем пульсе или повторных ошибках';

    public function handle(): int
    {
        $intervals = SchedulerHeartbeatTracker::EXPECTED_INTERVALS;
        $db = DB::connection(self::DB_CONNECTION);
        $now = Carbon::now();
        $dedupAt = $now->copy()->subMinutes(self::DEDUP_MINUTES);

        // Получаем adminId для FK incidents_log
        $adminId = $db->table('saas_admin_users')
            ->where('is_active', true)
            ->whereNull('deleted_at')
            ->value('id');

        if ($adminId === null) {
            // Паттерн VerifyAuditChains (hole #1): warn + SUCCESS, не FAILURE.
            // FAILURE здесь = бесконечный цикл self-alert (consecutive_failures растёт,
            // watcher пытается алертить, снова FAILURE, инцидент не создаётся).
            $this->warn('No active saas_admin_users — alerts disabled (warn-only mode).');

            return self::SUCCESS;
        }

        // Загружаем все существующие heartbeats
        $rows = $db->table('scheduler_heartbeats')
            ->get()
            ->keyBy('command_name');

        $alerted = 0;

        foreach ($intervals as $commandName => $expectedMinutes) {
            $row = $rows->get($commandName);

            // Проверка 1: пропавший пульс (нет строки вообще или last_run_at старше 2× интервала)
            $heartbeatMissing = false;
            if ($row === null) {
                $heartbeatMissing = true;
                $reason = "Команда '{$commandName}' не имеет ни одной записи heartbeat.";
            } elseif ($row->last_run_at === null) {
                $heartbeatMissing = true;
                $reason = "Команда '{$commandName}' никогда не запускалась.";
            } else {
                $lastRun = Carbon::parse($row->last_run_at);
                $ageMinutes = $lastRun->diffInMinutes($now);
                $threshold = $expectedMinutes * 2;

                if ($ageMinutes > $threshold) {
                    $heartbeatMissing = true;
                    $reason = "Команда '{$commandName}' не запускалась {$ageMinutes} мин. "
                        ."(ожидаемый интервал: {$expectedMinutes} мин, порог: {$threshold} мин).";
                }
            }

            // Проверка 2: consecutive_failures >= threshold
            $consecutiveFailures = $row !== null ? (int) $row->consecutive_failures : 0;
            $failureSpike = $consecutiveFailures >= self::FAILURE_THRESHOLD;

            if (! $heartbeatMissing && ! $failureSpike) {
                continue;
            }

            if (! isset($reason)) {
                $reason = "Команда '{$commandName}' завершилась с ошибкой {$consecutiveFailures} раз подряд.";
            } elseif ($failureSpike) {
                $reason .= " Плюс {$consecutiveFailures} последовательных ошибок.";
            }

            $lastError = $row?->last_error;

            // Дедупликация
            $summary = "Scheduler heartbeat: {$commandName} — {$reason}";
            $sigPrefix = substr("Scheduler heartbeat: {$commandName}", 0, 80);

            $alreadyOpen = $db->table('incidents_log')
                ->where('summary', 'like', '%'.addcslashes($sigPrefix, '%_\\').'%')
                ->whereNull('resolved_at')
                ->where('detected_at', '>=', $dedupAt)
                ->exists();

            if ($alreadyOpen) {
                $this->line("Dedup: {$commandName}");

                continue;
            }

            // Создаём инцидент
            $db->table('incidents_log')->insert([
                'type' => 'other',
                'severity' => 'high',
                'summary' => $summary,
                'root_cause' => null,
                'started_at' => $now,
                'detected_at' => $now,
                'resolved_at' => null,
                'created_by_admin_id' => $adminId,
                'created_at' => $now,
                'updated_at' => $now,
            ]);

            // Отправляем email
            Mail::to(self::ALERT_EMAIL)->send(
                new SchedulerHeartbeatMissingMail(
                    commandName: $commandName,
                    reason: $reason,
                    lastError: $lastError,
                    consecutiveFailures: $consecutiveFailures,
                )
            );

            $this->warn("Alert: {$commandName} — {$reason}");
            $alerted++;
            unset($reason);
        }

        $this->info("Done. {$alerted} alert(s) created.");

        return self::SUCCESS;
    }
}
