import { spawnSync } from "node:child_process";
import type { Server } from "bun";
import { join } from "node:path";
import { startAdmissionReconcileLoop } from "./admission-loop";
import { createSshCapabilityProber } from "./capability";
import { createBackgroundHostMetricsSampler, createSshHostMetricsSampler } from "./host-metrics";
import {
  ControllerFatalError,
  dataDir,
  loadConfigResult,
  type ConfigLoadResult,
} from "./config";
import { projectEventLog } from "./events";
import { createK3sJobWatcher } from "./k3s-watcher";
import { createIncidentNotifierAdapter, createLaptopNotifierSink, defaultDesktopExec } from "./notify";
import { createControllerRuntime, startServer, type ControllerRuntime } from "./server";
import { loadOrCreateToken } from "./token";

export type NotifyExecResult = {
  status: number | null;
  error?: { code?: string; message?: string };
};

export type NotifyExec = (file: string, argv: readonly string[]) => NotifyExecResult;

export interface ControllerHandle {
  server: Server<undefined>;
  configHealth: ConfigLoadResult;
  runtime: ControllerRuntime;
  shutdown(signal?: NodeJS.Signals): Promise<void>;
}

export interface StartControllerOptions {
  notifyExec?: NotifyExec;
  capabilityProber?: ReturnType<typeof createSshCapabilityProber>;
  configHealth?: ConfigLoadResult;
  token?: string;
  watchdogIntervalMs?: number;
  projectionIntervalMs?: number;
  port?: number;
}

const SYSTEMD_NOTIFY = "/usr/bin/systemd-notify";
const PROJECTION_INTERVAL_MS = 30_000;
const DEFAULT_WATCHDOG_INTERVAL_MS = 10_000;

export function defaultNotifyExec(file: string, argv: readonly string[]): NotifyExecResult {
  try {
    const result = spawnSync(file, [...argv], { stdio: "ignore" });
    return { status: result.status };
  } catch (error) {
    const err = error as NodeJS.ErrnoException;
    return {
      status: null,
      error: { code: err.code, message: err.message },
    };
  }
}

export async function startController(
  options: StartControllerOptions = {},
): Promise<ControllerHandle> {
  const configHealth = options.configHealth ?? loadConfigResult();
  const bindPort = options.port ?? configHealth.config.port;
  const token = options.token ?? loadOrCreateToken();
  const capabilityProber = options.capabilityProber ?? createSshCapabilityProber();
  const runtime = await createControllerRuntime({ configHealth, capabilityProber });
  const eventsPath = join(dataDir(configHealth.config), "events.jsonl");

  const sweepVanishedSpineConfigs = () => {
    const cleared = runtime.store.clearVanishedSpineConfigDegradation(new Date().toISOString());
    for (const path of cleared) {
      process.stdout.write(`spine-config: cleared degradation for vanished config ${path}\n`);
    }
  };

  projectEventLog(runtime.store, eventsPath);
  sweepVanishedSpineConfigs();
  const projectionTimer = setInterval(() => {
    projectEventLog(runtime.store, eventsPath);
    sweepVanishedSpineConfigs();
  }, options.projectionIntervalMs ?? PROJECTION_INTERVAL_MS);

  const laptopSink = createLaptopNotifierSink({
    mode: configHealth.config.notify.mode,
    journalWrite: (line) => {
      process.stdout.write(`${line}\n`);
    },
    desktopExec: defaultDesktopExec,
  });

  const hostMetricsSampler = createBackgroundHostMetricsSampler(
    createSshHostMetricsSampler(),
    () => runtime.store.listHosts().map(({ hostname }) => hostname),
  );

  const server = startServer({
    host: configHealth.config.bindHost,
    port: bindPort,
    token,
    store: runtime.store,
    engine: runtime.engine,
    scheduler: runtime.scheduler,
    capability: runtime.capability,
    configHealth,
    metricsSampler: hostMetricsSampler,
    notifier: createIncidentNotifierAdapter(laptopSink),
  });

  const admissionLoop = startAdmissionReconcileLoop({
    store: runtime.store,
    engine: runtime.engine,
    reconcileDeliveryLifecycle: () => runtime.deliveryActivator.reconcileExpired(),
  });

  // Additive telemetry only — no-ops cleanly when k3s_enabled is unset; see k3s-watcher.ts.
  const k3sWatcher = createK3sJobWatcher({
    store: runtime.store,
    capability: runtime.capability,
  });

  const notifyExec = options.notifyExec ?? defaultNotifyExec;
  let notifyDisabled = false;
  let notifyLoggedMissing = false;
  let watchdogTimer: ReturnType<typeof setInterval> | undefined;
  let shuttingDown = false;

  const notify = (argv: readonly string[]) => {
    if (notifyDisabled || !process.env.NOTIFY_SOCKET) return;
    const result = notifyExec(SYSTEMD_NOTIFY, argv);
    if (result.error?.code === "ENOENT") {
      if (!notifyLoggedMissing) {
        console.error(
          `${SYSTEMD_NOTIFY} not found (${result.error.message}); disabling systemd notifications`,
        );
        notifyLoggedMissing = true;
      }
      notifyDisabled = true;
      if (watchdogTimer) {
        clearInterval(watchdogTimer);
        watchdogTimer = undefined;
      }
    }
  };

  notify(["READY=1"]);
  const watchdogIntervalMs = options.watchdogIntervalMs ?? DEFAULT_WATCHDOG_INTERVAL_MS;
  if (process.env.NOTIFY_SOCKET) {
    watchdogTimer = setInterval(() => {
      notify(["WATCHDOG=1"]);
    }, watchdogIntervalMs);
  }

  const shutdown = async (_signal?: NodeJS.Signals) => {
    if (shuttingDown) return;
    shuttingDown = true;
    clearInterval(projectionTimer);
    if (watchdogTimer) {
      clearInterval(watchdogTimer);
      watchdogTimer = undefined;
    }
    admissionLoop.stop();
    k3sWatcher.stop();
    hostMetricsSampler.stop();
    server.stop(true);
    projectEventLog(runtime.store, eventsPath);
    runtime.store.close();
  };

  const onSignal = (signal: NodeJS.Signals) => {
    void shutdown(signal).then(() => {
      process.exit(0);
    });
  };

  process.once("SIGTERM", onSignal);
  process.once("SIGINT", onSignal);

  return { server, configHealth, runtime, shutdown };
}

if (import.meta.main) {
  startController().catch((error) => {
    if (error instanceof ControllerFatalError) {
      console.error(`${error.code}: ${error.message}`);
      process.exit(1);
    }
    throw error;
  });
}
