import { describe, it, expect, vi, beforeEach } from "vitest";
import { GET } from "@/app/api/token/conversations/[id]/export/route";
import { NextRequest } from "next/server";
import { db } from "@/lib/db";

// Mock database
vi.mock("@/lib/db", () => ({
  db: {
    tokenConversation: {
      findFirst: vi.fn(),
    },
  },
}));

// Mock auth
vi.mock("@/lib/auth", () => ({
  auth: vi.fn(),
}));

// Mock logger
vi.mock("@/lib/logger", () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

import { auth } from "@/lib/auth";

const mockConversation = {
  id: "conv_1",
  title: "Fleet optimization chat",
  createdAt: new Date("2026-02-20T10:00:00Z"),
  updatedAt: new Date("2026-02-21T15:30:00Z"),
  messages: [
    {
      id: "msg_1",
      role: "user",
      content: "What models should I run?",
      model: null,
      costUsd: 0,
      latencyMs: 0,
      inputTokens: 12,
      outputTokens: 0,
      messageType: "chat",
      createdAt: new Date("2026-02-20T10:00:00Z"),
    },
    {
      id: "msg_2",
      role: "assistant",
      content: "Based on your 8GB VRAM, I recommend:\n- llama3.1:8b for general use\n- qwen2.5-coder:7b for coding",
      model: "google/gemini-2.5-flash",
      costUsd: 0.0012,
      latencyMs: 1500,
      inputTokens: 50,
      outputTokens: 120,
      messageType: "chat",
      createdAt: new Date("2026-02-20T10:00:02Z"),
    },
  ],
};

function makeParams(id: string) {
  return { params: Promise.resolve({ id }) };
}

describe("GET /api/token/conversations/:id/export", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("should return 401 if no session", async () => {
    vi.mocked(auth).mockResolvedValue(null as any);

    const req = new NextRequest(
      "http://localhost:3000/api/token/conversations/conv_1/export?format=json",
    );

    const response = await GET(req, makeParams("conv_1"));
    const data = await response.json();

    expect(response.status).toBe(401);
    expect(data.error).toBe("Unauthorized");
    expect(db.tokenConversation.findFirst).not.toHaveBeenCalled();
  });

  it("should return 404 for non-existent conversation", async () => {
    vi.mocked(auth).mockResolvedValue({
      user: { id: "user_1", email: "test@test.com" },
    } as any);
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(null);

    const req = new NextRequest(
      "http://localhost:3000/api/token/conversations/nonexistent/export?format=json",
    );

    const response = await GET(req, makeParams("nonexistent"));
    const data = await response.json();

    expect(response.status).toBe(404);
    expect(data.error).toContain("not found");
  });

  it("should return 404 when conversation belongs to a different user (ownership check)", async () => {
    vi.mocked(auth).mockResolvedValue({
      user: { id: "user_1", email: "test@test.com" },
    } as any);
    // findFirst filters by userId — returns null for wrong owner
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(null);

    const req = new NextRequest(
      "http://localhost:3000/api/token/conversations/conv_other/export?format=json",
    );

    const response = await GET(req, makeParams("conv_other"));
    const data = await response.json();

    expect(response.status).toBe(404);
    expect(data.error).toContain("not found");
    expect(db.tokenConversation.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "conv_other", userId: "user_1" },
      }),
    );
  });

  it("should return 400 for invalid format parameter", async () => {
    vi.mocked(auth).mockResolvedValue({
      user: { id: "user_1", email: "test@test.com" },
    } as any);

    const req = new NextRequest(
      "http://localhost:3000/api/token/conversations/conv_1/export?format=xml",
    );

    const response = await GET(req, makeParams("conv_1"));
    const data = await response.json();

    expect(response.status).toBe(400);
    expect(data.error).toContain("Invalid format");
  });

  describe("JSON export", () => {
    it("should return valid JSON with conversation metadata and messages", async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: "user_1", email: "test@test.com" },
      } as any);
      vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(mockConversation as any);

      const req = new NextRequest(
        "http://localhost:3000/api/token/conversations/conv_1/export?format=json",
      );

      const response = await GET(req, makeParams("conv_1"));

      expect(response.status).toBe(200);
      expect(response.headers.get("Content-Type")).toBe("application/json; charset=utf-8");
      expect(response.headers.get("Content-Disposition")).toMatch(/^attachment; filename="token-.*\.json"$/);

      const body = await response.text();
      const data = JSON.parse(body);

      expect(data.exportedAt).toBeDefined();
      expect(data.conversation.id).toBe("conv_1");
      expect(data.conversation.title).toBe("Fleet optimization chat");
      expect(data.conversation.messageCount).toBe(2);
      expect(data.conversation.messages).toHaveLength(2);
      expect(data.conversation.messages[0].role).toBe("user");
      expect(data.conversation.messages[1].role).toBe("assistant");
      expect(data.conversation.messages[1].model).toBe("google/gemini-2.5-flash");
      expect(data.conversation.messages[1].costUsd).toBe(0.0012);
    });

    it("should default to JSON when no format parameter is provided", async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: "user_1", email: "test@test.com" },
      } as any);
      vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(mockConversation as any);

      const req = new NextRequest(
        "http://localhost:3000/api/token/conversations/conv_1/export",
      );

      const response = await GET(req, makeParams("conv_1"));

      expect(response.status).toBe(200);
      expect(response.headers.get("Content-Type")).toBe("application/json; charset=utf-8");
    });

    it("should produce pretty-printed JSON (indented)", async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: "user_1", email: "test@test.com" },
      } as any);
      vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(mockConversation as any);

      const req = new NextRequest(
        "http://localhost:3000/api/token/conversations/conv_1/export?format=json",
      );

      const response = await GET(req, makeParams("conv_1"));
      const body = await response.text();

      // Pretty-printed JSON contains newlines and indentation
      expect(body).toContain("\n");
      expect(body).toContain("  ");
    });
  });

  describe("CSV export", () => {
    it("should return valid CSV with header row and message rows", async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: "user_1", email: "test@test.com" },
      } as any);
      vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(mockConversation as any);

      const req = new NextRequest(
        "http://localhost:3000/api/token/conversations/conv_1/export?format=csv",
      );

      const response = await GET(req, makeParams("conv_1"));

      expect(response.status).toBe(200);
      expect(response.headers.get("Content-Type")).toBe("text/csv; charset=utf-8");
      expect(response.headers.get("Content-Disposition")).toMatch(/^attachment; filename="token-.*\.csv"$/);

      const body = await response.text();

      // Header row is the first line
      const headerEnd = body.indexOf("\n");
      const header = body.slice(0, headerEnd);
      expect(header).toBe("role,content,model,costUsd,latencyMs,createdAt");

      // The body after the header contains 2 CSV records (assistant content has embedded newlines,
      // so simple split("\n") won't yield clean rows; instead verify known values are present)
      const dataSection = body.slice(headerEnd + 1);

      // First message (user) — no special chars, appears as plain CSV
      expect(dataSection).toContain("user");
      expect(dataSection).toContain("What models should I run?");

      // Second message (assistant) — content has commas and newlines, so it's double-quoted
      expect(dataSection).toContain("assistant");
      expect(dataSection).toContain("google/gemini-2.5-flash");
      expect(dataSection).toContain("0.0012");
      expect(dataSection).toContain("1500");
    });

    it("should properly escape CSV values containing commas, quotes, and newlines", async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: "user_1", email: "test@test.com" },
      } as any);

      const trickConversation = {
        ...mockConversation,
        messages: [
          {
            id: "msg_tricky",
            role: "user",
            content: 'Hello, "world"\nNew line here',
            model: null,
            costUsd: 0,
            latencyMs: 0,
            inputTokens: 5,
            outputTokens: 0,
            messageType: "chat",
            createdAt: new Date("2026-02-20T10:00:00Z"),
          },
        ],
      };
      vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(trickConversation as any);

      const req = new NextRequest(
        "http://localhost:3000/api/token/conversations/conv_1/export?format=csv",
      );

      const response = await GET(req, makeParams("conv_1"));
      const body = await response.text();

      // The content field should be wrapped in double-quotes with internal quotes doubled
      expect(body).toContain('""world""');
    });
  });

  it("should query the database with correct select fields including export-specific columns", async () => {
    vi.mocked(auth).mockResolvedValue({
      user: { id: "user_1", email: "test@test.com" },
    } as any);
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(mockConversation as any);

    const req = new NextRequest(
      "http://localhost:3000/api/token/conversations/conv_1/export?format=json",
    );

    await GET(req, makeParams("conv_1"));

    expect(db.tokenConversation.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "conv_1", userId: "user_1" },
        select: expect.objectContaining({
          id: true,
          title: true,
          createdAt: true,
          updatedAt: true,
          messages: expect.objectContaining({
            orderBy: { createdAt: "asc" },
            select: expect.objectContaining({
              role: true,
              content: true,
              model: true,
              costUsd: true,
              latencyMs: true,
              inputTokens: true,
              outputTokens: true,
              messageType: true,
              createdAt: true,
            }),
          }),
        }),
      }),
    );
  });
});
