import type { NextApiRequest, NextApiResponse } from "next";
import { storage } from "@/firebase";
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
import { AssignmentWithCorporateId } from "@/interfaces/results";
import moment from "moment-timezone";
import ExcelJS from "exceljs";
import { getSpecificUsers } from "@/utils/users.be";
import { checkAccess } from "@/utils/permissions";
import { getAssignmentsForCorporates } from "@/utils/assignments.be";
import { search } from "@/utils/search";
import { getGradingSystem } from "@/utils/grading.be";
import { User } from "@/interfaces/user";
import { calculateBandScore, getGradingLabel } from "@/utils/score";
import { Module } from "@/interfaces";

export default withIronSessionApiRoute(handler, sessionOptions);

interface TableData {
  user: string;
  email: string;
  correct: number;
  corporate: string;
  submitted: boolean;
  date: moment.Moment;
  assignment: string;
  corporateId: string;
  score: number;
  level: string;
  part1?: string;
  part2?: string;
  part3?: string;
  part4?: string;
  part5?: string;
}

async function handler(req: NextApiRequest, res: NextApiResponse) {
  // if (req.method === "GET") return get(req, res);
  if (req.method === "POST") return await post(req, res);
}

const searchFilters = [["email"], ["user"], ["userId"]];

async function post(req: NextApiRequest, res: NextApiResponse) {
  // verify if it's a logged user that is trying to export
  if (req.session.user) {
    if (
      !checkAccess(req.session.user, ["mastercorporate", "corporate", "developer", "admin"])
    ) {
      return res.status(403).json({ error: "Unauthorized" });
    }
    const {
      ids,
      startDate,
      endDate,
      searchText,
      displaySelection = true,
    } = req.body as {
      ids: string[];
      startDate?: string;
      endDate?: string;
      searchText: string;
      displaySelection?: boolean;
    };
    const startDateParsed = startDate ? new Date(startDate) : undefined;
    const endDateParsed = endDate ? new Date(endDate) : undefined;
    const assignments = await getAssignmentsForCorporates(
      ids,
      startDateParsed,
      endDateParsed
    );

    const assignmentUsers = [
      ...new Set(assignments.flatMap((a) => a.assignees)),
    ];
    const assigners = [...new Set(assignments.map((a) => a.assigner))];
    const users = await getSpecificUsers(assignmentUsers);
    const assignerUsers = await getSpecificUsers(assigners);

    const assignerUsersGradingSystems = await Promise.all(
      assignerUsers.map(async (user: User) => {
        const data = await getGradingSystem(user);
        // in this context I need to override as I'll have to match to the assigner
        return { ...data, user: user.id };
      })
    );

    const getGradingSystemHelper = (
      exams: { id: string; module: Module; assignee: string }[],
      assigner: string,
      user: User,
      correct: number,
      total: number
    ) => {
      if (exams.some((e) => e.module === "level")) {
        const gradingSystem = assignerUsersGradingSystems.find(
          (gs) => gs.user === assigner
        );
        if (gradingSystem) {
          const bandScore = calculateBandScore(
            correct,
            total,
            "level",
            user.focus
          );
          return {
            label: getGradingLabel(bandScore, gradingSystem?.steps || []),
            score: bandScore,
          };
        }
      }

      return { score: -1, label: "N/A" };
    };

    const tableResults = assignments
      .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => {
        const userResults = a.assignees.map((assignee) => {
          const userStats =
            a.results.find((r) => r.user === assignee)?.stats || [];
          const userData = users.find((u) => u.id === assignee);
          const corporateUser = users.find((u) => u.id === a.assigner);
          const correct = userStats.reduce((n, e) => n + e.score.correct, 0);
          const total = userStats.reduce((n, e) => n + e.score.total, 0);
          const { label: level, score } = getGradingSystemHelper(
            a.exams,
            a.assigner,
            userData!,
            correct,
            total
          );

          console.log("Level", level);
          const commonData = {
            user: userData?.name || "",
            email: userData?.email || "",
            userId: assignee,
            corporateId: a.corporateId,
            corporate: corporateUser?.name || "",
            assignment: a.name,
            level,
            score,
          };
          if (userStats.length === 0) {
            return {
              ...commonData,
              correct: 0,
              submitted: false,
              // date: moment(),
            };
          }

          const partsData = userStats.every((e) => e.module === "level")
            ? userStats.reduce((acc, e, index) => {
                return {
                  ...acc,
                  [`part${index}`]: `${e.score.correct}/${e.score.total}`,
                };
              }, {})
            : {};

          return {
            ...commonData,
            correct,
            submitted: true,
            date: moment.max(userStats.map((e) => moment(e.date))),
            ...partsData,
          };
        }) as TableData[];

        return [...accmA, ...userResults];
      }, [])
      .sort((a, b) => b.score - a.score);

    // Create a new workbook and add a worksheet
    const workbook = new ExcelJS.Workbook();
    const worksheet = workbook.addWorksheet("Master Statistical");

    const headers = [
      {
        label: "User",
        value: (entry: TableData) => entry.user,
      },
      {
        label: "Email",
        value: (entry: TableData) => entry.email,
      },
      ...(displaySelection
        ? [
            {
              label: "Corporate",
              value: (entry: TableData) => entry.corporate,
            },
          ]
        : []),
      {
        label: "Assignment",
        value: (entry: TableData) => entry.assignment,
      },
      {
        label: "Submitted",
        value: (entry: TableData) => (entry.submitted ? "Yes" : "No"),
      },
      {
        label: "Correct",
        value: (entry: TableData) => entry.correct,
      },
      {
        label: "Date",
        value: (entry: TableData) => entry.date?.format("YYYY/MM/DD") || "",
      },
      {
        label: "Level",
        value: (entry: TableData) => entry.level,
      },
      ...new Array(5).fill(0).map((_, index) => ({
        label: `Part ${index + 1}`,
        value: (entry: TableData) => {
          const key = `part${index}` as keyof TableData;
          return entry[key] || "";
        },
      })),
    ];

    const filteredSearch = searchText
      ? search(searchText, searchFilters, tableResults)
      : tableResults;

    worksheet.addRow(headers.map((h) => h.label));
    (filteredSearch as TableData[]).forEach((entry) => {
      worksheet.addRow(headers.map((h) => h.value(entry)));
    });

    // Convert workbook to Buffer (Node.js) or Blob (Browser)
    const buffer = await workbook.xlsx.writeBuffer();

    // generate the file ref for storage
    const fileName = `${Date.now().toString()}.xlsx`;
    const refName = `statistical/${fileName}`;
    const fileRef = ref(storage, refName);
    // upload the pdf to storage
    await uploadBytes(fileRef, buffer, {
      contentType:
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    });

    const url = await getDownloadURL(fileRef);
    res.status(200).end(url);
    return;
  }

  return res.status(401).json({ error: "Unauthorized" });
}
