import { randomUUID } from "crypto";
import { getGameTerms, getDistractors } from "@lila/db";
import type {
  GameRequest,
  GameSession,
  GameQuestion,
  AnswerOption,
  AnswerSubmission,
  AnswerResult,
} from "@lila/shared";
import type { GameSessionStore } from "../gameSessionStore/index.js";
import {
  NotFoundError,
  ConflictError,
  UnprocessableEntityError,
} from "../errors/AppError.js";
import { shuffleArray } from "../lib/utils.js";

export const createGameSession = async (
  request: GameRequest,
  store: GameSessionStore,
  userId: string,
): Promise<GameSession> => {
  const terms = await getGameTerms(
    request.source_language,
    request.target_language,
    request.pos,
    request.difficulty,
    request.rounds,
  );

  if (terms.length === 0) {
    throw new UnprocessableEntityError("No terms found for the given filters");
  }

  const answerKey = new Map<string, { correctOptionId: number }>();

  const questions: GameQuestion[] = await Promise.all(
    terms.map(async (term) => {
      const distractorTexts = await getDistractors(
        term.termId,
        term.targetText,
        request.target_language,
        request.pos,
        request.difficulty,
        6,
      );

      const uniqueDistractors = [
        ...new Set(distractorTexts.filter((t) => t !== term.targetText)),
      ];

      if (uniqueDistractors.length < 3) {
        throw new Error(
          `Not enough unique distractors for term: ${term.targetText}`,
        );
      }

      const optionTexts = [term.targetText, ...uniqueDistractors.slice(0, 3)];
      const shuffledTexts = shuffleArray(optionTexts);
      const correctOptionId = shuffledTexts.indexOf(term.targetText);

      const options: AnswerOption[] = shuffledTexts.map((text, index) => ({
        optionId: index,
        text,
      }));

      const questionId = randomUUID();
      answerKey.set(questionId, { correctOptionId });

      return {
        questionId,
        prompt: term.sourceText,
        gloss: term.sourceGloss,
        options,
      };
    }),
  );

  const sessionId = randomUUID();
  await store.create(sessionId, { answers: answerKey, userId }, 30 * 60 * 1000);

  return { sessionId, questions };
};

export const evaluateAnswer = async (
  submission: AnswerSubmission,
  store: GameSessionStore,
  userId: string,
): Promise<AnswerResult> => {
  const session = await store.get(submission.sessionId);

  if (!session || session.userId !== userId) {
    throw new NotFoundError(`Game session not found: ${submission.sessionId}`);
  }

  const answer = session.answers.get(submission.questionId);

  if (answer === undefined) {
    throw new ConflictError(
      `Question already answered: ${submission.questionId}`,
    );
  }

  const updatedAnswers = new Map(session.answers);
  updatedAnswers.delete(submission.questionId);

  if (updatedAnswers.size === 0) {
    await store.delete(submission.sessionId);
  } else {
    await store.update(submission.sessionId, {
      answers: updatedAnswers,
      userId: session.userId,
    });
  }

  return {
    questionId: submission.questionId,
    isCorrect: submission.selectedOptionId === answer.correctOptionId,
    correctOptionId: answer.correctOptionId,
    selectedOptionId: submission.selectedOptionId,
  };
};
