// Helper to format seconds as mm:ss or hh:mm:ss
function formatTime(seconds: number): string {
  if (seconds >= 3600) {
    const h = Math.floor(seconds / 3600).toString().padStart(2, '0');
    const m = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0');
    const s = Math.floor(seconds % 60).toString().padStart(2, '0');
    return `${h}:${m}:${s}`;
  } else if (seconds >= 60) {
    const m = Math.floor(seconds / 60).toString().padStart(2, '0');
    const s = Math.floor(seconds % 60).toString().padStart(2, '0');
    return `${m}:${s}`;
  } else {
    return seconds.toString();
  }
}
import Quiz from '../models/quiz';
import Question from '../models/question';
import QuizResult from '../models/quizResult';
import QuizAttempt from '../models/quizAttempt';
import { IQuestionDocument } from '../models/question';
import { shuffle } from '../utils/shuffle';
import { seededShuffle } from '../utils/seededShuffle';
import { expireQuizzes } from '../cron/quizExpiry';
import { notifyQuizPublished } from './notificationService';
import { getIndiaNow, toKolkataTime } from '../utils/timezone';

// ─── Helpers ──────────────────────────────────────────────────────────────────

const notFound = (msg = 'Quiz not found'): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 404 });

const badRequest = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 400 });

const validateDateRange = (start: Date | string, end: Date | string): void => {
  if (new Date(start) >= new Date(end)) throw badRequest('start_datetime must be before end_datetime');
};

const validateQuestions = async (questionIds: string[]): Promise<void> => {
  if (!questionIds || questionIds.length === 0) throw badRequest('At least one question is required');
  const found = await Question.find({ _id: { $in: questionIds } }, '_id');
  if (found.length !== questionIds.length) throw badRequest('One or more question IDs are invalid');
};

// ─── CRUD ─────────────────────────────────────────────────────────────────────

interface QuizInput {
  title: string;
  questions: string[];
  start_datetime: string;
  end_datetime: string;
  status?: string;
}

export const createQuiz = async ({ title, questions, start_datetime, end_datetime, status }: QuizInput) => {
  validateDateRange(start_datetime, end_datetime);
  await validateQuestions(questions);
  const existing = await Quiz.findOne({ title: title.trim() });
  if (existing) throw badRequest('A quiz with this title already exists');
  return Quiz.create({ title: title.trim(), questions, start_datetime, end_datetime, status });
};

export const getAllQuizzes = async (status: string | undefined, page = 1, limit = 10) => {
  await expireQuizzes();
  const filter = status ? { status } : {};
  const skip = (page - 1) * limit;
  const [quizzes, total] = await Promise.all([
    Quiz.find(filter)
      .populate('questions', 'question_text correct_option points')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit),
    Quiz.countDocuments(filter),
  ]);
  return {
    quizzes,
    pagination: {
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
      hasNextPage: page < Math.ceil(total / limit),
      hasPrevPage: page > 1,
    },
  };
};

export const getQuizById = async (id: string) => {
  await expireQuizzes();
  const quiz = await Quiz.findById(id).populate(
    'questions',
    'question_text option_a option_b option_c option_d correct_option points',
  );
  if (!quiz) throw notFound();
  return quiz;
};

export const updateQuiz = async (id: string, { title, questions, start_datetime, end_datetime, status }: Partial<QuizInput>) => {
  const quiz = await Quiz.findById(id);
  if (!quiz) throw notFound();

  if (start_datetime && end_datetime) {
    validateDateRange(start_datetime, end_datetime);
  } else if (start_datetime) {
    validateDateRange(start_datetime, quiz.end_datetime);
  } else if (end_datetime) {
    validateDateRange(quiz.start_datetime, end_datetime);
  }

  if (questions) await validateQuestions(questions);

  if (title && title.trim() !== quiz.title) {
    const existing = await Quiz.findOne({ title: title.trim() });
    if (existing) throw badRequest('A quiz with this title already exists');
  }

  const updatedQuiz = await Quiz.findByIdAndUpdate(
    id,
    {
      ...(title && { title: title.trim() }),
      ...(questions && { questions }),
      ...(start_datetime && { start_datetime }),
      ...(end_datetime && { end_datetime }),
      ...(status && { status }),
    },
    { new: true, runValidators: true },
  ).populate('questions', 'question_text correct_option points');

  if (status === 'active' && quiz.status !== 'active' && updatedQuiz) {
    await notifyQuizPublished(updatedQuiz._id.toString(), updatedQuiz.title);
  }

  return updatedQuiz;
};

export const deleteQuiz = async (id: string): Promise<void> => {
  const quiz = await Quiz.findByIdAndDelete(id);
  if (!quiz) throw notFound();
};

// ─── User-facing ──────────────────────────────────────────────────────────────

export const getActiveQuizzes = async (userId: string, filter: 'live' | 'upcoming' | 'all' = 'all') => {
  const now = new Date();
  const quizzes = await Quiz.find({ status: 'active', end_datetime: { $gte: now } })
    .select('title start_datetime end_datetime questions')
    .populate('questions', 'points')
    .sort({ start_datetime: 1 })
    .lean();

  const quizIds = quizzes.map((q) => q._id);
  const submissions = await QuizResult.find(
    { quiz: { $in: quizIds }, user: userId },
    'quiz score total_points submitted_at',
  ).lean();

  const submissionMap = new Map(submissions.map((s) => [s.quiz.toString(), s]));

  const mapped = quizzes.map((quiz) => {
    const isLive = new Date(quiz.start_datetime) <= now;
    const questions = quiz.questions as unknown as { points: number }[];
    const total_points = questions.reduce((sum, q) => sum + q.points, 0);
    const total_questions = questions.length;
    const submission = submissionMap.get(quiz._id.toString());
    const { questions: _, ...quizWithoutQuestions } = quiz;
    if (submission) {
      return { ...quizWithoutQuestions, total_questions, total_points, is_live: isLive, submitted: true, points_earned: submission.score, submitted_at: submission.submitted_at };
    }
    return { ...quizWithoutQuestions, total_questions, total_points, is_live: isLive, submitted: false };
  });

  const sortBySubmitted = <T extends { submitted: boolean }>(items: T[]) =>
    [...items].sort((a, b) => Number(a.submitted) - Number(b.submitted));

  if (filter === 'live') return sortBySubmitted(mapped.filter((q) => q.is_live));
  if (filter === 'upcoming') return sortBySubmitted(mapped.filter((q) => !q.is_live));
  return sortBySubmitted(mapped);
};

export const getQuizForUser = async (quizId: string, userId: string, page = 1, limit = 10) => {
  const quiz = await Quiz.findById(quizId).populate(
    'questions',
    'question_text option_a option_b option_c option_d points',
  );
  if (!quiz) throw notFound();
  if (quiz.status !== 'active') throw badRequest('This quiz is not active');

  const now = new Date();
  if (now < quiz.start_datetime) throw badRequest('This quiz has not started yet');
  if (now > quiz.end_datetime) throw badRequest('This quiz has already ended');

  const alreadySubmitted = await QuizResult.exists({ quiz: quizId, user: userId });
  const allQuestions = quiz.questions as unknown as IQuestionDocument[];
  const totalQuestions = allQuestions.length;
  const totalPages = Math.ceil(totalQuestions / limit);
  const questions = allQuestions.slice((page - 1) * limit, (page - 1) * limit + limit);

  return {
    alreadySubmitted: !!alreadySubmitted,
    quiz: { _id: quiz._id, title: quiz.title, start_datetime: quiz.start_datetime, end_datetime: quiz.end_datetime, questions },
    pagination: { total: totalQuestions, page, limit, totalPages, hasNextPage: page < totalPages, hasPrevPage: page > 1 },
  };
};

// ─── Quiz Attempt (shuffled) ─────────────────────────────────────────────────

export const attemptQuiz = async (quizId: string, userId: string, page = 1, limit = 10) => {
  if (!Number.isInteger(page) || page < 1) throw badRequest('Invalid page. page must be an integer >= 1');
  if (!Number.isInteger(limit) || limit < 1 || limit > 50) throw badRequest('Invalid limit. limit must be an integer between 1 and 50');

  const quiz = await Quiz.findById(quizId).populate<{ questions: IQuestionDocument[] }>(
    'questions',
    'question_text option_a option_b option_c option_d points',
  );
  if (!quiz) throw notFound();
  if (quiz.status !== 'active') throw badRequest('This quiz is not active');

  const now = new Date();
  if (now < quiz.start_datetime) throw badRequest('This quiz has not started yet');
  if (now > quiz.end_datetime) throw badRequest('This quiz has already ended');

  const alreadySubmitted = await QuizResult.exists({ quiz: quizId, user: userId });
  if (alreadySubmitted) {
    return {
      alreadySubmitted: true,
      quiz: { _id: quiz._id, title: quiz.title, questions: [] },
      pagination: { total: 0, page, limit, totalPages: 0, hasNextPage: false, hasPrevPage: false },
    };
  }

  // Check for existing attempt (refresh-safe) — if none, create one

  let attempt = await QuizAttempt.findOne({ quiz: quizId, user: userId });

  if (!attempt) {
    // Use a deterministic seed based on user and quiz for unique, repeatable shuffle
    const seed = `${quizId}_${userId}`;
    const shuffledQuestions = seededShuffle(quiz.questions, seed);
    const startedAt = getIndiaNow();
    const durationSeconds = 5 * quiz.questions.length;
    const expiresAt = new Date(startedAt.getTime() + durationSeconds * 1000);
    attempt = await QuizAttempt.create({
      quiz: quizId,
      user: userId,
      questions: shuffledQuestions.map((q, idx) => ({
        question: q._id,
        shuffled_options: seededShuffle([q.option_a, q.option_b, q.option_c, q.option_d], `${seed}_${idx}`),
      })),
      startedAt,
      durationSeconds,
      expiresAt,
    });
  }

  // Build response with shuffled order and options
  const questionMap = new Map(quiz.questions.map((q) => [q._id.toString(), q]));
  const allShuffled = attempt.questions.map((aq) => {
    const original = questionMap.get(aq.question.toString());
    if (!original) return null;
    return {
      _id: original._id,
      question_text: original.question_text,
      option_a: aq.shuffled_options[0] ?? '',
      option_b: aq.shuffled_options[1] ?? '',
      option_c: aq.shuffled_options[2] ?? '',
      option_d: aq.shuffled_options[3] ?? '',
    };
  }).filter(Boolean);

  // Paginate
  const totalQuestions = allShuffled.length;
  const totalPages = Math.ceil(totalQuestions / limit);
  const questions = allShuffled.slice((page - 1) * limit, (page - 1) * limit + limit);


  // Timer fields (India time)
  const nowIndia = getIndiaNow();

  // Use Date objects for math, format as string for response
  const startedAtDate = attempt.startedAt;
  const expiresAtDate = attempt.expiresAt;
  const durationSeconds = attempt.durationSeconds;
  const remainingSeconds = Math.max(0, Math.floor((expiresAtDate.getTime() - nowIndia.getTime()) / 1000));
  const startedAt = toKolkataTime(startedAtDate, "yyyy-MM-dd HH:mm:ss");
  const expiresAt = toKolkataTime(expiresAtDate, "yyyy-MM-dd HH:mm:ss");

  return {
    alreadySubmitted: false,
    quiz: { _id: quiz._id, title: quiz.title, questions },
    pagination: { total: totalQuestions, page, limit, totalPages, hasNextPage: page < totalPages, hasPrevPage: page > 1 },
    startedAt,
    expiresAt,
    durationTime: durationSeconds > 60 ? formatTime(durationSeconds) : durationSeconds.toString(),
    remainingTime: remainingSeconds > 60 ? formatTime(remainingSeconds) : remainingSeconds.toString(),
  };
};
