/**
 * P2-5 — nightly rollup scheduling.
 *
 * The worker ticks every hour but the RollupService is expensive (hourly
 * upserts + chunked deletes across every monitor's raw heartbeats), so we
 * only run it when the configured interval has elapsed since the last run.
 *
 * `lastRunAt` is persisted in the SystemSetting table under the key
 * `rollup.lastRunAt` so the schedule survives worker restarts.
 */
import type { PrismaClient } from '@prisma/client';
import { RollupService } from './rollup.service';

const SETTING_KEY = 'rollup.lastRunAt';

export function shouldRunRollupNow(
    lastRunAt: Date | null,
    intervalHours: number,
    now: Date
): boolean {
    if (!lastRunAt) return true;
    // Clock-skew defence: a future timestamp means someone (or NTP) set
    // it ahead. Treat as "ran already" until real time catches up.
    if (lastRunAt.getTime() > now.getTime()) return false;
    return now.getTime() - lastRunAt.getTime() >= intervalHours * 60 * 60 * 1000;
}

/**
 * Read the persisted last-run timestamp. Returns null if never set or
 * if the stored value is unparseable.
 */
export async function readLastRunAt(prisma: PrismaClient): Promise<Date | null> {
    const row = await prisma.systemSetting.findUnique({ where: { key: SETTING_KEY } });
    if (!row) return null;
    const t = new Date(row.value);
    return Number.isFinite(t.getTime()) ? t : null;
}

export async function writeLastRunAt(prisma: PrismaClient, now: Date): Promise<void> {
    await prisma.systemSetting.upsert({
        where: { key: SETTING_KEY },
        update: { value: now.toISOString(), category: 'maintenance' },
        create: {
            key: SETTING_KEY,
            value: now.toISOString(),
            category: 'maintenance',
            description: 'Last successful HeartbeatHourly rollup run (P2-5)',
        },
    });
}

/**
 * Check the schedule and run the rollup if due. Safe to call frequently
 * (e.g. every hour from the worker); the bounded RollupService only runs
 * when `shouldRunRollupNow` returns true.
 */
export async function runRollupTickIfDue(
    prisma: PrismaClient,
    options: { intervalHours: number; retentionDays: number; now?: Date } = {
        intervalHours: 24,
        retentionDays: 7,
    }
): Promise<{ ran: boolean; reason?: string }> {
    const now = options.now ?? new Date();
    const lastRunAt = await readLastRunAt(prisma);

    if (!shouldRunRollupNow(lastRunAt, options.intervalHours, now)) {
        return {
            ran: false,
            reason: lastRunAt ? `lastRunAt=${lastRunAt.toISOString()} < intervalHours=${options.intervalHours}` : 'no-op',
        };
    }

    await RollupService.runRollup(options.retentionDays);
    await writeLastRunAt(prisma, now);
    return { ran: true };
}
