import cron from "node-cron";
import { postHeartbeat } from "./api-client.js";
import { env } from "./config.js";
import { runAuditLowConfidenceJob } from "./jobs/audit-low-confidence.js";
import { runTransformCorrectionsJob } from "./jobs/transform-corrections.js";
import { TechStepLlmService } from "./llm-verdict.js";
import { loadTechStepTaxonomy } from "./tech-step-taxonomy.js";

/**
 * Runs one full cycle: fetch the taxonomy, load the model, run both jobs,
 * dispose the model. The model is never kept loaded between scheduled
 * runs (see `llm-verdict.ts`'s `dispose()` doc comment) — this function's
 * own duration (model load/dispose easily adds several seconds) is an
 * accepted cost of keeping this process's idle RAM footprint low between
 * runs, not something to optimize away.
 */
export async function runOnce(): Promise<void> {
  console.info("[tech-step-llm-worker] starting scheduled run...");
  const taxonomy = await loadTechStepTaxonomy();
  const techStepKeys = taxonomy.map((techStep) => techStep.key);

  const llm = new TechStepLlmService();
  try {
    await llm.initialize(techStepKeys);

    const auditCount = await runAuditLowConfidenceJob(llm, {
      locale: env.TECH_STEP_WORKER_LOCALE,
      limit: env.TECH_STEP_WORKER_BATCH_LIMIT,
    });
    console.info(`[tech-step-llm-worker] audit-low-confidence: ${auditCount} suggestion(s)`);
    await postHeartbeat({
      event: "job",
      job: "audit-low-confidence",
      ok: true,
      counts: { suggestions: auditCount },
    });

    const correctionCount = await runTransformCorrectionsJob(llm, {
      limit: env.TECH_STEP_WORKER_BATCH_LIMIT,
    });
    console.info(`[tech-step-llm-worker] transform-corrections: ${correctionCount} suggestion(s)`);
    await postHeartbeat({
      event: "job",
      job: "transform-corrections",
      ok: true,
      counts: { suggestions: correctionCount },
    });
  } finally {
    await llm.dispose();
  }
  console.info("[tech-step-llm-worker] scheduled run complete.");
}

/**
 * Starts the long-lived cron loop — {@link runOnce} fires on
 * `env.TECH_STEP_WORKER_CRON`'s schedule, indefinitely, until the process
 * is stopped. A run that throws is logged, not left to crash the process —
 * the next scheduled fire still happens; a transient API/model failure on
 * one run shouldn't permanently kill the worker until someone notices and
 * manually restarts its container.
 */
export function startScheduler(): void {
  console.info(`[tech-step-llm-worker] scheduling runs on "${env.TECH_STEP_WORKER_CRON}"`);
  cron.schedule(env.TECH_STEP_WORKER_CRON, () => {
    // Prove liveness even for a fire that then fails inside `runOnce`.
    void postHeartbeat({ event: "tick" });
    runOnce().catch((err: unknown) => {
      console.error("[tech-step-llm-worker] scheduled run failed:", err);
      void postHeartbeat({ event: "job", ok: false });
    });
  });
}
