import { existsSync, mkdirSync } from "node:fs";
import { dirname } from "node:path";
import { createHourlyJsonlWriter, logRetentionBytesForService } from "../../../shared/src/rotating-jsonl";
import { commanderContract } from "./contract";
import { commanderHighRiskActions } from "./contract";
import {
  appendCommanderTraceEvent,
  buildCommanderApprovalDraft,
  commanderApprovalPreview,
  commanderHealth,
  commanderSessionPreview,
  commanderStatePaths,
  listCommanderSessions,
  normalizeCommanderApprovalState,
  normalizeCommanderPromptState,
  normalizeCommanderSessionState,
  readCommanderApproval,
  readCommanderSession,
  writeCommanderApproval,
  writeCommanderSession,
  summarizeCommanderTrace,
  type CommanderStorageConfig,
  type CommanderApprovalDraftRecord,
  type CommanderSessionRecord,
} from "./state";

export interface RuntimeConfig extends CommanderStorageConfig {
  host: string;
  port: number;
  logFile: string;
  serviceId: string;
  stateRoot: string;
  sessionId: string;
}

type JsonRecord = Record<string, unknown>;

const startedAt = new Date().toISOString();
const recentLogs: JsonRecord[] = [];
const isCheckMode = process.execArgv.includes("--check");

function envString(name: string, fallback: string): string {
  const value = process.env[name];
  return value === undefined || value.length === 0 ? fallback : value;
}

function envNumber(name: string, fallback: number): number {
  const raw = process.env[name];
  if (raw === undefined || raw.trim().length === 0) return fallback;
  const parsed = Number(raw);
  return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
}

function buildConfig(): RuntimeConfig {
  const stateRoot = envString("COMMANDER_STATE_ROOT", "/var/lib/unidesk/commander");
  const logFile = envString("LOG_FILE", "/var/log/unidesk/commander.jsonl");
  return {
    rootDir: stateRoot,
    host: envString("HOST", "0.0.0.0"),
    port: envNumber("PORT", 4261),
    logFile,
    serviceId: "host-codex-commander",
    stateRoot,
    sessionId: envString("COMMANDER_SESSION_ID", "primary"),
  };
}

let config = buildConfig();
let logWriter: ReturnType<typeof createHourlyJsonlWriter> | null = null;

export function setCommanderRuntimeConfig(next: RuntimeConfig): void {
  config = next;
  logWriter = null;
}

function getLogWriter(): ReturnType<typeof createHourlyJsonlWriter> {
  if (logWriter === null) {
    mkdirSync(dirname(config.logFile), { recursive: true });
    mkdirSync(config.stateRoot, { recursive: true });
    logWriter = createHourlyJsonlWriter({
      baseLogFile: config.logFile,
      service: "host-codex-commander",
      maxBytes: logRetentionBytesForService("host-codex-commander"),
    });
    logWriter.prune();
  }
  return logWriter;
}

function log(event: string, detail: JsonRecord = {}): void {
  const record: JsonRecord = { at: new Date().toISOString(), service: config.serviceId, event, ...detail };
  recentLogs.push(record);
  while (recentLogs.length > 200) recentLogs.shift();
  try {
    getLogWriter().appendJson(record, new Date(String(record.at)));
  } catch {
    // Logging must never block the skeleton service.
  }
  console.log(JSON.stringify(record));
}

function jsonResponse(body: unknown, status = 200, headers: Record<string, string> = {}): Response {
  return new Response(JSON.stringify(body), {
    status,
    headers: { "content-type": "application/json; charset=utf-8", ...headers },
  });
}

function errorToJson(error: unknown): JsonRecord {
  if (error instanceof Error) return { name: error.name, message: error.message, stack: error.stack ?? "" };
  return { message: String(error) };
}

function errorResponse(error: unknown, status = 500): Response {
  log("request_failed", { status, error: errorToJson(error) });
  return jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, status);
}

function readJsonBody(body: Request): Promise<unknown> {
  return body.json();
}

function routePath(url: URL): string {
  return url.pathname.replace(/\/+$/u, "") || "/";
}

function commanderInfo(): JsonRecord {
  const paths = commanderStatePaths(config);
  const session = readCommanderSession(config, config.sessionId);
  return {
    ok: true,
    service: config.serviceId,
    serviceId: config.serviceId,
    status: "healthy",
    startedAt,
    config: {
      host: config.host,
      port: config.port,
      stateRoot: config.stateRoot,
      logFile: config.logFile,
      sessionId: config.sessionId,
    },
    state: commanderSessionPreview(session),
    files: {
      stateFile: paths.stateFile,
      approvalFile: paths.approvalFile,
      traceFile: paths.traceFile,
      logFile: paths.logFile,
      stateRootExists: existsSync(config.stateRoot),
    },
  };
}

export function createCommanderRequestHandler(runtimeConfig: RuntimeConfig): (req: Request) => Promise<Response> | Response {
  return async (req: Request) => {
    const previousConfig = config;
    config = runtimeConfig;
    try {
      return await Promise.resolve(handleCommanderRequest(req));
    } finally {
      config = previousConfig;
    }
  };
}

function normalizeSessionUpdate(input: unknown): CommanderSessionRecord {
  const body = typeof input === "object" && input !== null && !Array.isArray(input) ? input as Record<string, unknown> : {};
  const current = readCommanderSession(config, typeof body.sessionId === "string" && body.sessionId.length > 0 ? body.sessionId : config.sessionId);
  current.state = typeof body.state === "string" ? normalizeCommanderSessionState(body.state) : current.state;
  current.promptState = typeof body.promptState === "string" ? normalizeCommanderPromptState(body.promptState) : current.promptState;
  current.approvalState = typeof body.approvalState === "string" ? normalizeCommanderApprovalState(body.approvalState) : current.approvalState;
  current.pid = typeof body.pid === "number" && Number.isFinite(body.pid) ? Math.floor(body.pid) : current.pid;
  current.cwd = typeof body.cwd === "string" ? body.cwd : current.cwd;
  current.lastSeq = typeof body.lastSeq === "number" && Number.isFinite(body.lastSeq) ? Math.max(0, Math.floor(body.lastSeq)) : current.lastSeq;
  current.heartbeatAt = typeof body.heartbeatAt === "string" ? body.heartbeatAt : current.heartbeatAt;
  current.updatedAt = new Date().toISOString();
  if (Array.isArray(body.notes)) {
    current.notes = body.notes.filter((item): item is string => typeof item === "string");
  }
  return writeCommanderSession(config, current);
}

function updateSessionFromTrace(input: unknown): JsonRecord {
  const body = typeof input === "object" && input !== null && !Array.isArray(input) ? input as Record<string, unknown> : {};
  const sessionId = typeof body.sessionId === "string" && body.sessionId.length > 0 ? body.sessionId : config.sessionId;
  const traceJsonl = typeof body.traceJsonl === "string" ? body.traceJsonl : "";
  const taskSummary = typeof body.taskSummary === "string" ? body.taskSummary : null;
  const taskId = typeof body.taskId === "string" ? body.taskId : null;
  const summary = summarizeCommanderTrace({ taskId, sessionId, traceJsonl, taskSummary });
  appendCommanderTraceEvent(config, sessionId, {
    type: "trace-summary-dry-run",
    taskId,
    summary,
  });
  const nextState = summary.status === "terminal"
    ? "stopped"
    : summary.status === "attention_required"
      ? "attention_required"
      : summary.status === "blocked"
        ? "degraded"
        : "running";
  const session = writeCommanderSession(config, {
    ...readCommanderSession(config, sessionId),
    sessionId,
    state: nextState,
    promptState: summary.status === "attention_required" ? "planned" : "draft",
    approvalState: summary.status === "attention_required" ? "requested" : "draft",
    lastSeq: summary.lastSeq,
    heartbeatAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    notes: [...readCommanderSession(config, sessionId).notes, `trace-summary:${summary.status}`].slice(-20),
    pid: readCommanderSession(config, sessionId).pid,
    cwd: readCommanderSession(config, sessionId).cwd,
  });
  return { ok: true, summary, session: commanderSessionPreview(session) };
}

function writeApprovalDraft(input: unknown): JsonRecord {
  const body = typeof input === "object" && input !== null && !Array.isArray(input) ? input as Record<string, unknown> : {};
  const action = typeof body.action === "string" ? body.action : "unknown";
  if (!commanderHighRiskActions.includes(action as typeof commanderHighRiskActions[number])) {
    return { ok: false, error: "validation-failed", message: `unsupported high-risk action: ${action}`, highRiskActions: commanderHighRiskActions };
  }
  const taskId = typeof body.taskId === "string" ? body.taskId : null;
  const reason = typeof body.reason === "string" ? body.reason : "";
  const approvalId = typeof body.approvalId === "string" && body.approvalId.length > 0 ? body.approvalId : "draft";
  const draft = buildCommanderApprovalDraft({
    approvalId,
    action,
    taskId,
    reason,
    sessionId: config.sessionId,
  });
  const stored = writeCommanderApproval(config, draft);
  return { ok: true, approval: commanderApprovalPreview(stored), previewMarkdown: stored.previewMarkdown, previewJson: stored.previewJson };
}

export function handleCommanderRequest(req: Request): Promise<Response> | Response {
  const url = new URL(req.url);
  const path = routePath(url);
  if (req.method === "GET" && (path === "/" || path === "/health")) return jsonResponse(commanderHealth(config, startedAt));
  if (req.method === "GET" && path === "/logs") return jsonResponse({ ok: true, logs: recentLogs.slice(-100), logFile: config.logFile, stateRoot: config.stateRoot, startedAt });
  if (req.method === "GET" && path === "/api/commander/contract") return jsonResponse(commanderContract());
  if (req.method === "GET" && path === "/api/commander/sessions") return jsonResponse({ ok: true, sessions: listCommanderSessions(config).map(commanderSessionPreview) });
  if (req.method === "GET" && path === "/api/commander/state") return jsonResponse({ ok: true, session: commanderSessionPreview(readCommanderSession(config, config.sessionId)), approval: commanderApprovalPreview(readCommanderApproval(config, "draft")) });
  if (req.method === "POST" && path === "/api/commander/state") {
    return readJsonBody(req).then((body) => jsonResponse({ ok: true, session: commanderSessionPreview(normalizeSessionUpdate(body)) }));
  }
  if (req.method === "GET" && path === "/api/commander/trace-summary") {
    const traceJsonl = url.searchParams.get("traceJsonl") ?? "";
    const taskSummary = url.searchParams.get("taskSummary");
    const taskId = url.searchParams.get("taskId");
    return jsonResponse({ ok: true, summary: summarizeCommanderTrace({ taskId, sessionId: config.sessionId, traceJsonl, taskSummary }) });
  }
  if (req.method === "POST" && path === "/api/commander/trace-summary") {
    return readJsonBody(req).then((body) => jsonResponse(updateSessionFromTrace(body)));
  }
  if (req.method === "POST" && path === "/api/commander/approvals") {
    return readJsonBody(req).then((body) => {
      const result = writeApprovalDraft(body);
      return jsonResponse(result, result.ok === false ? 400 : 200);
    });
  }
  return jsonResponse({
    ok: false,
    service: config.serviceId,
    status: "not_found",
    available: ["/health", "/logs", "/api/commander/contract", "/api/commander/sessions", "/api/commander/state", "/api/commander/trace-summary", "/api/commander/approvals"],
  }, 404);
}

if (import.meta.main && !isCheckMode) {
  const server = Bun.serve({
    hostname: config.host,
    port: config.port,
    idleTimeout: 120,
    fetch(req) {
      return Promise.resolve().then(() => handleCommanderRequest(req)).catch((error) => errorResponse(error));
    },
  });

  log("service_started", { startedAt, service: config.serviceId, stateRoot: config.stateRoot, logFile: config.logFile, port: server.port });
}
