// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next";
import {app} from "@/firebase";
import {getFirestore, collection, getDocs, query, where, setDoc, doc, getDoc} from "firebase/firestore";
import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {uuidv4} from "@firebase/util";
import {Module} from "@/interfaces";
import {getExams} from "@/utils/exams.be";
import {Exam, InstructorGender, Variant} from "@/interfaces/exam";
import {capitalize, flatten, uniqBy} from "lodash";
import {User} from "@/interfaces/user";
import moment from "moment";
import {sendEmail} from "@/email";
import { release } from "os";

const db = getFirestore(app);

export default withIronSessionApiRoute(handler, sessionOptions);

async function handler(req: NextApiRequest, res: NextApiResponse) {
	if (!req.session.user) {
		res.status(401).json({ok: false});
		return;
	}

	if (req.method === "GET") return GET(req, res);
	if (req.method === "POST") return POST(req, res);

	res.status(404).json({ok: false});
}

async function GET(req: NextApiRequest, res: NextApiResponse) {
	const q = query(collection(db, "assignments"));
	const snapshot = await getDocs(q);

	const docs = snapshot.docs.map((doc) => ({
		id: doc.id,
		...doc.data(),
	}));

	res.status(200).json(docs);
}

interface ExamWithUser {
	module: Module;
	id: string;
	assignee: string;
}

function getRandomIndex(arr: any[]): number {
	const randomIndex = Math.floor(Math.random() * arr.length);
	return randomIndex;
}

const generateExams = async (
	generateMultiple: Boolean,
	selectedModules: Module[],
	assignees: string[],
	variant?: Variant,
	instructorGender?: InstructorGender,
): Promise<ExamWithUser[]> => {
	if (generateMultiple) {
		// for optimization purposes, it would be better to create a new endpoint that returned the answers for all users at once
		const allExams = assignees.map(async (assignee) => {
			const selectedModulePromises = selectedModules.map(async (module: Module) => {
				try {
					const exams: Exam[] = await getExams(db, module, "true", assignee, variant, instructorGender);

					const exam = exams[getRandomIndex(exams)];
					if (exam) {
						return {module: exam.module, id: exam.id, assignee};
					}
					return null;
				} catch (e) {
					console.error(e);
					return null;
				}
			}, []);
			const newModules = await Promise.all(selectedModulePromises);

			return newModules;
		}, []);

		const exams = flatten(await Promise.all(allExams)).filter((x) => x !== null) as ExamWithUser[];
		return exams;
	}

	const selectedModulePromises = selectedModules.map(async (module: Module) => {
		const exams: Exam[] = await getExams(db, module, "false", undefined, variant, instructorGender);
		const exam = exams[getRandomIndex(exams)];

		if (exam) {
			return {module: exam.module, id: exam.id};
		}
		return null;
	});

	const exams = await Promise.all(selectedModulePromises);
	const examesFiltered = exams.filter((x) => x !== null) as ExamWithUser[];
	return flatten(assignees.map((assignee) => examesFiltered.map((exam) => ({...exam, assignee}))));
};

async function POST(req: NextApiRequest, res: NextApiResponse) {
	const {
		examIDs,
		selectedModules,
		assignees,
		// Generate multiple true would generate an unique exam for each user
		// false would generate the same exam for all users
		generateMultiple = false,
		variant,
		instructorGender,
		...body
	} = req.body as {
		examIDs?: {id: string; module: Module}[];
		selectedModules: Module[];
		assignees: string[];
		generateMultiple: Boolean;
		name: string;
		startDate: string;
		endDate: string;
		variant?: Variant;
		instructorGender?: InstructorGender;
	};

	const exams: ExamWithUser[] = !!examIDs
		? examIDs.flatMap((e) => assignees.map((a) => ({...e, assignee: a})))
		: await generateExams(generateMultiple, selectedModules, assignees, variant, instructorGender);

	if (exams.length === 0) {
		res.status(400).json({ok: false, error: "No exams found for the selected modules"});
		return;
	}

	await setDoc(doc(db, "assignments", uuidv4()), {
		assigner: req.session.user?.id,
		assignees,
		results: [],
		exams,
		instructorGender,
		released: false,
		...body,
	});

	res.status(200).json({ok: true});

	for (const assigneeID of assignees) {
		const assigneeSnapshot = await getDoc(doc(db, "users", assigneeID));
		if (!assigneeSnapshot.exists()) continue;

		const assignee = {id: assigneeID, ...assigneeSnapshot.data()} as User;
		const name = body.name;
		const teacher = req.session.user!;
		const examModulesLabel = uniqBy(exams, (x) => x.module)
			.map((x) => capitalize(x.module))
			.join(", ");
		const startDate = moment(body.startDate).format("DD/MM/YYYY - HH:mm");
		const endDate = moment(body.endDate).format("DD/MM/YYYY - HH:mm");

		await sendEmail(
			"assignment",
			{
				user: {name: assignee.name},
				assignment: {
					name,
					startDate,
					endDate,
					modules: examModulesLabel,
					assigner: teacher.name,
				},
				environment: process.env.ENVIRONMENT,
			},
			[assignee.email],
			"EnCoach - New Assignment!",
		);
	}
}
