<?php

use App\Jobs\External\RefreshExternalBalancesJob;
use App\Jobs\SendNewLeadsDigestJob;
use App\Jobs\SnapshotProjectRoutingJob;
use App\Jobs\Supplier\CleanupInactiveSupplierProjectsJob;
use App\Jobs\Supplier\CsvReconcileJob;
use App\Jobs\Supplier\FlushDeferredOnlineSyncJob;
use App\Jobs\Supplier\RefreshSupplierSessionJob;
use App\Jobs\Supplier\SyncSupplierProjectsJob;
use App\Services\SchedulerHeartbeatTracker;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

// Hole #6: heartbeat-трекинг всех cron-задач.
// SchedulerHeartbeatTracker::recordRun() оборачивает каждую задачу через
// before/after/onFailure хуки Laravel Scheduler — минимально инвазивный подход.
/** @var SchedulerHeartbeatTracker $hb */
$hb = app(SchedulerHeartbeatTracker::class);

// Spec §6.1: ежедневный сброс projects.delivered_today=0 в 00:00 МСК.
// delivered_in_month НЕ трогаем — это месячный счётчик, отдельный cron Plan 4.
//
// NB: без `withoutOverlapping()` — операция идемпотентна (UPDATE WHERE delivered_today <> 0)
// и завершается за < 1 сек на любом ожидаемом объёме, overlap физически невозможен.
// Кроме того, `withoutOverlapping` требует таблицу `cache_locks`, которой в нашей
// schema.sql нет (Laravel-default-миграции удалены, см. project_state.md фаза 1).
Schedule::command('projects:reset-delivered-today')
    ->dailyAt('00:00')
    ->timezone('Europe/Moscow')
    ->before(fn () => $startTimes['projects:reset-delivered-today'] = microtime(true))
    ->onSuccess(function () use ($hb, &$startTimes): void {
        $name = 'projects:reset-delivered-today';
        $ms = isset($startTimes[$name]) ? (int) ((microtime(true) - $startTimes[$name]) * 1000) : null;
        $hb->recordRunResult($name, true, null, $ms);
    })
    ->onFailure(function () use ($hb): void {
        $hb->recordRunResult('projects:reset-delivered-today', false, 'Command failed', null);
    });

// Task 4.2: досыл отложенной онлайн-очереди в 00:05 МСК (после сброса счётчиков в 00:00,
// вне окна 18:00→00:00 — отложенные правки уходят поставщику немедленно).
Schedule::job(new FlushDeferredOnlineSyncJob)
    ->dailyAt('00:05')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\Supplier\FlushDeferredOnlineSyncJob', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\Supplier\FlushDeferredOnlineSyncJob', false, 'Job failed', null));

// Плитка балансов (28.06): ежедневный сбор баланса внешних сервисов (DaData/Поставщик/
// Yandex Cloud) в 06:30 МСК. Изоляция провайдеров внутри джобы; падение сети одного
// сервиса не роняет остальных и не валит расписание.
Schedule::job(new RefreshExternalBalancesJob)
    ->dailyAt('06:30')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\External\RefreshExternalBalancesJob', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\External\RefreshExternalBalancesJob', false, 'Job failed', null));

// Plan 4: monthly reset 1-го числа в 00:00 МСК для tier-lookup в LedgerService.
Schedule::command('projects:reset-monthly')
    ->monthlyOn(1, '00:00')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('projects:reset-monthly', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('projects:reset-monthly', false, 'Command failed', null));

// Audit #2 Phase 14 P2: partition maintenance — создаёт разделы на 3 месяца вперёд.
// Без этой записи partitions:create-months не запускается автоматически.
Schedule::command('partitions:create-months')
    ->daily()
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('partitions:create-months', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('partitions:create-months', false, 'Command failed', null));

// Hole #2 (23.05.2026): удаление устаревших месячных партиций согласно retention
// (system_settings: partition_retention_months_<table>).
// Запускается еженедельно в воскресенье в 03:00 МСК — вне пиковых часов,
// но раз в неделю достаточно (данные удаляются целыми месяцами).
Schedule::command('partitions:drop-expired')
    ->weeklyOn(0, '03:00')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('partitions:drop-expired', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('partitions:drop-expired', false, 'Command failed', null));

// F-P1 (152-ФЗ ретеншен): ежедневная анонимизация ПДн soft-deleted сделок старше
// retention-срока (system_settings.pd_scrub_soft_deleted_deals_days; no-op пока не задан).
// 03:30 МСК — вне пиковых часов, после partitions:drop-expired (03:00).
Schedule::command('pd:scrub-soft-deleted-deals')
    ->dailyAt('03:30')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('pd:scrub-soft-deleted-deals', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('pd:scrub-soft-deleted-deals', false, 'Command failed', null));

// Billing v2 Spec C §3.2: преfflight баланса в 18:00 MSK — заморозка/разморозка
// тенантов перед формированием заказа поставщику (без «бедных» клиентов).
// ВАЖНО: идёт ДО SyncSupplierProjectsJob (сдвинут на 18:05) — фильтр frozen-проектов
// должен примениться к расчёту заказа того же вечера.
Schedule::command('billing:preflight-sweep')
    ->dailyAt('18:00')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('billing:preflight-sweep', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('billing:preflight-sweep', false, 'Command failed', null));

// Billing v2 Spec C §3.7: повторные письма заморозки (reminder +1д, final +3д).
// Идёт ПОСЛЕ основного sweep — если sweep только что заморозил тенанта, окно reminder
// (24h+) ещё не открылось, повторного письма в тот же день не будет (correct).
Schedule::command('billing:frozen-reminder')
    ->dailyAt('18:30')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('billing:frozen-reminder', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('billing:frozen-reminder', false, 'Command failed', null));

// Plan 3 Task 8: 5 Schedule entries для supplier-flow.
//
// NB: ->onOneServer() требует cache_locks таблицу, которой у нас нет
// (см. project_state.md фаза 1). Операции идемпотентны: SyncSupplierProjectsJob
// делает diff'ы (skip-no-diff), CleanupJob — UPDATE WHERE conditions, RefreshSession
// — Cache::lock guard внутри handle, RetryFailedSupplierJobs — WHERE retried_at
// фильтр. На multi-server prod может потребовать cache_locks таблицу.
Schedule::job(new RefreshSupplierSessionJob)->hourly()
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\Supplier\RefreshSupplierSessionJob@hourly', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\Supplier\RefreshSupplierSessionJob@hourly', false, 'Job failed', null));
// Spec docs/superpowers/specs/2026-05-19-supplier-project-channel-failover-design.md §4.7:
// крон переехал с 20:30 на 18:00 МСК — даёт ~3 часа окно восстановления
// (эскалация на медленный ярус 2 / ручной ярус 3) в рабочее время до
// портального дедлайна 21:00. Session refresh — на 15 мин раньше sync (17:45).
Schedule::job(new RefreshSupplierSessionJob)
    ->dailyAt('17:45')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\Supplier\RefreshSupplierSessionJob@daily', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\Supplier\RefreshSupplierSessionJob@daily', false, 'Job failed', null));
// Spec 2026-05-26-slepok-routing-protection §4.2.2:
// SnapshotProjectRoutingJob создаёт slepok №NЛ для дня N+1 в 18:02 МСК.
// Запускается ПОСЛЕ billing:preflight-sweep (18:00) и ДО SyncSupplierProjectsJob (18:05).
Schedule::job(new SnapshotProjectRoutingJob)
    ->dailyAt('18:02')
    ->timezone('Europe/Moscow')
    ->before(fn () => $startTimes['SnapshotProjectRoutingJob'] = microtime(true))
    ->onSuccess(function () use ($hb, &$startTimes): void {
        $name = 'SnapshotProjectRoutingJob';
        $ms = isset($startTimes[$name]) ? (int) ((microtime(true) - $startTimes[$name]) * 1000) : null;
        $hb->recordRunResult($name, true, null, $ms);
    })
    ->onFailure(fn () => $hb->recordRunResult('SnapshotProjectRoutingJob', false, 'Job failed', null));

// Billing v2 Spec C: сдвинут 18:00 → 18:05, чтобы billing:preflight-sweep (18:00)
// успел проставить frozen-флаги до формирования заказа поставщику.
Schedule::job(new SyncSupplierProjectsJob)
    ->dailyAt('18:05')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\Supplier\SyncSupplierProjectsJob', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\Supplier\SyncSupplierProjectsJob', false, 'Job failed', null));
Schedule::job(new CleanupInactiveSupplierProjectsJob)
    ->dailyAt('02:00')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\Supplier\CleanupInactiveSupplierProjectsJob', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\Supplier\CleanupInactiveSupplierProjectsJob', false, 'Job failed', null));
Schedule::command('supplier:retry-failed')->hourly()
    ->onSuccess(fn () => $hb->recordRunResult('supplier:retry-failed', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('supplier:retry-failed', false, 'Command failed', null));

// Резервный CSV-канал (Путь 2): сверка каждые 30 минут.
// Spec: docs/superpowers/specs/2026-05-18-supplier-csv-reconcile-channel-design.md §4.5
Schedule::job(new CsvReconcileJob)->everyThirtyMinutes()
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\Supplier\CsvReconcileJob', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\Supplier\CsvReconcileJob', false, 'Job failed', null));

// G2-A: дайджест новых сделок клиентам — каждые 30 минут.
Schedule::job(new SendNewLeadsDigestJob)->everyThirtyMinutes()
    ->onSuccess(fn () => $hb->recordRunResult('App\Jobs\SendNewLeadsDigestJob', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('App\Jobs\SendNewLeadsDigestJob', false, 'Job failed', null));

// Audit #2 Phase 14 P2: авто-детекция штормов упавших webhook-джобов.
// Сканирует за последние 10 мин, порог 200, дедуп 60 мин.
Schedule::command('incidents:watch-failures')
    ->everyTenMinutes()
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('incidents:watch-failures', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('incidents:watch-failures', false, 'Command failed', null));

// Hole #1: ежедневная проверка SHA-256 hash-chain в 6 audit-таблицах.
// Разрыв → incidents_log (severity high) + email kdv1@bk.ru.
// Ref: docs/superpowers/plans/2026-05-23-hole-1-hash-chain-validator.md
Schedule::command('audit:verify-chains')
    ->dailyAt('04:00')
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('audit:verify-chains', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('audit:verify-chains', false, 'Command failed', null));

// Hole #6: проверка пульса планировщика — hourly МСК.
Schedule::command('scheduler:check-heartbeats')
    ->hourly()
    ->timezone('Europe/Moscow')
    ->onSuccess(fn () => $hb->recordRunResult('scheduler:check-heartbeats', true, null, null))
    ->onFailure(fn () => $hb->recordRunResult('scheduler:check-heartbeats', false, 'Command failed', null));
