import { getConfig } from "./config.js";
import { query } from "./db.js";
import { readSendFailures } from "./failures.js";
import { drainStartedAt, hasPushDirectory } from "./drain.js";
import { hasKnownApps } from "./registry.js";
const BACKLOG_ROWS = 500;
const BACKLOG_AGE_S = 300;
const NEVER_DELIVERED_DAYS = 7;
const REJECTED_SENTINEL = "notify.rejected";
export async function notifyHealth() {
    const problems = [];
    let fcmConfigured = false;
    let delivery = "unknown";
    try {
        fcmConfigured = getConfig().fcm !== null;
        delivery = fcmConfigured ? "fcm" : "log_mode";
    }
    catch {
        problems.push("configuration_unreadable");
    }
    if (delivery === "log_mode")
        problems.push("fcm_credentials_absent");
    const directorySet = hasPushDirectory();
    if (!directorySet)
        problems.push("push_directory_absent");
    const registrySet = hasKnownApps();
    if (!registrySet)
        problems.push("app_registry_absent");
    const drainRunning = Boolean(drainStartedAt());
    if (!drainRunning)
        problems.push("drain_stopped");
    let database = true;
    let outbox = null;
    let recent = {};
    let neverDelivered = [];
    try {
        outbox = await readOutbox();
        recent = await readRecentOutcomes();
        neverDelivered = await readNeverDelivered();
    }
    catch (err) {
        console.error("[notify] health could not read the database", err);
        database = false;
        problems.push("database_unreachable");
    }
    if (outbox && (outbox.overdue > BACKLOG_ROWS || outbox.oldest_overdue_s > BACKLOG_AGE_S)) {
        problems.push("outbox_backlog");
    }
    const sent = recent.sent ?? 0;
    const failed = recent.failed ?? 0;
    const noToken = recent.no_token ?? 0;
    if (sent === 0 && failed > 0)
        problems.push("delivery_failing");
    if (sent === 0 && failed === 0 && noToken > 0)
        problems.push("no_push_targets");
    if (neverDelivered.length > 0) {
        problems.push("categories_never_delivered");
        const described = neverDelivered
            .map((n) => `${n.app_id}:${n.category} (${n.reason ?? "reason not recorded"})`)
            .join(", ");
        console.error(`[notify] never delivered in ${NEVER_DELIVERED_DAYS} days: ${described}`);
    }
    const status = !database
        ? "down"
        : problems.length > 0
            ? "degraded"
            : "ok";
    return {
        ok: status === "ok",
        status,
        delivery,
        problems,
        checks: {
            database,
            fcm_credentials: fcmConfigured,
            push_directory: directorySet,
            drain: drainRunning,
            app_registry: registrySet,
            outbox,
            last_48h: recent,
            never_delivered: neverDelivered,
        },
    };
}
async function readOutbox() {
    const res = await query(`SELECT count(*) AS pending,
            count(*) FILTER (WHERE send_after <= now()) AS overdue,
            coalesce(
              extract(epoch FROM now() - min(send_after) FILTER (WHERE send_after <= now())),
              0
            ) AS oldest_overdue_s,
            count(*) FILTER (WHERE attempts > 0) AS retrying
       FROM outbox`);
    const row = res.rows[0];
    return {
        pending: Number(row.pending),
        overdue: Number(row.overdue),
        oldest_overdue_s: Math.round(Number(row.oldest_overdue_s ?? 0)),
        retrying: Number(row.retrying),
    };
}
async function readNeverDelivered() {
    const res = await query(`SELECT app_id, category FROM send_counters
      WHERE day >= CURRENT_DATE - $1::int AND category <> $2
      GROUP BY app_id, category
     HAVING coalesce(sum(count) FILTER (WHERE outcome = 'sent'), 0) = 0
        AND coalesce(sum(count) FILTER (WHERE outcome IN ('failed', 'rejected')), 0) > 0
      ORDER BY app_id, category`, [NEVER_DELIVERED_DAYS, REJECTED_SENTINEL]);
    const reasons = await readSendFailures(res.rows.map((row) => `${row.app_id}:${row.category}`));
    // A null reason is a real answer, not a gap to hide: it says this pair failed before the
    // reason was being written down, which is every failure recorded before 2026-09-01.
    return res.rows.map((row) => {
        const failure = reasons.get(`${row.app_id}:${row.category}`);
        return {
            app_id: row.app_id,
            category: row.category,
            reason: failure?.reason ?? null,
            failures: failure?.failures ?? null,
            last_at: failure?.last_at ?? null,
        };
    });
}
async function readRecentOutcomes() {
    const res = await query(`SELECT outcome, sum(count) AS total FROM send_counters
      WHERE day >= CURRENT_DATE - 1
      GROUP BY outcome`);
    const out = {};
    for (const row of res.rows)
        out[row.outcome] = Number(row.total);
    return out;
}
