import { spawn } from "child_process";
import { promises as fs } from "fs";
import path from "path";

export type CodexSandbox = "read-only" | "workspace-write";

export type CodexExecJsonLine = {
  type: string;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [key: string]: any;
};

const getCodexBin = () => (process.platform === "win32" ? "codex.cmd" : "codex");

const isNonEmptyString = (v: unknown): v is string => typeof v === "string" && v.trim().length > 0;

const safeKill = (child: ReturnType<typeof spawn> | null | undefined) => {
  if (!child) return;
  try {
    if (child.exitCode !== null) return;
    child.kill();
  } catch {
    // ignore
  }
};

/**
 * 从 startDir 向上查找 workspace 根目录。
 * - 优先以 `AGENTS.md` 作为锚点（本仓库协作约定）
 * - 若找不到，则退化为 `startDir`
 */
export const findWorkspaceRoot = async (startDir: string): Promise<string> => {
  let dir = path.resolve(startDir || process.cwd());
  for (let i = 0; i < 12; i += 1) {
    const agents = path.join(dir, "AGENTS.md");
    try {
      const st = await fs.stat(agents);
      if (st.isFile()) return dir;
    } catch {
      // ignore
    }
    const parent = path.dirname(dir);
    if (parent === dir) break;
    dir = parent;
  }
  return path.resolve(startDir || process.cwd());
};

const buildCodexPromptFromMessages = (
  messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
): string => {
  const lines: string[] = [];
  for (const m of messages) {
    const role = m.role === "system" ? "系统" : m.role === "user" ? "用户" : "助手";
    lines.push(`【${role}】`);
    lines.push(String(m.content ?? "").trimEnd());
    lines.push("");
  }
  return lines.join("\n").trim();
};

export const codexExecToLastMessage = async ({
  cwd,
  sandbox,
  prompt,
  model,
}: {
  cwd: string;
  sandbox: CodexSandbox;
  prompt: string;
  model?: string | null;
}): Promise<{ text: string; rawLines: CodexExecJsonLine[] }> => {
  const bin = getCodexBin();
  // 用 stdin 传入 prompt，避免 Windows 命令行长度限制
  const args = ["exec", "--json", "-C", cwd, "-s", sandbox, "--color", "never"];
  if (isNonEmptyString(model)) args.push("-m", model.trim());
  args.push("-");

  const rawLines: CodexExecJsonLine[] = [];
  let lastAgentText = "";

  await new Promise<void>((resolve, reject) => {
    const child = spawn(bin, args, {
      windowsHide: true,
      env: process.env,
    });

    try {
      child.stdin?.setDefaultEncoding("utf8");
      child.stdin?.write(String(prompt ?? ""));
      child.stdin?.end();
    } catch {
      // ignore
    }

    let buf = "";
    const onData = (chunk: Buffer) => {
      buf += chunk.toString("utf8");
      while (true) {
        const nl = buf.indexOf("\n");
        if (nl === -1) break;
        const line = buf.slice(0, nl).trim();
        buf = buf.slice(nl + 1);
        if (!line) continue;
        try {
          const j = JSON.parse(line) as CodexExecJsonLine;
          rawLines.push(j);
          if (j.type === "item.completed" && j.item && j.item.type === "agent_message") {
            lastAgentText = String(j.item.text ?? "");
          }
        } catch {
          // ignore：非 JSON 行
        }
      }
    };

    child.stdout?.on("data", onData);
    child.stderr?.on("data", () => {
      // ignore：codex --json 主要走 stdout
    });
    child.on("error", reject);
    child.on("close", (code) => {
      if (buf.trim()) {
        try {
          const j = JSON.parse(buf.trim()) as CodexExecJsonLine;
          rawLines.push(j);
          if (j.type === "item.completed" && j.item && j.item.type === "agent_message") {
            lastAgentText = String(j.item.text ?? "");
          }
        } catch {
          // ignore
        }
      }
      if (code && code !== 0) {
        reject(new Error(`Codex 执行失败：exit=${code}`));
        return;
      }
      resolve();
    });
  });

  return { text: lastAgentText.trim() ? lastAgentText.trim() : "（无输出）", rawLines };
};

export const codexMessagesToPrompt = buildCodexPromptFromMessages;

export const startCodexJsonRun = ({
  cwd,
  sandbox,
  prompt,
  model,
  sessionId,
  onJsonLine,
}: {
  cwd: string;
  sandbox: CodexSandbox;
  prompt: string;
  model?: string | null;
  sessionId?: string | null;
  onJsonLine?: (line: CodexExecJsonLine) => void;
}) => {
  const bin = getCodexBin();

  const args = (() => {
    // 新会话：可指定 -C / -s
    if (!isNonEmptyString(sessionId)) {
      const a = ["exec", "--json", "-C", cwd, "-s", sandbox, "--color", "never"];
      if (isNonEmptyString(model)) a.push("-m", model.trim());
      a.push("-"); // stdin prompt
      return a;
    }

    // 续聊：用 sessionId 恢复；resume 子命令不支持 -C/-s（沿用会话配置）
    const a = ["exec", "resume", sessionId.trim(), "--json"];
    if (isNonEmptyString(model)) a.push("-m", model.trim());
    a.push("-"); // stdin prompt
    return a;
  })();

  const child = spawn(bin, args, {
    cwd,
    windowsHide: true,
    env: process.env,
  });

  try {
    child.stdin?.setDefaultEncoding("utf8");
    child.stdin?.write(String(prompt ?? ""));
    child.stdin?.end();
  } catch {
    // ignore
  }

  let threadId = "";
  let lastAgentText = "";

  let buf = "";
  const parseLine = (line: string) => {
    const s = String(line ?? "").trim();
    if (!s) return;
    try {
      const j = JSON.parse(s) as CodexExecJsonLine;
      onJsonLine?.(j);
      if (j.type === "thread.started" && isNonEmptyString(j.thread_id)) {
        threadId = j.thread_id.trim();
      }
      if (j.type === "item.completed" && j.item && j.item.type === "agent_message") {
        lastAgentText = String(j.item.text ?? "");
      }
    } catch {
      // ignore
    }
  };

  child.stdout?.on("data", (chunk: Buffer) => {
    buf += chunk.toString("utf8");
    while (true) {
      const nl = buf.indexOf("\n");
      if (nl === -1) break;
      const line = buf.slice(0, nl);
      buf = buf.slice(nl + 1);
      parseLine(line);
    }
  });

  child.stderr?.on("data", () => {
    // ignore：--json 主要走 stdout
  });

  const done = new Promise<{ ok: true; threadId: string; text: string } | { ok: false; error: string; threadId: string; text: string }>((resolve) => {
    child.on("close", (code) => {
      if (buf.trim()) parseLine(buf);
      const text = lastAgentText.trim() ? lastAgentText.trim() : "（无输出）";
      if (code && code !== 0) {
        resolve({ ok: false, error: `Codex 执行失败：exit=${code}`, threadId, text });
        return;
      }
      resolve({ ok: true, threadId, text });
    });
    child.on("error", (e) => {
      resolve({ ok: false, error: e instanceof Error ? e.message : String(e), threadId, text: lastAgentText.trim() || "（无输出）" });
    });
  });

  return { child, done, kill: () => safeKill(child) };
};
