import "server-only";

import type { CheckTrigger } from "@/generated/prisma/client";
import { db } from "../db";
import { logger } from "../logger";
import type { BulkCheckSummary } from "../health/runner";

// SchedulerRun rows are the execution log *and* the lock (see prisma/schema.prisma).

export const HEARTBEAT_INTERVAL_MS = 30_000;
// A RUNNING row whose heartbeat is this old belongs to a process that died.
const STALE_AFTER_MS = 5 * 60_000;

function isUniqueViolation(error: unknown): boolean {
  return typeof error === "object" && error !== null && (error as { code?: string }).code === "P2002";
}

async function failStaleRuns() {
  const { count } = await db.schedulerRun.updateMany({
    where: { status: "RUNNING", heartbeatAt: { lt: new Date(Date.now() - STALE_AFTER_MS) } },
    data: { status: "FAILED", finishedAt: new Date(), errorMessage: "Run did not finish (server stopped or crashed)" },
  });
  if (count > 0) logger.warn("scheduler.run.stale_cleared", { count });
}

/**
 * Try to become the one bulk run in progress. Returns null when another run
 * is active, or when this scheduled slot was already claimed — the database's
 * unique indexes decide, so this is safe across processes and instances.
 */
export async function claimRun(options: { trigger: CheckTrigger; slotKey?: string; startedBy?: string }) {
  await failStaleRuns();
  try {
    return await db.schedulerRun.create({
      data: { trigger: options.trigger, slotKey: options.slotKey ?? null, startedBy: options.startedBy ?? null },
    });
  } catch (error) {
    if (isUniqueViolation(error)) return null;
    throw error;
  }
}

export function touchRun(id: string) {
  return db.schedulerRun.update({ where: { id }, data: { heartbeatAt: new Date() } });
}

export function completeRun(id: string, summary: BulkCheckSummary) {
  return db.schedulerRun.update({
    where: { id },
    data: {
      status: "COMPLETED",
      finishedAt: new Date(),
      appsFound: summary.found,
      appsChecked: summary.checked,
      working: summary.working,
      slow: summary.slow,
      notWorking: summary.notWorking,
      disabled: summary.disabled,
      errored: summary.errored,
    },
  });
}

export function failRun(id: string, error: unknown) {
  const message = error instanceof Error ? error.message : String(error);
  return db.schedulerRun.update({
    where: { id },
    data: { status: "FAILED", finishedAt: new Date(), errorMessage: message.slice(0, 500) },
  });
}

export async function slotAlreadyRun(slotKey: string): Promise<boolean> {
  return (await db.schedulerRun.count({ where: { slotKey } })) > 0;
}

export function getActiveRun() {
  return db.schedulerRun.findFirst({
    where: { status: "RUNNING" },
    select: { id: true, trigger: true, startedAt: true, startedBy: true },
  });
}

export function listRecentRuns(limit = 10) {
  return db.schedulerRun.findMany({ orderBy: { startedAt: "desc" }, take: limit });
}

/** One run plus the result it recorded for each app, ordered by app code. Null when the run doesn't exist. */
export function getRunWithResults(id: string) {
  return db.schedulerRun.findUnique({
    where: { id },
    include: {
      healthChecks: {
        orderBy: { project: { projectCode: "asc" } },
        select: {
          id: true,
          status: true,
          checkedAt: true,
          httpStatusCode: true,
          responseTimeMs: true,
          errorMessage: true,
          project: { select: { id: true, projectCode: true, projectName: true, deletedAt: true } },
        },
      },
    },
  });
}
