import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { buildCommanderApprovalNotificationDraft, commanderApprovalNotificationPathUnavailable } from "./approval-notification";
import type { CommanderApprovalState, CommanderPromptState, CommanderSessionState } from "./contract";
import { redactJsonValue, redactText } from "./redaction";

export interface CommanderStorageConfig {
  rootDir: string;
}

export interface CommanderSessionRecord {
  sessionId: string;
  state: CommanderSessionState;
  promptState: CommanderPromptState;
  approvalState: CommanderApprovalState;
  pid: number | null;
  cwd: string | null;
  lastSeq: number;
  heartbeatAt: string | null;
  updatedAt: string;
  notes: string[];
}

export interface CommanderApprovalDraftRecord {
  id: string;
  action: string;
  taskId: string | null;
  reason: string;
  status: CommanderApprovalState;
  previewMarkdown: string;
  previewJson: Record<string, unknown>;
  redactionsApplied: number;
  createdAt: string;
  updatedAt: string;
}

export interface CommanderApprovalDraftInput {
  approvalId?: string;
  action: string;
  taskId?: string | null;
  reason: string;
  sessionId?: string | null;
}

export interface CommanderTraceInput {
  taskId: string | null;
  sessionId: string;
  traceJsonl: string;
  taskSummary?: string | null;
}

export interface CommanderTraceSummary {
  taskId: string | null;
  sessionId: string;
  lastSeq: number;
  status: "running" | "attention_required" | "blocked" | "terminal" | "unknown";
  keyEvents: string[];
  openQuestions: string[];
  recommendedNextActions: string[];
  redactionsApplied: number;
  sourceCount: number;
  taskSummaryPreview: string | null;
  traceLineCount: number;
}

export interface CommanderHealth {
  ok: true;
  service: "host-codex-commander";
  status: "healthy";
  startedAt: string;
  stateRoot: string;
  stateRootExists: boolean;
  currentStateFile: string;
  currentApprovalFile: string;
  currentTraceFile: string;
  currentLogFile: string;
}

interface PersistedSessionFile {
  sessionId: string;
  state: CommanderSessionState;
  promptState: CommanderPromptState;
  approvalState: CommanderApprovalState;
  pid: number | null;
  cwd: string | null;
  lastSeq: number;
  heartbeatAt: string | null;
  updatedAt: string;
  notes: string[];
}

interface PersistedApprovalFile {
  id: string;
  action: string;
  taskId: string | null;
  reason: string;
  status: CommanderApprovalState;
  previewMarkdown: string;
  previewJson: Record<string, unknown>;
  redactionsApplied: number;
  createdAt: string;
  updatedAt: string;
}

const defaultSessionId = "primary";

function ensureDir(path: string): void {
  mkdirSync(path, { recursive: true });
}

function nowIso(): string {
  return new Date().toISOString();
}

function safeJsonParse(text: string): Record<string, unknown> | null {
  try {
    const parsed = JSON.parse(text) as unknown;
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
    return parsed as Record<string, unknown>;
  } catch {
    return null;
  }
}

function clampText(value: string, maxChars = 1200): string {
  const compact = value.replace(/\s+/gu, " ").trim();
  return compact.length <= maxChars ? compact : `${compact.slice(0, Math.max(0, maxChars - 12))}…<truncated>`;
}

function stateFilePath(rootDir: string, sessionId = defaultSessionId): string {
  return join(rootDir, "sessions", `${sessionId}.json`);
}

function traceFilePath(rootDir: string, sessionId = defaultSessionId): string {
  return join(rootDir, "events", `${sessionId}.jsonl`);
}

function approvalFilePath(rootDir: string, approvalId = "draft"): string {
  return join(rootDir, "approvals", `${approvalId}.json`);
}

function logFilePath(rootDir: string): string {
  return join(rootDir, "logs", "commander.jsonl");
}

export function commanderStatePaths(config: CommanderStorageConfig, sessionId = defaultSessionId, approvalId = "draft"): {
  stateFile: string;
  approvalFile: string;
  traceFile: string;
  logFile: string;
} {
  return {
    stateFile: stateFilePath(config.rootDir, sessionId),
    approvalFile: approvalFilePath(config.rootDir, approvalId),
    traceFile: traceFilePath(config.rootDir, sessionId),
    logFile: logFilePath(config.rootDir),
  };
}

export function readCommanderSession(config: CommanderStorageConfig, sessionId = defaultSessionId): CommanderSessionRecord {
  const path = stateFilePath(config.rootDir, sessionId);
  if (!existsSync(path)) {
    return {
      sessionId,
      state: "unknown",
      promptState: "draft",
      approvalState: "draft",
      pid: null,
      cwd: null,
      lastSeq: 0,
      heartbeatAt: null,
      updatedAt: nowIso(),
      notes: [],
    };
  }
  const parsed = safeJsonParse(readFileSync(path, "utf8"));
  if (parsed === null) throw new Error(`invalid commander session file: ${path}`);
  return normalizeSessionRecord(parsed, sessionId);
}

export function writeCommanderSession(config: CommanderStorageConfig, session: CommanderSessionRecord): CommanderSessionRecord {
  const path = stateFilePath(config.rootDir, session.sessionId);
  ensureDir(dirname(path));
  const normalized = normalizeSessionRecord(session as unknown as Record<string, unknown>, session.sessionId);
  writeFileSync(path, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
  return normalized;
}

export function readCommanderApproval(config: CommanderStorageConfig, approvalId = "draft"): CommanderApprovalDraftRecord {
  const path = approvalFilePath(config.rootDir, approvalId);
  if (!existsSync(path)) {
    return {
      id: approvalId,
      action: "unknown",
      taskId: null,
      reason: "",
      status: "draft",
      previewMarkdown: "",
      previewJson: { ok: true, status: "draft" },
      redactionsApplied: 0,
      createdAt: nowIso(),
      updatedAt: nowIso(),
    };
  }
  const parsed = safeJsonParse(readFileSync(path, "utf8"));
  if (parsed === null) throw new Error(`invalid commander approval file: ${path}`);
  return normalizeApprovalRecord(parsed, approvalId);
}

export function writeCommanderApproval(config: CommanderStorageConfig, approval: CommanderApprovalDraftRecord): CommanderApprovalDraftRecord {
  const path = approvalFilePath(config.rootDir, approval.id);
  ensureDir(dirname(path));
  const normalized = normalizeApprovalRecord(approval as unknown as Record<string, unknown>, approval.id);
  writeFileSync(path, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
  return normalized;
}

export function buildCommanderApprovalDraft(input: CommanderApprovalDraftInput): CommanderApprovalDraftRecord {
  const approvalId = input.approvalId ?? "draft";
  const reason = redactText(input.reason);
  const action = String(input.action || "unknown");
  const taskId = input.taskId ?? null;
  const notificationDraft = buildCommanderApprovalNotificationDraft({ action, taskId, reason: input.reason });
  const notificationPath = commanderApprovalNotificationPathUnavailable(notificationDraft.message);
  const previewMarkdown = [
    "# Commander approval draft",
    "",
    `- approvalId: ${approvalId}`,
    `- action: ${action}`,
    `- taskId: ${taskId ?? "null"}`,
    `- status: draft`,
    `- redactionsApplied: ${reason.redactionsApplied}`,
    input.sessionId ? `- sessionId: ${input.sessionId}` : "- sessionId: null",
    "",
    "## Reason",
    "",
    reason.text || "(empty)",
    "",
    "## ClaudeQQ notification draft",
    "",
    notificationDraft.message,
    "",
    "## Boundary",
    "",
    "- ClaudeQQ send is not implemented in this skeleton.",
    "- If an authorized send is required, use backend-core /api/microservices/claudeqq/proxy only.",
    "- Approval is preview-only until explicit user approval is recorded.",
  ].join("\n");
  return {
    id: approvalId,
    action,
    taskId,
    reason: reason.text,
    status: "draft",
    previewMarkdown,
    previewJson: redactJsonValue({
      ok: true,
      service: "host-codex-commander",
      approvalId,
      action,
      taskId,
      reason: reason.text,
      redactionsApplied: reason.redactionsApplied,
      requiresExplicitUserApproval: true,
      sendImplemented: false,
      notificationDraft,
      claudeqq: {
        mutation: false,
        target: "configured primary user private chat",
        endpointShape: `POST ${notificationPath.servicePath}`,
        fallbackEndpointShape: `POST ${notificationPath.fallbackServicePath}`,
        messageTemplate: notificationDraft.message,
        sendImplemented: false,
        dryRunNoClaudeQqSend: true,
        notificationPath,
      },
      blockedUntilApproved: [action],
    }) as Record<string, unknown>,
    redactionsApplied: reason.redactionsApplied,
    createdAt: nowIso(),
    updatedAt: nowIso(),
  };
}

export function commanderSessionPreview(session: CommanderSessionRecord): Record<string, unknown> {
  return redactJsonValue({
    sessionId: session.sessionId,
    state: session.state,
    promptState: session.promptState,
    approvalState: session.approvalState,
    pid: session.pid,
    cwd: session.cwd,
    lastSeq: session.lastSeq,
    heartbeatAt: session.heartbeatAt,
    updatedAt: session.updatedAt,
    notes: session.notes,
  }) as Record<string, unknown>;
}

export function commanderApprovalPreview(approval: CommanderApprovalDraftRecord): Record<string, unknown> {
  return redactJsonValue({
    id: approval.id,
    action: approval.action,
    taskId: approval.taskId,
    reason: approval.reason,
    status: approval.status,
    redactionsApplied: approval.redactionsApplied,
    createdAt: approval.createdAt,
    updatedAt: approval.updatedAt,
  }) as Record<string, unknown>;
}

export function listCommanderSessions(config: CommanderStorageConfig): CommanderSessionRecord[] {
  const dir = join(config.rootDir, "sessions");
  if (!existsSync(dir)) return [readCommanderSession(config)];
  const sessions = readdirSync(dir)
    .filter((name) => name.endsWith(".json"))
    .map((name) => name.slice(0, -".json".length))
    .map((sessionId) => {
      try {
        return readCommanderSession(config, sessionId);
      } catch {
        return null;
      }
    })
    .filter((item): item is CommanderSessionRecord => item !== null);
  if (sessions.length === 0) sessions.push(readCommanderSession(config));
  return sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
}

export function appendCommanderTraceEvent(config: CommanderStorageConfig, sessionId: string, event: Record<string, unknown>): string {
  const path = traceFilePath(config.rootDir, sessionId);
  ensureDir(dirname(path));
  const redactedEvent = redactJsonValue({ ...event, at: nowIso(), sessionId });
  writeFileSync(path, `${JSON.stringify(redactedEvent)}\n`, { flag: "a" });
  return path;
}

export function summarizeCommanderTrace(input: CommanderTraceInput): CommanderTraceSummary {
  const lines = input.traceJsonl.split(/\r?\n/gu).map((line) => line.trim()).filter(Boolean);
  const keyEvents: string[] = [];
  const openQuestions: string[] = [];
  const recommendedNextActions: string[] = [];
  let lastSeq = 0;
  let status: CommanderTraceSummary["status"] = "unknown";
  let redactionsApplied = 0;
  for (const line of lines) {
    const parsed = safeJsonParse(line);
    if (parsed === null) continue;
    const seq = Number(parsed.seq ?? parsed.sequence ?? parsed.eventSequence ?? 0);
    if (Number.isFinite(seq)) lastSeq = Math.max(lastSeq, Math.floor(seq));
    const rawText = String(parsed.summary ?? parsed.text ?? parsed.message ?? parsed.command ?? parsed.output ?? "");
    const redacted = redactText(rawText);
    redactionsApplied += redacted.redactionsApplied;
    const compact = clampText(redacted.text, 180);
    const kind = String(parsed.kind ?? parsed.channel ?? parsed.type ?? "event");
    const statusText = String(parsed.status ?? "").toLowerCase();
    if (statusText.includes("failed") || statusText.includes("error")) status = "blocked";
    else if (statusText.includes("completed") || statusText.includes("succeeded")) status = "terminal";
    else if (statusText.includes("blocked") || statusText.includes("attention")) status = "attention_required";
    else if (status === "unknown") status = "running";
    if (compact.length > 0 && keyEvents.length < 8) keyEvents.push(`${kind}: ${compact}`);
  }
  const taskSummary = input.taskSummary ? redactText(input.taskSummary) : null;
  if (taskSummary !== null) redactionsApplied += taskSummary.redactionsApplied;
  const summaryPreview = taskSummary === null ? null : clampText(taskSummary.text, 300);
  if (status === "unknown" && lines.length > 0) status = "running";
  if (status === "running") {
    openQuestions.push("Confirm whether current host Codex session still needs direct control.");
    recommendedNextActions.push("Review the last session events and keep the state file current.");
  } else if (status === "attention_required") {
    openQuestions.push("Identify the action that needs approval or operator intervention.");
    recommendedNextActions.push("Draft approval text before any live command path is considered.");
  } else if (status === "blocked") {
    openQuestions.push("Identify the blocking error or missing prerequisite.");
    recommendedNextActions.push("Capture the failure summary and keep the action non-live.");
  } else if (status === "terminal") {
    recommendedNextActions.push("Persist the terminal summary and mark the session read.");
  } else {
    recommendedNextActions.push("Collect more trace lines before making a state claim.");
  }
  return {
    taskId: input.taskId,
    sessionId: input.sessionId,
    lastSeq,
    status,
    keyEvents,
    openQuestions,
    recommendedNextActions,
    redactionsApplied,
    sourceCount: lines.length,
    taskSummaryPreview: summaryPreview,
    traceLineCount: lines.length,
  };
}

export function commanderHealth(config: CommanderStorageConfig, startedAt: string): CommanderHealth {
  const paths = commanderStatePaths(config);
  return {
    ok: true,
    service: "host-codex-commander",
    status: "healthy",
    startedAt,
    stateRoot: config.rootDir,
    stateRootExists: existsSync(config.rootDir),
    currentStateFile: paths.stateFile,
    currentApprovalFile: paths.approvalFile,
    currentTraceFile: paths.traceFile,
    currentLogFile: paths.logFile,
  };
}

function normalizeSessionRecord(record: Record<string, unknown>, sessionId: string): CommanderSessionRecord {
  const notes = Array.isArray(record.notes) ? record.notes.filter((item): item is string => typeof item === "string").map((item) => redactText(item).text) : [];
  return {
    sessionId: String(record.sessionId ?? sessionId),
    state: normalizeSessionState(String(record.state ?? "unknown")),
    promptState: normalizePromptState(String(record.promptState ?? "draft")),
    approvalState: normalizeApprovalState(String(record.approvalState ?? "draft")),
    pid: typeof record.pid === "number" && Number.isFinite(record.pid) ? Math.floor(record.pid) : null,
    cwd: typeof record.cwd === "string" && record.cwd.length > 0 ? record.cwd : null,
    lastSeq: Number.isFinite(Number(record.lastSeq ?? 0)) ? Math.max(0, Math.floor(Number(record.lastSeq ?? 0))) : 0,
    heartbeatAt: typeof record.heartbeatAt === "string" && record.heartbeatAt.length > 0 ? record.heartbeatAt : null,
    updatedAt: typeof record.updatedAt === "string" && record.updatedAt.length > 0 ? record.updatedAt : nowIso(),
    notes,
  };
}

function normalizeApprovalRecord(record: Record<string, unknown>, approvalId: string): CommanderApprovalDraftRecord {
  const previewMarkdown = typeof record.previewMarkdown === "string" ? redactText(record.previewMarkdown).text : "";
  const previewJson = typeof record.previewJson === "object" && record.previewJson !== null && !Array.isArray(record.previewJson)
    ? redactJsonValue(record.previewJson) as Record<string, unknown>
    : { ok: true, status: "draft" };
  return {
    id: String(record.id ?? approvalId),
    action: typeof record.action === "string" ? record.action : "unknown",
    taskId: typeof record.taskId === "string" ? record.taskId : null,
    reason: typeof record.reason === "string" ? redactText(record.reason).text : "",
    status: normalizeApprovalState(String(record.status ?? "draft")),
    previewMarkdown,
    previewJson,
    redactionsApplied: Number.isFinite(Number(record.redactionsApplied ?? 0)) ? Math.max(0, Math.floor(Number(record.redactionsApplied ?? 0))) : 0,
    createdAt: typeof record.createdAt === "string" && record.createdAt.length > 0 ? record.createdAt : nowIso(),
    updatedAt: typeof record.updatedAt === "string" && record.updatedAt.length > 0 ? record.updatedAt : nowIso(),
  };
}

function normalizeSessionState(value: string): CommanderSessionState {
  return ["unknown", "discovered", "planned", "starting", "running", "attention_required", "stopping", "stopped", "degraded"].includes(value)
    ? value as CommanderSessionState
    : "unknown";
}

function normalizePromptState(value: string): CommanderPromptState {
  return ["draft", "planned", "queued_for_injection", "injected", "rejected", "failed"].includes(value)
    ? value as CommanderPromptState
    : "draft";
}

function normalizeApprovalState(value: string): CommanderApprovalState {
  return ["draft", "requested", "approved", "rejected", "expired", "consumed"].includes(value)
    ? value as CommanderApprovalState
    : "draft";
}

export function normalizeCommanderSessionState(value: string): CommanderSessionState {
  return normalizeSessionState(value);
}

export function normalizeCommanderPromptState(value: string): CommanderPromptState {
  return normalizePromptState(value);
}

export function normalizeCommanderApprovalState(value: string): CommanderApprovalState {
  return normalizeApprovalState(value);
}
