import { Hono } from "hono";
import { describe, it, expect, vi, beforeEach } from "vitest";

/* ── Mocks ────────────────────────────────────────────────────────── */

const mockPromptManager = vi.hoisted(() => ({
  listPrompts: vi.fn().mockReturnValue([]),
  getPrompt: vi.fn().mockReturnValue(null),
  createPrompt: vi.fn(),
  updatePrompt: vi.fn().mockReturnValue(null),
  deletePrompt: vi.fn().mockReturnValue(false),
}));

vi.mock("../prompt-manager.js", () => mockPromptManager);

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

import { registerPromptRoutes } from "./prompt-routes.js";

let app: Hono;

beforeEach(() => {
  vi.restoreAllMocks();
  mockPromptManager.listPrompts.mockReturnValue([]);
  mockPromptManager.getPrompt.mockReturnValue(null);
  mockPromptManager.createPrompt.mockReset();
  mockPromptManager.updatePrompt.mockReturnValue(null);
  mockPromptManager.deletePrompt.mockReturnValue(false);
  app = new Hono();
  registerPromptRoutes(app);
});

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

function req(method: string, path: string, body?: unknown) {
  const init: RequestInit = { method, headers: {} };
  if (body) {
    (init.headers as Record<string, string>)["content-type"] = "application/json";
    init.body = JSON.stringify(body);
  }
  return app.request(path, init);
}

function makePrompt(overrides: Record<string, unknown> = {}) {
  return {
    id: "prompt-1",
    name: "Test Prompt",
    content: "Hello world",
    scope: "global",
    createdAt: Date.now(),
    updatedAt: Date.now(),
    ...overrides,
  };
}

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

describe("GET /prompts", () => {
  it("returns empty list", async () => {
    const res = await req("GET", "/prompts");
    expect(res.status).toBe(200);
    expect(await res.json()).toEqual([]);
  });

  it("returns prompts list", async () => {
    const prompt = makePrompt();
    mockPromptManager.listPrompts.mockReturnValue([prompt]);
    const res = await req("GET", "/prompts");
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data).toHaveLength(1);
    expect(data[0].id).toBe("prompt-1");
  });

  it("passes cwd and scope query params", async () => {
    await req("GET", "/prompts?cwd=/tmp&scope=project");
    expect(mockPromptManager.listPrompts).toHaveBeenCalledWith({
      cwd: "/tmp",
      scope: "project",
    });
  });

  it("normalizes invalid scope to undefined", async () => {
    await req("GET", "/prompts?scope=invalid");
    expect(mockPromptManager.listPrompts).toHaveBeenCalledWith({
      cwd: undefined,
      scope: undefined,
    });
  });

  it("returns 500 on internal error", async () => {
    mockPromptManager.listPrompts.mockImplementation(() => {
      throw new Error("boom");
    });
    const res = await req("GET", "/prompts");
    expect(res.status).toBe(500);
    const body = await res.json();
    expect(body.error).toBe("boom");
  });
});

describe("GET /prompts/:id", () => {
  it("returns 404 for non-existent prompt", async () => {
    const res = await req("GET", "/prompts/nonexistent");
    expect(res.status).toBe(404);
  });

  it("returns prompt", async () => {
    const prompt = makePrompt();
    mockPromptManager.getPrompt.mockReturnValue(prompt);
    const res = await req("GET", "/prompts/prompt-1");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.id).toBe("prompt-1");
  });
});

describe("POST /prompts", () => {
  it("creates a prompt and returns 201", async () => {
    const created = makePrompt({ id: "new-prompt" });
    mockPromptManager.createPrompt.mockReturnValue(created);
    const res = await req("POST", "/prompts", {
      title: "New Prompt",
      content: "Some content",
    });
    expect(res.status).toBe(201);
    const body = await res.json();
    expect(body.id).toBe("new-prompt");
    expect(mockPromptManager.createPrompt).toHaveBeenCalledWith(
      "New Prompt",
      "Some content",
      "global",
      undefined,
    );
  });

  it("uses name field when title is absent", async () => {
    mockPromptManager.createPrompt.mockReturnValue(makePrompt());
    await req("POST", "/prompts", { name: "By Name" });
    expect(mockPromptManager.createPrompt).toHaveBeenCalledWith(
      "By Name",
      "",
      "global",
      undefined,
    );
  });

  it("returns 400 on store error", async () => {
    mockPromptManager.createPrompt.mockImplementation(() => {
      throw new Error("Name is required");
    });
    const res = await req("POST", "/prompts", { title: "" });
    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.error).toContain("Name is required");
  });
});

describe("PUT /prompts/:id", () => {
  it("returns 404 for non-existent prompt", async () => {
    const res = await req("PUT", "/prompts/missing", { title: "Updated" });
    expect(res.status).toBe(404);
  });

  it("updates and returns prompt", async () => {
    const updated = makePrompt({ name: "Updated" });
    mockPromptManager.updatePrompt.mockReturnValue(updated);
    const res = await req("PUT", "/prompts/prompt-1", { title: "Updated" });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.name).toBe("Updated");
  });

  it("returns 400 on store error", async () => {
    mockPromptManager.updatePrompt.mockImplementation(() => {
      throw new Error("Invalid update");
    });
    const res = await req("PUT", "/prompts/prompt-1", { title: "Bad" });
    expect(res.status).toBe(400);
  });
});

describe("DELETE /prompts/:id", () => {
  it("returns 404 for non-existent prompt", async () => {
    const res = await req("DELETE", "/prompts/missing");
    expect(res.status).toBe(404);
  });

  it("deletes prompt and returns ok", async () => {
    mockPromptManager.deletePrompt.mockReturnValue(true);
    const res = await req("DELETE", "/prompts/prompt-1");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.ok).toBe(true);
  });
});
