import prisma from '../services/prisma.js';
import asyncHandler from '../utils/asyncHandler.js';
import ApiError from '../utils/ApiError.js';
import { startAttemptSchema, submitAnswerSchema } from '../utils/validators.js';

async function userHasAccess(user, course) {
  if (course.isFree || course.priceCents === 0) return true;
  if (!user) return false;
  if (user.role === 'ADMIN') return true;
  const purchase = await prisma.purchase.findFirst({
    where: { userId: user.id, courseId: course.id, status: 'PAID' },
  });
  return Boolean(purchase);
}

function sanitizeQuestion(q) {
  return {
    id: q.id,
    text: q.text,
    type: q.type,
    options: q.options
      .sort((a, b) => a.order - b.order)
      .map((o) => ({ id: o.id, text: o.text, order: o.order })),
  };
}

// POST /api/attempts/start
export const startAttempt = asyncHandler(async (req, res) => {
  const { courseId, mode } = startAttemptSchema.parse(req.body);

  const course = await prisma.course.findUnique({ where: { id: courseId } });
  if (!course || !course.published) throw ApiError.notFound('Course not found');

  // Practice and Exam draw from separate question banks.
  const allQuestions = await prisma.question.findMany({
    where: { courseId, status: 'APPROVED', category: mode },
    orderBy: { order: 'asc' },
    include: { options: true },
  });
  if (allQuestions.length === 0) {
    throw ApiError.badRequest(`This exam has no ${mode.toLowerCase()} questions yet`);
  }

  const hasAccess = await userHasAccess(req.user, course);
  const totalAvailable = allQuestions.length;

  let served = allQuestions;
  let locked = false;
  let lockedCount = 0;

  if (!hasAccess) {
    served = allQuestions.slice(0, course.freeQuestionLimit);
    locked = totalAvailable > served.length;
    lockedCount = totalAvailable - served.length;
  }

  const attempt = await prisma.attempt.create({
    data: { userId: req.user.id, courseId, mode, total: served.length },
  });

  res.status(201).json({
    success: true,
    attempt: { id: attempt.id, mode: attempt.mode, courseId, total: served.length },
    course: {
      id: course.id,
      title: course.title,
      slug: course.slug,
      priceCents: course.priceCents,
      currency: course.currency,
    },
    questions: served.map(sanitizeQuestion),
    access: { hasAccess, locked, lockedCount, totalAvailable, freeLimit: course.freeQuestionLimit },
  });
});

// POST /api/attempts/:id/answer
export const submitAnswer = asyncHandler(async (req, res) => {
  const { questionId, selectedOptionIds } = submitAnswerSchema.parse(req.body);

  const attempt = await prisma.attempt.findUnique({ where: { id: req.params.id } });
  if (!attempt) throw ApiError.notFound('Attempt not found');
  if (attempt.userId !== req.user.id) throw ApiError.forbidden();
  if (attempt.completed) throw ApiError.badRequest('Attempt already completed');

  const question = await prisma.question.findUnique({
    where: { id: questionId },
    include: { options: true },
  });
  if (!question || question.courseId !== attempt.courseId) {
    throw ApiError.badRequest('Question does not belong to this attempt');
  }

  const correctIds = question.options.filter((o) => o.isCorrect).map((o) => o.id).sort();
  const picked = [...new Set(selectedOptionIds)].sort();
  const isCorrect =
    correctIds.length === picked.length && correctIds.every((id, i) => id === picked[i]);

  await prisma.attemptAnswer.upsert({
    where: { attemptId_questionId: { attemptId: attempt.id, questionId } },
    create: { attemptId: attempt.id, questionId, selectedOptionIds: picked, isCorrect },
    update: { selectedOptionIds: picked, isCorrect },
  });

  // Practice mode: reveal correctness immediately. Exam mode: just acknowledge.
  if (attempt.mode === 'PRACTICE') {
    return res.json({
      success: true,
      mode: 'PRACTICE',
      isCorrect,
      correctOptionIds: correctIds,
      explanation: question.explanation || null,
    });
  }
  res.json({ success: true, mode: 'EXAM', recorded: true });
});

// POST /api/attempts/:id/finish
export const finishAttempt = asyncHandler(async (req, res) => {
  const attempt = await prisma.attempt.findUnique({
    where: { id: req.params.id },
    include: { answers: true },
  });
  if (!attempt) throw ApiError.notFound('Attempt not found');
  if (attempt.userId !== req.user.id) throw ApiError.forbidden();

  const score = attempt.answers.filter((a) => a.isCorrect).length;

  const updated = await prisma.attempt.update({
    where: { id: attempt.id },
    data: { completed: true, completedAt: new Date(), score },
  });

  res.json({ success: true, result: await buildResult(updated.id) });
});

// GET /api/attempts/:id/result
export const getResult = asyncHandler(async (req, res) => {
  const attempt = await prisma.attempt.findUnique({ where: { id: req.params.id } });
  if (!attempt) throw ApiError.notFound('Attempt not found');
  if (attempt.userId !== req.user.id && req.user.role !== 'ADMIN') throw ApiError.forbidden();
  res.json({ success: true, result: await buildResult(attempt.id) });
});

async function buildResult(attemptId) {
  const attempt = await prisma.attempt.findUnique({
    where: { id: attemptId },
    include: {
      course: { select: { title: true, slug: true } },
      answers: true,
    },
  });

  const answerMap = new Map(attempt.answers.map((a) => [a.questionId, a]));
  const questionIds = [...answerMap.keys()];

  const questions = await prisma.question.findMany({
    where: { id: { in: questionIds.length ? questionIds : ['__none__'] } },
    include: { options: { orderBy: { order: 'asc' } } },
  });

  const review = questions.map((q) => {
    const a = answerMap.get(q.id);
    return {
      questionId: q.id,
      text: q.text,
      type: q.type,
      explanation: q.explanation,
      options: q.options.map((o) => ({ id: o.id, text: o.text, isCorrect: o.isCorrect })),
      selectedOptionIds: a?.selectedOptionIds || [],
      isCorrect: a?.isCorrect || false,
    };
  });

  return {
    attemptId: attempt.id,
    mode: attempt.mode,
    course: attempt.course,
    score: attempt.score,
    total: attempt.total,
    completed: attempt.completed,
    answered: attempt.answers.length,
    review,
  };
}

// GET /api/attempts  (history)
export const listMyAttempts = asyncHandler(async (req, res) => {
  const attempts = await prisma.attempt.findMany({
    where: { userId: req.user.id },
    orderBy: { startedAt: 'desc' },
    include: { course: { select: { title: true, slug: true } } },
  });
  res.json({ success: true, attempts });
});
