import { Hono } from "hono";
import { describe, it, expect, vi } from "vitest";
import type { SessionStore } from "../session-store.js";
import type { PersistedSession } from "../session-store.js";
import type { BrowserIncomingMessage, SessionState } from "../session-types.js";

/* ── Helpers ──────────────────────────────────────────────────────── */

function makeSessionState(overrides: Partial<SessionState> = {}): SessionState {
  return {
    session_id: "sess-1",
    model: "claude-4",
    cwd: "/tmp",
    tools: [],
    permissionMode: "default",
    claude_code_version: "1.0.0",
    mcp_servers: [],
    agents: [],
    slash_commands: [],
    skills: [],
    total_cost_usd: 0,
    num_turns: 0,
    context_used_percent: 0,
    input_tokens: 0,
    output_tokens: 0,
    cache_read_tokens: 0,
    cache_creation_tokens: 0,
    context_window: 200000,
    is_compacting: false,
    git_branch: "main",
    is_worktree: false,
    is_containerized: false,
    repo_root: "/tmp",
    git_ahead: 0,
    git_behind: 0,
    total_lines_added: 0,
    total_lines_removed: 0,
    ...overrides,
  };
}

function makeSession(overrides: Partial<PersistedSession> = {}): PersistedSession {
  return {
    id: "sess-1",
    state: makeSessionState({ session_id: overrides.id ?? "sess-1" }),
    messageHistory: [],
    pendingMessages: [],
    pendingPermissions: [],
    ...overrides,
  };
}

function makeUserMessage(
  content: string,
  timestamp: number,
  id?: string,
): BrowserIncomingMessage {
  return { type: "user_message", content, timestamp, id } as BrowserIncomingMessage;
}

function makeAssistantMessage(): BrowserIncomingMessage {
  return {
    type: "assistant",
    message: {
      id: "msg-1",
      type: "message",
      role: "assistant",
      model: "claude-4",
      content: [{ type: "text", text: "Hello" }],
      stop_reason: "end_turn",
      usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
    },
    parent_tool_use_id: null,
  } as BrowserIncomingMessage;
}

/* ── Mock SessionStore ────────────────────────────────────────────── */

function createMockStore(sessions: PersistedSession[] = []): SessionStore {
  return {
    loadAll: vi.fn().mockResolvedValue(sessions),
    load: vi.fn((id: string) => sessions.find((s) => s.id === id) ?? null),
  } as unknown as SessionStore;
}

/* ── App setup ────────────────────────────────────────────────────── */

import { registerHistoryRoutes } from "./history-routes.js";

function req(app: Hono, method: string, path: string) {
  return app.request(path, { method });
}

/* ── Tests ────────────────────────────────────────────────────────── */

describe("GET /history/prompts", () => {
  it("returns empty list when no sessions exist", async () => {
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([]));

    const res = await req(app, "GET", "/history/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toEqual([]);
    expect(body.total).toBe(0);
  });

  it("returns user messages from sessions", async () => {
    const session = makeSession({
      id: "sess-1",
      messageHistory: [
        makeUserMessage("Hello world", 1000, "msg-1"),
        makeAssistantMessage(),
        makeUserMessage("How are you?", 2000, "msg-2"),
      ],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(2);
    expect(body.total).toBe(2);
    // Most recent first
    expect(body.prompts[0].content).toBe("How are you?");
    expect(body.prompts[1].content).toBe("Hello world");
  });

  it("filters by search (case-insensitive)", async () => {
    const session = makeSession({
      messageHistory: [
        makeUserMessage("Fix the bug in auth", 1000, "m1"),
        makeUserMessage("Write unit tests", 2000, "m2"),
        makeUserMessage("Deploy to production", 3000, "m3"),
      ],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/prompts?search=UNIT");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(1);
    expect(body.prompts[0].content).toBe("Write unit tests");
  });

  it("filters by sessionId", async () => {
    const session1 = makeSession({
      id: "sess-1",
      messageHistory: [makeUserMessage("From session 1", 1000, "m1")],
    });
    const session2 = makeSession({
      id: "sess-2",
      state: makeSessionState({ session_id: "sess-2" }),
      messageHistory: [makeUserMessage("From session 2", 2000, "m2")],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session1, session2]));

    const res = await req(app, "GET", "/history/prompts?sessionId=sess-2");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(1);
    expect(body.prompts[0].content).toBe("From session 2");
  });

  it("applies pagination with limit and offset", async () => {
    const messages = Array.from({ length: 5 }, (_, i) =>
      makeUserMessage(`Message ${i}`, (i + 1) * 1000, `m${i}`),
    );
    const session = makeSession({ messageHistory: messages });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/prompts?limit=2&offset=1");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(2);
    expect(body.total).toBe(5);
    expect(body.limit).toBe(2);
    expect(body.offset).toBe(1);
    // Sorted descending: Message 4, 3, 2, 1, 0 → offset 1 → Message 3, 2
    expect(body.prompts[0].content).toBe("Message 3");
    expect(body.prompts[1].content).toBe("Message 2");
  });

  it("excludes archived sessions by default", async () => {
    const active = makeSession({
      id: "active",
      messageHistory: [makeUserMessage("Active prompt", 2000, "m1")],
    });
    const archived = makeSession({
      id: "archived",
      archived: true,
      state: makeSessionState({ session_id: "archived" }),
      messageHistory: [makeUserMessage("Archived prompt", 1000, "m2")],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([active, archived]));

    const res = await req(app, "GET", "/history/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(1);
    expect(body.prompts[0].content).toBe("Active prompt");
  });

  it("includes archived sessions when includeArchived=true", async () => {
    const active = makeSession({
      id: "active",
      messageHistory: [makeUserMessage("Active prompt", 2000, "m1")],
    });
    const archived = makeSession({
      id: "archived",
      archived: true,
      state: makeSessionState({ session_id: "archived" }),
      messageHistory: [makeUserMessage("Archived prompt", 1000, "m2")],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([active, archived]));

    const res = await req(app, "GET", "/history/prompts?includeArchived=true");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(2);
  });

  it("skips empty user messages", async () => {
    const session = makeSession({
      messageHistory: [
        makeUserMessage("", 1000, "m1"),
        makeUserMessage("   ", 2000, "m2"),
        makeUserMessage("Real message", 3000, "m3"),
      ],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(1);
    expect(body.prompts[0].content).toBe("Real message");
  });

  it("returns 400 for invalid query parameters", async () => {
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([]));

    const res = await req(app, "GET", "/history/prompts?limit=-5");
    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.error).toBe("Invalid query parameters");
  });

  it("returns 500 when store throws", async () => {
    const store = createMockStore();
    (store.loadAll as ReturnType<typeof vi.fn>).mockRejectedValue(new Error("disk error"));
    const app = new Hono();
    registerHistoryRoutes(app, store);

    const res = await req(app, "GET", "/history/prompts");
    expect(res.status).toBe(500);
    const body = await res.json();
    expect(body.error).toBe("Failed to fetch prompt history");
  });

  it("generates fallback id when message has no id", async () => {
    const session = makeSession({
      id: "sess-1",
      messageHistory: [
        makeUserMessage("No id message", 1234),
      ],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts[0].id).toBe("sess-1-1234");
  });
});

describe("GET /history/sessions/:sessionId/prompts", () => {
  it("returns prompts for a specific session", async () => {
    const session = makeSession({
      id: "sess-1",
      messageHistory: [
        makeUserMessage("First", 1000, "m1"),
        makeUserMessage("Second", 2000, "m2"),
      ],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/sessions/sess-1/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toHaveLength(2);
    expect(body.total).toBe(2);
    // Chronological order (oldest first)
    expect(body.prompts[0].content).toBe("First");
    expect(body.prompts[1].content).toBe("Second");
  });

  it("returns 404 for non-existent session", async () => {
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([]));

    const res = await req(app, "GET", "/history/sessions/nonexistent/prompts");
    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.error).toBe("Session not found");
  });

  it("returns empty list when session has no user messages", async () => {
    const session = makeSession({
      id: "sess-1",
      messageHistory: [makeAssistantMessage()],
    });
    const app = new Hono();
    registerHistoryRoutes(app, createMockStore([session]));

    const res = await req(app, "GET", "/history/sessions/sess-1/prompts");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.prompts).toEqual([]);
    expect(body.total).toBe(0);
  });

  it("returns 500 when store.load throws", async () => {
    const store = createMockStore();
    (store.load as ReturnType<typeof vi.fn>).mockImplementation(() => {
      throw new Error("read error");
    });
    const app = new Hono();
    registerHistoryRoutes(app, store);

    const res = await req(app, "GET", "/history/sessions/sess-1/prompts");
    expect(res.status).toBe(500);
    const body = await res.json();
    expect(body.error).toBe("Failed to fetch session prompts");
  });
});
