import { describe, test, expect, beforeAll, afterAll, mock } from "bun:test";
import { db } from "@/db";
import {
  annotators,
  paragraphs,
  assignments,
  humanLabels,
  quizSessions,
} from "@/db/schema";
import { eq, and } from "drizzle-orm";

const cookieJar = new Map<string, string>();
const mockCookieStore = {
  get(name: string) {
    const value = cookieJar.get(name);
    return value ? { value } : undefined;
  },
  set(name: string, value: string, _opts?: unknown) {
    cookieJar.set(name, value);
  },
  delete(name: string) {
    cookieJar.delete(name);
  },
};

mock.module("next/headers", () => ({
  cookies: async () => mockCookieStore,
}));

const { createSession } = await import("@/lib/auth");
const { GET, POST } = await import("../route");

const TEST_ANNOTATOR = {
  id: "test-label-user",
  displayName: "Test Label User",
  password: "testpass",
};

const TEST_PARAGRAPHS = [
  {
    id: "test-para-1",
    text: "The board oversees cybersecurity risk.",
    wordCount: 6,
    paragraphIndex: 0,
    companyName: "Test Corp",
    cik: "0001234567",
    ticker: "TEST",
    filingType: "10-K",
    filingDate: "2024-03-15",
    fiscalYear: 2024,
    accessionNumber: "0001234567-24-000001",
    secItem: "Item 1C",
  },
  {
    id: "test-para-2",
    text: "Our CISO manages the cybersecurity program.",
    wordCount: 7,
    paragraphIndex: 1,
    companyName: "Test Corp",
    cik: "0001234567",
    ticker: "TEST",
    filingType: "10-K",
    filingDate: "2024-03-15",
    fiscalYear: 2024,
    accessionNumber: "0001234567-24-000001",
    secItem: "Item 1C",
  },
];

const QUIZ_SESSION_ID = "test-label-quiz-session";

beforeAll(async () => {
  // Clean up in dependency order
  await db
    .delete(humanLabels)
    .where(eq(humanLabels.annotatorId, TEST_ANNOTATOR.id));
  await db
    .delete(assignments)
    .where(eq(assignments.annotatorId, TEST_ANNOTATOR.id));
  await db
    .delete(quizSessions)
    .where(eq(quizSessions.annotatorId, TEST_ANNOTATOR.id));
  for (const p of TEST_PARAGRAPHS) {
    await db.delete(paragraphs).where(eq(paragraphs.id, p.id));
  }
  await db.delete(annotators).where(eq(annotators.id, TEST_ANNOTATOR.id));

  // Create test data
  await db.insert(annotators).values(TEST_ANNOTATOR);
  await db.insert(paragraphs).values(TEST_PARAGRAPHS);
  await db.insert(assignments).values(
    TEST_PARAGRAPHS.map((p) => ({
      paragraphId: p.id,
      annotatorId: TEST_ANNOTATOR.id,
    })),
  );

  // Create a passed quiz session with warmup completed
  await db.insert(quizSessions).values({
    id: QUIZ_SESSION_ID,
    annotatorId: TEST_ANNOTATOR.id,
    totalQuestions: 8,
    score: 8,
    passed: true,
    completedAt: new Date(),
    answers: JSON.stringify({ quizAnswers: [], warmupCompleted: 5 }),
  });

  // Set up session cookie
  cookieJar.clear();
  await createSession(TEST_ANNOTATOR.id);
});

afterAll(async () => {
  await db
    .delete(humanLabels)
    .where(eq(humanLabels.annotatorId, TEST_ANNOTATOR.id));
  await db
    .delete(assignments)
    .where(eq(assignments.annotatorId, TEST_ANNOTATOR.id));
  await db
    .delete(quizSessions)
    .where(eq(quizSessions.annotatorId, TEST_ANNOTATOR.id));
  for (const p of TEST_PARAGRAPHS) {
    await db.delete(paragraphs).where(eq(paragraphs.id, p.id));
  }
  await db.delete(annotators).where(eq(annotators.id, TEST_ANNOTATOR.id));
});

describe("GET /api/label", () => {
  test("returns first assigned paragraph", async () => {
    const res = await GET();
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.paragraph).toBeDefined();
    expect(data.paragraph.id).toBe("test-para-1");
    expect(data.paragraph.text).toBe(TEST_PARAGRAPHS[0].text);
    expect(data.paragraph.companyName).toBe("Test Corp");
    expect(data.paragraph.filingType).toBe("10-K");
    expect(data.progress).toBeDefined();
    expect(data.progress.completed).toBe(0);
    expect(data.progress.total).toBe(2);
  });

  test("redirects to warmup if warmup not complete", async () => {
    // Temporarily modify quiz session to remove warmup completion
    await db
      .update(quizSessions)
      .set({ answers: "[]" })
      .where(eq(quizSessions.id, QUIZ_SESSION_ID));

    const res = await GET();
    const data = await res.json();
    expect(data.redirectToWarmup).toBe(true);

    // Restore warmup completion
    await db
      .update(quizSessions)
      .set({
        answers: JSON.stringify({ quizAnswers: [], warmupCompleted: 5 }),
      })
      .where(eq(quizSessions.id, QUIZ_SESSION_ID));
  });
});

describe("POST /api/label", () => {
  test("creates a label for assigned paragraph", async () => {
    const req = new Request("http://localhost:3000/api/label", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        paragraphId: "test-para-1",
        contentCategory: "Board Governance",
        specificityLevel: 1,
        sessionId: "test-session-1",
        durationMs: 5000,
      }),
    });

    const res = await POST(req);
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.ok).toBe(true);
    expect(data.progress.completed).toBe(1);
    expect(data.progress.total).toBe(2);
  });

  test("GET returns next unlabeled paragraph after labeling", async () => {
    const res = await GET();
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.paragraph.id).toBe("test-para-2");
    expect(data.progress.completed).toBe(1);
    expect(data.progress.total).toBe(2);
  });

  test("rejects duplicate label", async () => {
    const req = new Request("http://localhost:3000/api/label", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        paragraphId: "test-para-1",
        contentCategory: "Board Governance",
        specificityLevel: 1,
        sessionId: "test-session-1",
        durationMs: 3000,
      }),
    });

    const res = await POST(req);
    expect(res.status).toBe(409);
    const data = await res.json();
    expect(data.error).toBe("Already labeled this paragraph");
  });

  test("rejects invalid category", async () => {
    const req = new Request("http://localhost:3000/api/label", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        paragraphId: "test-para-2",
        contentCategory: "Invalid Category",
        specificityLevel: 1,
        sessionId: "test-session-1",
      }),
    });

    const res = await POST(req);
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toBe("Invalid content category");
  });

  test("rejects invalid specificity", async () => {
    const req = new Request("http://localhost:3000/api/label", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        paragraphId: "test-para-2",
        contentCategory: "Board Governance",
        specificityLevel: 5,
        sessionId: "test-session-1",
      }),
    });

    const res = await POST(req);
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toBe("Invalid specificity level");
  });

  test("rejects unassigned paragraph", async () => {
    const req = new Request("http://localhost:3000/api/label", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        paragraphId: "nonexistent-para",
        contentCategory: "Board Governance",
        specificityLevel: 1,
        sessionId: "test-session-1",
      }),
    });

    const res = await POST(req);
    expect(res.status).toBe(403);
  });

  test("labels second paragraph and GET returns done", async () => {
    const req = new Request("http://localhost:3000/api/label", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        paragraphId: "test-para-2",
        contentCategory: "Management Role",
        specificityLevel: 2,
        notes: "Test note",
        sessionId: "test-session-1",
        durationMs: 4000,
      }),
    });

    const res = await POST(req);
    const data = await res.json();
    expect(res.status).toBe(200);
    expect(data.ok).toBe(true);
    expect(data.progress.completed).toBe(2);
    expect(data.progress.total).toBe(2);

    // GET should now return done
    const getRes = await GET();
    const getData = await getRes.json();
    expect(getData.done).toBe(true);
    expect(getData.progress.completed).toBe(2);
  });
});

describe("GET /api/label - unauthorized", () => {
  test("returns 401 without session", async () => {
    cookieJar.clear();
    const res = await GET();
    expect(res.status).toBe(401);
  });
});
