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

// Mock cookie store that captures set/delete calls
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 next/headers before importing route handlers
mock.module("next/headers", () => ({
  cookies: async () => mockCookieStore,
}));

// Set up a valid session using the same auth module (it writes to mocked cookies)
import { createSession } from "@/lib/auth";
import { QUIZ_QUESTIONS } from "@/lib/quiz-questions";

// Import route handlers after mock is set up
const { GET, POST } = await import("../route");

const TEST_USER = {
  id: "test-quiz-user",
  displayName: "Quiz Tester",
  password: "test",
};

beforeAll(async () => {
  await db
    .delete(quizSessions)
    .where(eq(quizSessions.annotatorId, TEST_USER.id));
  await db.delete(annotators).where(eq(annotators.id, TEST_USER.id));
  await db.insert(annotators).values(TEST_USER);
  await createSession(TEST_USER.id);
});

afterAll(async () => {
  await db
    .delete(quizSessions)
    .where(eq(quizSessions.annotatorId, TEST_USER.id));
  await db.delete(annotators).where(eq(annotators.id, TEST_USER.id));
});

function makeRequest(body: unknown): Request {
  return new Request("http://localhost:3000/api/quiz", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
}

describe("POST /api/quiz {action: 'start'}", () => {
  test("returns quizSessionId and 8 questions", async () => {
    const res = await POST(makeRequest({ action: "start" }));
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.quizSessionId).toBeDefined();
    expect(typeof data.quizSessionId).toBe("string");
    expect(data.questions).toHaveLength(8);
    expect(data.questions[0]).toHaveProperty("id");
    expect(data.questions[0]).toHaveProperty("paragraphText");
    expect(data.questions[0]).toHaveProperty("options");
    expect(data.questions[0]).toHaveProperty("correctAnswer");
  });
});

describe("POST /api/quiz {action: 'answer'}", () => {
  let quizSessionId: string;
  let questions: Array<{
    id: string;
    correctAnswer: string;
    explanation: string;
  }>;

  beforeAll(async () => {
    const res = await POST(makeRequest({ action: "start" }));
    const data = await res.json();
    quizSessionId = data.quizSessionId;
    questions = data.questions;
  });

  test("returns correct: true for a correct answer", async () => {
    const q = questions[0];
    const res = await POST(
      makeRequest({
        action: "answer",
        quizSessionId,
        questionId: q.id,
        answer: q.correctAnswer,
      }),
    );
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.correct).toBe(true);
    expect(data.explanation).toBe(q.explanation);
    expect(data.completed).toBe(false);
  });

  test("returns correct: false for a wrong answer", async () => {
    const q = questions[1];
    // Pick an answer that is NOT the correct one
    const wrongAnswer =
      q.correctAnswer === q.correctAnswer
        ? QUIZ_QUESTIONS.find((qq) => qq.id === q.id)!.options.find(
            (o) => o.value !== q.correctAnswer,
          )!.value
        : q.correctAnswer;

    const res = await POST(
      makeRequest({
        action: "answer",
        quizSessionId,
        questionId: q.id,
        answer: wrongAnswer,
      }),
    );
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.correct).toBe(false);
    expect(data.correctAnswer).toBe(q.correctAnswer);
    expect(data.explanation).toBeDefined();
    expect(data.completed).toBe(false);
  });

  test("rejects duplicate answer for same question", async () => {
    const q = questions[0]; // already answered above
    const res = await POST(
      makeRequest({
        action: "answer",
        quizSessionId,
        questionId: q.id,
        answer: q.correctAnswer,
      }),
    );

    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toBe("Question already answered");
  });
});

describe("Full quiz flow - all correct", () => {
  let quizSessionId: string;
  let questions: Array<{
    id: string;
    correctAnswer: string;
  }>;

  test("answering all 8 correctly returns passed: true", async () => {
    const startRes = await POST(makeRequest({ action: "start" }));
    const startData = await startRes.json();
    quizSessionId = startData.quizSessionId;
    questions = startData.questions;

    let lastData: { score?: number; passed?: boolean; completed: boolean } = {
      completed: false,
    };

    for (let i = 0; i < questions.length; i++) {
      const q = questions[i];
      const res = await POST(
        makeRequest({
          action: "answer",
          quizSessionId,
          questionId: q.id,
          answer: q.correctAnswer,
        }),
      );
      lastData = await res.json();
    }

    expect(lastData.completed).toBe(true);
    expect(lastData.score).toBe(8);
    expect(lastData.passed).toBe(true);
  });

  test("GET returns hasPassedQuiz: true after passing", async () => {
    const res = await GET();
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.hasPassedQuiz).toBe(true);
    expect(data.quizSessionId).toBeDefined();
  });
});

describe("Full quiz flow - failing score", () => {
  test("answering with 5 wrong returns passed: false", async () => {
    // Clean up passed quizzes so GET doesn't short-circuit
    await db
      .delete(quizSessions)
      .where(eq(quizSessions.annotatorId, TEST_USER.id));

    const startRes = await POST(makeRequest({ action: "start" }));
    const startData = await startRes.json();
    const quizSessionId = startData.quizSessionId;
    const questions: Array<{ id: string; correctAnswer: string }> =
      startData.questions;

    let lastData: { score?: number; passed?: boolean; completed: boolean } = {
      completed: false,
    };

    for (let i = 0; i < questions.length; i++) {
      const q = questions[i];
      const fullQ = QUIZ_QUESTIONS.find((qq) => qq.id === q.id)!;

      // Get wrong answer for first 5, correct for last 3
      let answer: string;
      if (i < 5) {
        const wrongOption = fullQ.options.find(
          (o) => o.value !== q.correctAnswer,
        );
        answer = wrongOption ? wrongOption.value : q.correctAnswer;
      } else {
        answer = q.correctAnswer;
      }

      const res = await POST(
        makeRequest({
          action: "answer",
          quizSessionId,
          questionId: q.id,
          answer,
        }),
      );
      lastData = await res.json();
    }

    expect(lastData.completed).toBe(true);
    expect(lastData.score).toBe(3);
    expect(lastData.passed).toBe(false);
  });

  test("GET returns hasPassedQuiz: false after failing", async () => {
    const res = await GET();
    const data = await res.json();

    expect(res.status).toBe(200);
    expect(data.hasPassedQuiz).toBe(false);
  });
});
