import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { spawn } from "node:child_process";
import { connect } from "node:net";
import { resolve } from "node:path";
import { readProviderAppsConfig } from "../../src/components/provider-apps/config";
import { runProviderAppsLinuxCommand } from "./provider-apps-linux";

interface ProcessState {
  readonly pid: number;
  readonly startedAt: string;
}

const repositoryRoot = resolve(import.meta.dir, "../..");

function processState(path: string): ProcessState | null {
  try {
    const value = JSON.parse(readFileSync(path, "utf8")) as ProcessState;
    return Number.isInteger(value.pid) && value.pid > 0 ? value : null;
  } catch {
    return null;
  }
}

function processRunning(pid: number): boolean {
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}

function writeState(path: string, value: ProcessState): void {
  const temporary = `${path}.tmp`;
  writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, "utf8");
  renameSync(temporary, path);
}

async function portListening(host: string, port: number): Promise<boolean> {
  return await new Promise((done) => {
    const socket = connect({ host, port });
    const finish = (value: boolean) => {
      socket.destroy();
      done(value);
    };
    socket.setTimeout(500);
    socket.once("connect", () => finish(true));
    socket.once("timeout", () => finish(false));
    socket.once("error", () => finish(false));
  });
}

async function health(config: ReturnType<typeof readProviderAppsConfig>): Promise<Record<string, unknown> | null> {
  try {
    const response = await fetch(
      `http://${config.runtime.probeHost}:${config.runtime.port}${config.release.routes.health}`,
      { signal: AbortSignal.timeout(1500) },
    );
    return response.ok ? await response.json() as Record<string, unknown> : null;
  } catch {
    return null;
  }
}

async function status(): Promise<Record<string, unknown>> {
  const config = readProviderAppsConfig(repositoryRoot);
  const state = processState(resolve(repositoryRoot, config.runtime.pidPath));
  const running = state !== null && processRunning(state.pid);
  const healthPayload = running ? await health(config) : null;
  return {
    ok: running && healthPayload?.ok === true,
    operation: "provider-apps.server.status",
    target: config.target,
    runtime: {
      pid: state?.pid ?? null,
      running,
      healthy: healthPayload?.ok === true,
      bind: `${config.runtime.bindHost}:${config.runtime.port}`,
      stateDir: config.runtime.stateDir,
      logPath: config.runtime.logPath,
    },
    publicUrl: `${config.publicExposure.publicBaseUrl}${config.release.routes.manifest}`,
    installUrls: {
      human: `${config.publicExposure.publicBaseUrl}${config.install.routes.index}`,
      agentContract: `${config.publicExposure.publicBaseUrl}${config.install.routes.contract}`,
      installer: `${config.publicExposure.publicBaseUrl}${config.install.routes.installer}`,
    },
    health: healthPayload,
  };
}

async function start(): Promise<Record<string, unknown>> {
  const config = readProviderAppsConfig(repositoryRoot);
  const current = await status();
  if (current.ok === true) return { ...current, operation: "provider-apps.server.start", mutation: false };
  const statePath = resolve(repositoryRoot, config.runtime.pidPath);
  const stateDir = resolve(repositoryRoot, config.runtime.stateDir);
  const logPath = resolve(repositoryRoot, config.runtime.logPath);
  mkdirSync(stateDir, { recursive: true });
  if (await portListening(config.runtime.bindHost, config.runtime.port)) {
    throw new Error(`runtime port ${config.runtime.port} is occupied; stop the owning service before retrying`);
  }
  const log = openSync(logPath, "a");
  const child = spawn(process.execPath, [resolve(repositoryRoot, "src/components/provider-apps/index.ts")], {
    cwd: repositoryRoot,
    detached: true,
    stdio: ["ignore", log, log],
  });
  child.unref();
  closeSync(log);
  if (child.pid === undefined) throw new Error("Provider Apps process did not return a PID");
  writeState(statePath, { pid: child.pid, startedAt: new Date().toISOString() });
  await Bun.sleep(250);
  const result = await status();
  if (result.ok !== true) throw new Error(`Provider Apps failed to become healthy; inspect ${config.runtime.logPath}`);
  return { ...result, operation: "provider-apps.server.start", mutation: true };
}

async function stop(): Promise<Record<string, unknown>> {
  const config = readProviderAppsConfig(repositoryRoot);
  const state = processState(resolve(repositoryRoot, config.runtime.pidPath));
  if (state === null || !processRunning(state.pid)) {
    return { ok: true, operation: "provider-apps.server.stop", mutation: false, running: false };
  }
  process.kill(state.pid, "SIGTERM");
  for (let attempt = 0; attempt < 20 && processRunning(state.pid); attempt += 1) await Bun.sleep(100);
  return {
    ok: !processRunning(state.pid),
    operation: "provider-apps.server.stop",
    mutation: true,
    pid: state.pid,
    running: processRunning(state.pid),
  };
}

function logs(): Record<string, unknown> {
  const config = readProviderAppsConfig(repositoryRoot);
  const logPath = resolve(repositoryRoot, config.runtime.logPath);
  const contents = existsSync(logPath) ? readFileSync(logPath) : Buffer.alloc(0);
  const tail = contents.subarray(Math.max(0, contents.length - config.runtime.maxLogTailBytes)).toString("utf8");
  return {
    ok: true,
    operation: "provider-apps.server.logs",
    logPath: config.runtime.logPath,
    totalBytes: contents.length,
    tailBytes: Buffer.byteLength(tail),
    tail,
  };
}

export function providerAppsHelp(): Record<string, unknown> {
  return {
    ok: true,
    operation: "provider-apps.help",
    usage: [
      "bun scripts/cli.ts provider-apps server start",
      "bun scripts/cli.ts provider-apps server stop",
      "bun scripts/cli.ts provider-apps server restart",
      "bun scripts/cli.ts provider-apps server status",
      "bun scripts/cli.ts provider-apps server logs",
      "bun scripts/cli.ts provider-apps linux help",
    ],
    config: "config/platform-infra/apps.yaml",
  };
}

export async function runProviderAppsCommand(args: string[]): Promise<Record<string, unknown>> {
  if (args.length === 0 || args[0] === "help" || args[0] === "--help") return providerAppsHelp();
  if (args[0] === "linux") return await runProviderAppsLinuxCommand(readProviderAppsConfig(repositoryRoot), args.slice(1));
  if (args[0] !== "server" || args.length !== 2) throw new Error("expected provider-apps server start|stop|restart|status|logs");
  if (args[1] === "start") return await start();
  if (args[1] === "stop") return await stop();
  if (args[1] === "restart") {
    const stopped = await stop();
    const started = await start();
    return { ok: started.ok === true, operation: "provider-apps.server.restart", stopped, started };
  }
  if (args[1] === "status") return await status();
  if (args[1] === "logs") return logs();
  throw new Error(`unsupported provider-apps server action: ${args[1]}`);
}
