import { describe, expect, it, vi } from "vitest";
import { isCodexUnknownSessionError, parseCodexJsonl } from "@paperclipai/adapter-codex-local/server";
import { parseCodexStdoutLine } from "@paperclipai/adapter-codex-local/ui";
import { printCodexStreamEvent } from "@paperclipai/adapter-codex-local/cli";

describe("codex_local parser", () => {
  it("extracts session, summary, usage, and terminal error message", () => {
    const stdout = [
      JSON.stringify({ type: "thread.started", thread_id: "thread-123" }),
      JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: "hello" } }),
      JSON.stringify({ type: "turn.completed", usage: { input_tokens: 10, cached_input_tokens: 2, output_tokens: 4 } }),
      JSON.stringify({ type: "turn.failed", error: { message: "model access denied" } }),
    ].join("\n");

    const parsed = parseCodexJsonl(stdout);
    expect(parsed.sessionId).toBe("thread-123");
    expect(parsed.summary).toBe("hello");
    expect(parsed.usage).toEqual({
      inputTokens: 10,
      cachedInputTokens: 2,
      outputTokens: 4,
    });
    expect(parsed.errorMessage).toBe("model access denied");
  });
});

describe("codex_local stale session detection", () => {
  it("treats missing rollout path as an unknown session error", () => {
    const stderr =
      "2026-02-19T19:58:53.281939Z ERROR codex_core::rollout::list: state db missing rollout path for thread 019c775d-967c-7ef1-acc7-e396dc2c87cc";

    expect(isCodexUnknownSessionError("", stderr)).toBe(true);
  });
});

describe("codex_local ui stdout parser", () => {
  it("parses turn and reasoning lifecycle events", () => {
    const ts = "2026-02-20T00:00:00.000Z";

    expect(parseCodexStdoutLine(JSON.stringify({ type: "turn.started" }), ts)).toEqual([
      { kind: "system", ts, text: "turn started" },
    ]);

    expect(
      parseCodexStdoutLine(
        JSON.stringify({
          type: "item.completed",
          item: { id: "item_1", type: "reasoning", text: "**Preparing to use paperclip skill**" },
        }),
        ts,
      ),
    ).toEqual([
      { kind: "thinking", ts, text: "**Preparing to use paperclip skill**" },
    ]);
  });

  it("parses command execution and file changes", () => {
    const ts = "2026-02-20T00:00:00.000Z";

    expect(
      parseCodexStdoutLine(
        JSON.stringify({
          type: "item.started",
          item: { id: "item_2", type: "command_execution", command: "/bin/zsh -lc ls", status: "in_progress" },
        }),
        ts,
      ),
    ).toEqual([
      {
        kind: "tool_call",
        ts,
        name: "command_execution",
        toolUseId: "item_2",
        input: { id: "item_2", command: "/bin/zsh -lc ls" },
      },
    ]);

    expect(
      parseCodexStdoutLine(
        JSON.stringify({
          type: "item.completed",
          item: {
            id: "item_2",
            type: "command_execution",
            command: "/bin/zsh -lc ls",
            aggregated_output: "agents\n",
            exit_code: 0,
            status: "completed",
          },
        }),
        ts,
      ),
    ).toEqual([
      {
        kind: "tool_result",
        ts,
        toolUseId: "item_2",
        content: "command: /bin/zsh -lc ls\nstatus: completed\nexit_code: 0\n\nagents",
        isError: false,
      },
    ]);

    expect(
      parseCodexStdoutLine(
        JSON.stringify({
          type: "item.completed",
          item: {
            id: "item_52",
            type: "file_change",
            changes: [{ path: "/Users/paperclipuser/project/ui/src/pages/AgentDetail.tsx", kind: "update" }],
            status: "completed",
          },
        }),
        ts,
      ),
    ).toEqual([
      {
        kind: "system",
        ts,
        text: "file changes: update /Users/paperclipuser/project/ui/src/pages/AgentDetail.tsx",
      },
    ]);
  });

  it("parses error items and failed turns", () => {
    const ts = "2026-02-20T00:00:00.000Z";

    expect(
      parseCodexStdoutLine(
        JSON.stringify({
          type: "item.completed",
          item: {
            id: "item_0",
            type: "error",
            message: "This session was recorded with model `gpt-5.2-pro` but is resuming with `gpt-5.2-codex`.",
          },
        }),
        ts,
      ),
    ).toEqual([
      {
        kind: "stderr",
        ts,
        text: "This session was recorded with model `gpt-5.2-pro` but is resuming with `gpt-5.2-codex`.",
      },
    ]);

    expect(
      parseCodexStdoutLine(
        JSON.stringify({
          type: "turn.failed",
          error: { message: "model access denied" },
          usage: { input_tokens: 10, cached_input_tokens: 2, output_tokens: 4 },
        }),
        ts,
      ),
    ).toEqual([
      {
        kind: "result",
        ts,
        text: "",
        inputTokens: 10,
        outputTokens: 4,
        cachedTokens: 2,
        costUsd: 0,
        subtype: "turn.failed",
        isError: true,
        errors: ["model access denied"],
      },
    ]);
  });
});

function stripAnsi(value: string): string {
  return value.replace(/\x1b\[[0-9;]*m/g, "");
}

describe("codex_local cli formatter", () => {
  it("prints lifecycle, command execution, file change, and error events", () => {
    const spy = vi.spyOn(console, "log").mockImplementation(() => {});

    try {
      printCodexStreamEvent(JSON.stringify({ type: "turn.started" }), false);
      printCodexStreamEvent(
        JSON.stringify({
          type: "item.started",
          item: { id: "item_2", type: "command_execution", command: "/bin/zsh -lc ls", status: "in_progress" },
        }),
        false,
      );
      printCodexStreamEvent(
        JSON.stringify({
          type: "item.completed",
          item: {
            id: "item_2",
            type: "command_execution",
            command: "/bin/zsh -lc ls",
            aggregated_output: "agents\n",
            exit_code: 0,
            status: "completed",
          },
        }),
        false,
      );
      printCodexStreamEvent(
        JSON.stringify({
          type: "item.completed",
          item: {
            id: "item_52",
            type: "file_change",
            changes: [{ path: "/home/user/project/ui/src/pages/AgentDetail.tsx", kind: "update" }],
            status: "completed",
          },
        }),
        false,
      );
      printCodexStreamEvent(
        JSON.stringify({
          type: "turn.failed",
          error: { message: "model access denied" },
          usage: { input_tokens: 10, cached_input_tokens: 2, output_tokens: 4 },
        }),
        false,
      );
      printCodexStreamEvent(
        JSON.stringify({
          type: "item.completed",
          item: { type: "error", message: "resume model mismatch" },
        }),
        false,
      );

      const lines = spy.mock.calls
        .map((call) => call.map((v) => String(v)).join(" "))
        .map(stripAnsi);

      expect(lines).toEqual(expect.arrayContaining([
        "turn started",
        "tool_call: command_execution",
        "/bin/zsh -lc ls",
        "tool_result: command_execution command=\"/bin/zsh -lc ls\" status=completed exit_code=0",
        "agents",
        "file_change: update /home/user/project/ui/src/pages/AgentDetail.tsx",
        "turn failed: model access denied",
        "tokens: in=10 out=4 cached=2",
        "error: resume model mismatch",
      ]));
    } finally {
      spy.mockRestore();
    }
  });
});
