/* eslint-disable @next/next/no-img-element */
import { Module } from "@/interfaces";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import AbandonPopup from "@/components/AbandonPopup";
import Layout from "@/components/High/Layout";
import Finish from "@/exams/Finish";
import Level from "@/exams/Level";
import Listening from "@/exams/Listening";
import Reading from "@/exams/Reading";
import Selection from "@/exams/Selection";
import Speaking from "@/exams/Speaking";
import Writing from "@/exams/Writing";
import { Exam, LevelExam, UserSolution, Variant } from "@/interfaces/exam";
import { User } from "@/interfaces/user";
import { evaluateSpeakingAnswer, evaluateWritingAnswer } from "@/utils/evaluation";
import { getExam } from "@/utils/exams";
import axios from "axios";
import { useRouter } from "next/router";
import { toast, ToastContainer } from "react-toastify";
import ShortUniqueId from "short-unique-id";
import { ExamProps } from "@/exams/types";
import useExamStore from "@/stores/exam";
import useEvaluationPolling from "@/hooks/useEvaluationPolling";

interface Props {
	page: "exams" | "exercises";
	user: User;
	destination?: string
	hideSidebar?: boolean
}

export default function ExamPage({ page, user, destination = "/", hideSidebar = false }: Props) {
	const router = useRouter();
	const [variant, setVariant] = useState<Variant>("full");
	const [avoidRepeated, setAvoidRepeated] = useState(false);
	const [showAbandonPopup, setShowAbandonPopup] = useState(false);
	const [pendingExercises, setPendingExercises] = useState<string[]>([]);

	const {
		exam, setExam,
		exams,
		sessionId, setSessionId, setPartIndex,
		moduleIndex, setModuleIndex,
		setQuestionIndex, setExerciseIndex,
		userSolutions, setUserSolutions,
		showSolutions, setShowSolutions,
		selectedModules, setSelectedModules,
		setUser,
		inactivity,
		timeSpent,
		assignment,
		bgColor,
		flags,
		dispatch,
		reset: resetStore,
		saveStats,
		saveSession,
		setFlags,
		setShuffles,
		evaluated,
		setEvaluated,
	} = useExamStore();

	const [isFetchingExams, setIsFetchingExams] = useState(false);
	const [isExamLoaded, setIsExamLoaded] = useState(moduleIndex < selectedModules.length);

	useEffect(() => {
		setIsExamLoaded(moduleIndex < selectedModules.length);
	}, [showSolutions, moduleIndex, selectedModules]);

	useEffect(() => {
		if (!showSolutions && sessionId.length === 0 && user?.id) {
			const shortUID = new ShortUniqueId();
			setUser(user.id);
			setSessionId(shortUID.randomUUID(8));
		}
	}, [setSessionId, isExamLoaded, sessionId, showSolutions, setUser, user?.id]);

	useEffect(() => {
		if (user?.type === "developer") console.log(exam);
	}, [exam, user]);

	useEffect(() => {
		(async () => {
			if (selectedModules.length > 0 && exams.length === 0) {
				setIsFetchingExams(true);
				const examPromises = selectedModules.map((module) =>
					getExam(
						module,
						avoidRepeated,
						variant,
						user?.type === "student" || user?.type === "developer" ? user.preferredGender : undefined,
					),
				);
				Promise.all(examPromises).then((values) => {
					setIsFetchingExams(false);
					if (values.every((x) => !!x)) {
						dispatch({ type: 'INIT_EXAM', payload: { exams: values.map((x) => x!), modules: selectedModules } })
					} else {
						toast.error("Something went wrong, please try again");
						setTimeout(router.reload, 500);
					}
				});
			}
		})();
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [selectedModules, exams]);


	const reset = () => {
		resetStore();
		setVariant("full");
		setAvoidRepeated(false);
		setShowAbandonPopup(false);
	};

	useEffect(() => {
		if (flags.finalizeModule && !showSolutions && flags.pendingEvaluation) {
			if (exam && (exam.module === "writing" || exam.module === "speaking") && userSolutions.length > 0 && !showSolutions) {
				const exercisesToEvaluate = exam.exercises
					.map(exercise => exercise.id);

				setPendingExercises(exercisesToEvaluate);
				(async () => {
					await Promise.all(
						exam.exercises.map(async (exercise, index) => {
							if (exercise.type === "writing")
								await evaluateWritingAnswer(user.id, sessionId, exercise, index + 1, userSolutions.find((x) => x.exercise === exercise.id)!);

							if (exercise.type === "interactiveSpeaking" || exercise.type === "speaking"){
								await evaluateSpeakingAnswer(
									user.id,
									sessionId,
									exercise,
									userSolutions.find((x) => x.exercise === exercise.id)!,
									index + 1,
								);
							}
						}),
					)
				})();
			}
		}
	}, [exam, showSolutions, userSolutions, sessionId, user?.id, flags]);

	useEvaluationPolling({pendingExercises, setPendingExercises});

	useEffect(() => {
		if (flags.finalizeExam && moduleIndex !== -1) {
			setModuleIndex(-1);
		}
	}, [flags, moduleIndex, setModuleIndex]);

	useEffect(() => {
		if (flags.finalizeExam && !flags.pendingEvaluation && pendingExercises.length === 0) {
			(async () => {
				if (evaluated.length !== 0) {
					setUserSolutions(
						userSolutions.map(solution => {
							const evaluatedSolution = evaluated.find(e => e.exercise === solution.exercise);
							if (evaluatedSolution) {
								return { ...solution, ...evaluatedSolution };
							}
							return solution;
						})
					);
				}
				await saveStats();
				await axios.get("/api/stats/update");
				setShowSolutions(true);
				setFlags({ finalizeExam: false });
				dispatch({ type: "UPDATE_EXAMS" })
			})();
		}
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [saveStats, setFlags, setModuleIndex, evaluated, pendingExercises, setUserSolutions, flags]);


	const aggregateScoresByModule = (isPractice?: boolean): {
		module: Module;
		total: number;
		missing: number;
		correct: number;
	}[] => {
		const scores: {
			[key in Module]: { total: number; missing: number; correct: number };
		} = {
			reading: {
				total: 0,
				correct: 0,
				missing: 0,
			},
			listening: {
				total: 0,
				correct: 0,
				missing: 0,
			},
			writing: {
				total: 0,
				correct: 0,
				missing: 0,
			},
			speaking: {
				total: 0,
				correct: 0,
				missing: 0,
			},
			level: {
				total: 0,
				correct: 0,
				missing: 0,
			},
		};

		userSolutions.filter(x => isPractice ? x.isPractice : !x.isPractice).forEach((x) => {
			const examModule =
				x.module || (x.type === "writing" ? "writing" : x.type === "speaking" || x.type === "interactiveSpeaking" ? "speaking" : undefined);

			scores[examModule!] = {
				total: scores[examModule!].total + x.score.total,
				correct: scores[examModule!].correct + x.score.correct,
				missing: scores[examModule!].missing + x.score.missing,
			};
		});

		return Object.keys(scores)
			.filter((x) => scores[x as Module].total > 0)
			.map((x) => ({ module: x as Module, ...scores[x as Module] }));
	};

	const ModuleExamMap: Record<Module, React.ComponentType<ExamProps<Exam>>> = {
		"reading": Reading as React.ComponentType<ExamProps<Exam>>,
		"listening": Listening as React.ComponentType<ExamProps<Exam>>,
		"writing": Writing as React.ComponentType<ExamProps<Exam>>,
		"speaking": Speaking as React.ComponentType<ExamProps<Exam>>,
		"level": Level as React.ComponentType<ExamProps<Exam>>,
	}

	const CurrentExam = exam?.module ? ModuleExamMap[exam.module] : undefined;

	const onAbandon = async () => {
		await saveSession();
		reset();
	};

	return (
		<>
			<ToastContainer />
			{user && (
				<Layout
					user={user}
					bgColor={bgColor}
					hideSidebar={hideSidebar}
					className="justify-between"
					focusMode={selectedModules.length !== 0 && !showSolutions && moduleIndex < selectedModules.length}
					onFocusLayerMouseEnter={() => setShowAbandonPopup(true)}>
					<>
						{/* Modules weren't yet set by an INIT_EXAM or INIT_SOLUTIONS dispatch, show Selection component*/}
						{selectedModules.length === 0 && <Selection
							page={page}
							user={user!}
							onStart={(modules: Module[], avoid: boolean, variant: Variant) => {
								setModuleIndex(0);
								setAvoidRepeated(avoid);
								setSelectedModules(modules);
								setVariant(variant);
							}}
						/>}
						{isFetchingExams && (
							<div className="flex flex-grow flex-col items-center justify-center animate-pulse">
								<span className={`loading loading-infinity w-32 bg-ielts-${selectedModules[0]}`} />
								<span className={`font-bold text-2xl text-ielts-${selectedModules[0]}`}>Loading Exam ...</span>
							</div>
						)}
						{(moduleIndex === -1 && selectedModules.length !== 0) &&
							<Finish
								isLoading={flags.pendingEvaluation}
								user={user!}
								modules={selectedModules}
								solutions={userSolutions}
								assignment={assignment}
								information={{
									timeSpent,
									inactivity,
								}}
								destination={destination}
								onViewResults={(index?: number) => {
									if (exams[0].module === "level") {
										const levelExam = exams[0] as LevelExam;
										const allExercises = levelExam.parts.flatMap((part) => part.exercises);
										const exerciseOrderMap = new Map(allExercises.map((ex, index) => [ex.id, index]));
										const orderedSolutions = userSolutions.slice().sort((a, b) => {
											const indexA = exerciseOrderMap.get(a.exercise) ?? Infinity;
											const indexB = exerciseOrderMap.get(b.exercise) ?? Infinity;
											return indexA - indexB;
										});
										setUserSolutions(orderedSolutions);
									} else {
										setUserSolutions(userSolutions);
									}
									setShuffles([]);
									if (index === undefined) {
										setFlags({ reviewAll: true });
										setModuleIndex(0);
										setExam(exams[0]);
									} else {
										setModuleIndex(index);
										setExam(exams[index]);
									}
									setShowSolutions(true);
									setQuestionIndex(0);
									setExerciseIndex(0);
									setPartIndex(0);
								}}
								scores={aggregateScoresByModule()}
								practiceScores={aggregateScoresByModule(true)}
							/>}
						{/* Exam is on going, display it and the abandon modal */}
						{isExamLoaded && moduleIndex !== -1 && (
							<>
								{exam && CurrentExam && <CurrentExam exam={exam} showSolutions={showSolutions} />}
								{!showSolutions && <AbandonPopup
									isOpen={showAbandonPopup}
									abandonPopupTitle="Leave Exercise"
									abandonPopupDescription="Are you sure you want to leave the exercise? Your progress will be saved and this exam can be resumed on the Dashboard."
									abandonConfirmButtonText="Confirm"
									onAbandon={onAbandon}
									onCancel={() => setShowAbandonPopup(false)}
								/>
								}
							</>
						)}
					</>
				</Layout>
			)}
		</>
	);
}
