// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { getDownloadURL, getStorage, ref } from "firebase/storage";
import { app, storage } from "@/firebase";
import axios from "axios";
import { requestUser } from "@/utils/api";
import { checkAccess } from "@/utils/permissions";
import { EntityWithRoles } from "@/interfaces/entity";
import { Stat, StudentUser } from "@/interfaces/user";
import { Assignment, AssignmentResult } from "@/interfaces/results";
import { Exam } from "@/interfaces/exam";
import { capitalize, groupBy, uniqBy } from "lodash";
import { findBy, mapBy } from "@/utils";
import ExcelJS from "exceljs";
import moment from "moment";
import { Session } from "@/hooks/useSessions";
import { getGradingSystemByEntity } from "@/utils/grading.be";
import { getGradingLabel } from "@/utils/score";

export default withIronSessionApiRoute(handler, sessionOptions);

interface Item {
	student: StudentUser
	result: AssignmentResult
	assignment: Assignment
	exams: Exam[]
	session?: Session
}

interface Body {
	entities: EntityWithRoles[]
	items: Item[]
	assignments: Assignment[]
	startDate: Date
	endDate: Date
}

interface EntityInformation {
	entity: EntityWithRoles
	exams: Exam[]
	numberOfAssignees: number
	numberOfSubmissions: number
	numberOfAbsentees: number
	assignment: Assignment
	items: Item[]
}

async function handler(req: NextApiRequest, res: NextApiResponse) {
	if (req.method !== "POST") return res.status(404).json({ ok: false })

	const user = await requestUser(req, res)
	if (!user) return res.status(401).json({ ok: false });
	if (!checkAccess(user, ['admin', 'developer', 'mastercorporate', 'corporate'])) return res.status(403).json({ ok: false });

	const { entities, items, assignments } = req.body as Body
	const entityInformations: EntityInformation[] = []

	for (const entity of entities) {
		const entityItems = items.filter(i => i.assignment.entity === entity.id)
		const groupedByAssignments = groupBy(entityItems, (a) => a.assignment.id)
		for (const assignmentID of Object.keys(groupedByAssignments)) {
			const assignmentItems = groupedByAssignments[assignmentID]
			const assignment = findBy(assignments, 'id', assignmentID)!
			const assignmentExams =
				uniqBy(assignmentItems.flatMap(a => a.exams.map(e => ({ ...e, moduleID: `${e.id}_${e.module}` }))), 'moduleID')

			const assignmentEntityInformation: EntityInformation = {
				entity,
				exams: assignmentExams,
				numberOfAssignees: assignmentItems.length,
				numberOfSubmissions: assignmentItems.filter(x => !!x.result).length,
				numberOfAbsentees: assignmentItems.filter(x => !x.result).length,
				assignment,
				items: assignmentItems
			}

			entityInformations.push(assignmentEntityInformation)
		}
	}

	const workbook = new ExcelJS.Workbook();
	const worksheet = workbook.addWorksheet("Statistical");

	for (const e of entityInformations) {
		await addEntityInformationToWorksheet(worksheet, e)
	}

	const buffer = await workbook.xlsx.writeBuffer()
	res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
	res.status(200).send(buffer);
}

const addEntityInformationToWorksheet = async (worksheet: ExcelJS.Worksheet, entityInformation: EntityInformation) => {
	const data = [
		['Entity', undefined, undefined, entityInformation.entity.label],
		['Assignment', undefined, undefined, entityInformation.assignment.name],
		['Date of the Assignment', undefined, undefined, moment(entityInformation.assignment.startDate).format("DD/MM/YYYY")],
		['Exams', undefined, undefined, mapBy(entityInformation.exams, 'id').join(', ')],
		['Modules', undefined, undefined, entityInformation.exams.map(e => capitalize(e.module)).join(', ')],
		['Number of Assignees', undefined, undefined, entityInformation.numberOfAssignees],
		['Number of Submissions', undefined, undefined, entityInformation.numberOfSubmissions],
		['Number of Absentees', undefined, undefined, entityInformation.numberOfAbsentees]
	]

	const dataRows = worksheet.addRows(data);
	dataRows.forEach(row => row.getCell(1).font = { bold: true, color: { argb: "ffffffff" } })
	dataRows.forEach(row => row.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: "ff674ea7" } })
	dataRows.forEach(row => worksheet.mergeCells(`${row.getCell(1).address}:${row.getCell(3).address}`))
	dataRows.forEach(row => row.getCell(4).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: "FFD9D2E9" } })
	dataRows.forEach(row => worksheet.mergeCells(`${row.getCell(4).address}:${row.getCell(7).address}`))

	worksheet.addRows([[], []]);
	const gradingSystem = await getGradingSystemByEntity(entityInformation.entity.id)

	for (const exam of entityInformation.exams) {
		const examRow = worksheet.addRow([`${capitalize(exam.module)} Exam`, undefined, exam.id])
		examRow.getCell(1).font = { bold: true, color: { argb: "ffffffff" } }
		examRow.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: "ff674ea7" } }
		examRow.getCell(3).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: "FFD9D2E9" } }

		worksheet.mergeCells(`${examRow.getCell(1).address}:${examRow.getCell(2).address}`)
		worksheet.mergeCells(`${examRow.getCell(3).address}:${examRow.getCell(6).address}`)

		const parts = exam.module === "level" || exam.module === "listening" || exam.module === "reading" ? exam.parts : []

		const header = worksheet.addRow([
			"#",
			"Name",
			"E-mail",
			"Student ID",
			"Passport/ID",
			"Gender",
			"Finished at",
			"Score",
			...(exam.module === "level" ? ["Grade"] : []),
			...parts.map((_, i) => `Part ${i + 1}`)
		])
		header.font = { bold: true, color: { argb: "FFFFFFFF" } }
		header.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: "FFD9D2E9" } }

		const examItems =
			entityInformation.items
				.filter(i => !!i.result)
				.map(i => ({
					...i,
					result: { ...i.result, stats: i.result.stats.filter(x => x.exam === exam.id) },
				}))

		const orderedItems = examItems.sort((a, b) => {
			const aTotalScore = a.result.stats.filter(x => !x.isPractice).reduce((acc, curr) => acc + curr.score.correct, 0)
			const bTotalScore = b.result.stats.filter(x => !x.isPractice).reduce((acc, curr) => acc + curr.score.correct, 0)

			return bTotalScore - aTotalScore
		})

		const itemRows = orderedItems.map((item, index) => {
			const { total, correct } = calculateScore(item.result.stats)
			const score = `${correct} / ${total}`

			const finishTimestamp = [...item.result.stats].sort((a, b) => b.date - a.date).shift()?.date || -1
			const finishDate = finishTimestamp === -1 ? "N/A" : moment(new Date(finishTimestamp)).format("DD/MM/YYYY HH:mm")

			const grade = getGradingLabel(correct, gradingSystem.steps)

			return [
				index + 1,
				item.student.name,
				item.student.email,
				item.student.studentID || "N/A",
				item.student.demographicInformation?.passport_id || "N/A",
				item.student.demographicInformation?.gender || "N/A",
				finishDate,
				score,
				...(exam.module === "level" ? [grade] : []),
				...parts.map((part) => {
					const exerciseIDs = mapBy(part.exercises, 'id')
					const { total, correct } = calculateScore(item.result.stats.filter(s => exerciseIDs.includes(s.exercise)))

					return `${correct} / ${total}`
				})
			]
		})

		worksheet.addRows(itemRows)
		worksheet.addRows([[]]);
	}
	worksheet.addRows([[], []]);
}

const calculateScore = (stats: Stat[]) => {
	const total = stats.filter(x => !x.isPractice).reduce((acc, curr) => acc + curr.score.total, 0)
	const correct = stats.filter(x => !x.isPractice).reduce((acc, curr) => acc + curr.score.correct, 0)

	return { total, correct }
}

export const config = {
	api: {
		bodyParser: {
			sizeLimit: '20mb',
		},
	},
};
