/**
 * Unified Cron Tick Route (/api/cron/tick)
 *
 * Designed for serverless, cPanel cron, or single-container Docker deployments
 * where `scripts/worker.ts` is not running as a persistent daemon.
 *
 * Secures execution using:
 *  1. CRON_SECRET verification (fails closed if missing or invalid)
 *  2. MySQL non-blocking advisory lock (`GET_LOCK('uptime_sentinel_cron_tick', 0)`)
 *
 * Runs idempotent operations sequentially:
 *  - Monitor checks (`runPendingChecks`)
 *  - Passive heartbeat sweeps (`sweepHeartbeatMonitors`)
 *  - Notification outbox drain (`OutboxService.tick`)
 *  - Notification escalations (`EscalationService.tick`)
 *  - Expired dedup row purging (`DedupService.purgeExpired`)
 *  - Hourly stats rollups (`runRollupTickIfDue`)
 */
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { requireCronSecret } from '@/lib/api-helpers/cron-auth';
import { runPendingChecks } from '@/lib/monitor-engine';
import { sweepHeartbeatMonitors } from '@/lib/services/heartbeat-sweeper';
import { OutboxService } from '@/lib/notification-system/outbox/outbox.service';
import { EscalationService } from '@/lib/services/escalation.service';
import { DedupService } from '@/lib/notification-system/dedup/dedup.service';
import { runRollupTickIfDue } from '@/lib/services/rollup-scheduler';
import { log } from '@/lib/observability/logger';
import { captureException } from '@/lib/observability/sentry';

export const dynamic = 'force-dynamic';

const LOCK_NAME = 'uptime_sentinel_cron_tick';

export async function POST(req: Request) {
    return handleTick(req);
}

export async function GET(req: Request) {
    return handleTick(req);
}

async function handleTick(req: Request) {
    const denied = requireCronSecret(req);
    if (denied) return denied;

    // Acquire non-blocking advisory lock: 0 timeout means fail immediately if held
    const lockRows = await prisma.$queryRaw<Array<{ acquired: number | null }>>`
        SELECT GET_LOCK(${LOCK_NAME}, 0) AS acquired
    `;
    const acquired = Number(lockRows?.[0]?.acquired) === 1;

    if (!acquired) {
        return NextResponse.json(
            { error: 'locked', message: 'Another cron tick is currently running.' },
            { status: 409 }
        );
    }

    try {
        // 1. Run pending active monitor checks
        const checks = await runPendingChecks(50);

        // 2. Sweep passive heartbeat monitors for missed windows
        const heartbeatResult = await sweepHeartbeatMonitors(prisma);

        // 3. Drain notification outbox
        const outbox = OutboxService.default();
        const outboxResult = await outbox.tick(50);

        // 4. Process pending notification escalations
        const escalation = new EscalationService(prisma);
        const escalationResult = await escalation.tick(50);

        // 5. Purge expired notification dedup records
        const dedup = DedupService.default();
        const purgedDedupCount = await dedup.purgeExpired();

        // 6. Run hourly rollups if due
        const rollupResult = await runRollupTickIfDue(prisma, {
            intervalHours: parseInt(process.env.HEARTBEAT_ROLLUP_INTERVAL_HOURS || '24', 10),
            retentionDays: parseInt(process.env.HEARTBEAT_RETENTION_DAYS || '7', 10),
        });

        return NextResponse.json({
            success: true,
            timestamp: new Date().toISOString(),
            checksCount: checks.length,
            heartbeats: {
                inspected: heartbeatResult.inspected,
                markedDown: heartbeatResult.markedDown,
            },
            outbox: {
                claimed: outboxResult.claimed,
                delivered: outboxResult.delivered,
                retried: outboxResult.retried,
                permanentlyFailed: outboxResult.permanentlyFailed,
            },
            escalations: {
                inspected: escalationResult.inspected,
                dispatched: escalationResult.dispatched,
                cancelled: escalationResult.cancelled,
                errored: escalationResult.errored,
            },
            dedupPurged: purgedDedupCount,
            rollupRan: rollupResult.ran,
        });
    } catch (err: unknown) {
        log.error({ err }, 'Error during cron tick execution');
        captureException(err, { stage: 'cron-tick' });
        const message = err instanceof Error ? err.message : String(err);
        return NextResponse.json({ success: false, error: message }, { status: 500 });
    } finally {
        try {
            await prisma.$queryRaw`SELECT RELEASE_LOCK(${LOCK_NAME})`;
        } catch (releaseErr) {
            log.error({ err: releaseErr }, 'Failed to release cron tick lock');
        }
    }
}
