import type { NextApiRequest, NextApiResponse } from "next";
import { app, storage } from "@/firebase";
import {
  getFirestore,
  doc,
  getDoc,
  updateDoc,
  getDocs,
  query,
  collection,
  where,
  documentId,
} from "firebase/firestore";
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
import { CorporateUser, MasterCorporateUser } from "@/interfaces/user";
import { User } from "@/interfaces/user";
import { Module } from "@/interfaces";
import moment from "moment-timezone";
import ExcelJS from "exceljs";
import { getStudentGroupsForUsersWithoutAdmin } from "@/utils/groups.be";
import { getSpecificUsers, getUser } from "@/utils/users.be";
import { getUserName } from "@/utils/users";
interface GroupScoreSummaryHelper {
  score: [number, number];
  label: string;
  sessions: string[];
}

interface AssignmentData {
  assigner: string;
  assignees: string[];
  results: any;
  exams: { module: Module }[];
  startDate: string;
  excel: {
    path: string;
    version: string;
  };
  name: string;
}

const db = getFirestore(app);

export default withIronSessionApiRoute(handler, sessionOptions);

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

function logWorksheetData(worksheet: any) {
  worksheet.eachRow((row: any, rowNumber: number) => {
    console.log(`Row ${rowNumber}:`);
    row.eachCell((cell: any, colNumber: number) => {
      console.log(`  Cell ${colNumber}: ${cell.value}`);
    });
  });
}

function commonExcel({
  data,
  userName,
  users,
  sectionName,
  customTable,
  customTableHeaders,
  renderCustomTableData,
}: {
  data: AssignmentData;
  userName: string;
  users: User[];
  sectionName: string;
  customTable: string[][];
  customTableHeaders: string[];
  renderCustomTableData: (data: any) => string[];
}) {
  const allStats = data.results.flatMap((r: any) => r.stats);

  const uniqueExercises = [...new Set(allStats.map((s: any) => s.exercise))];

  const assigneesData = data.assignees
    .map((assignee: string) => {
      const userStats = allStats.filter((s: any) => s.user === assignee);
      const dates = userStats.map((s: any) => moment(s.date));
      const user = users.find((u) => u.id === assignee);
      return {
        userId: assignee,
        // added some default values in case the user is not found
        // could it be possible to have an assigned user deleted from the database?
        user: user || {
          name: "Unknown",
          email: "Unknown",
          demographicInformation: { passportId: "Unknown", gender: "Unknown" },
        },
        ...userStats.reduce(
          (acc: any, curr: any) => {
            return {
              ...acc,
              correct: acc.correct + curr.score.correct,
              missing: acc.missing + curr.score.missing,
              total: acc.total + curr.score.total,
            };
          },
          { correct: 0, missing: 0, total: 0 }
        ),
        firstDate: moment.min(...dates),
        lastDate: moment.max(...dates),
        stats: userStats,
      };
    })
    .sort((a, b) => b.correct - a.correct);

  const results = assigneesData.map((r: any) => r.correct);
  const highestScore = Math.max(...results);
  const lowestScore = Math.min(...results);
  const averageScore = results.reduce((a, b) => a + b, 0) / results.length;
  const firstDate = moment.min(assigneesData.map((r: any) => r.firstDate));
  const lastDate = moment.max(assigneesData.map((r: any) => r.lastDate));

  const firstSectionData = [
    {
      label: sectionName,
      value: userName,
    },
    {
      label: "Report Download date :",
      value: moment().format("DD/MM/YYYY"),
    },
    { label: "Test Information :", value: data.name },
    {
      label: "Date of Test :",
      value: moment(data.startDate).format("DD/MM/YYYY"),
    },
    { label: "Number of Candidates  :", value: data.assignees.length },
    { label: "Highest score  :", value: highestScore },
    { label: "Lowest score  :", value: lowestScore },
    { label: "Average score  :", value: averageScore },
    { label: "", value: "" },
    {
      label: "Date and time of First submission :",
      value: firstDate.format("DD/MM/YYYY"),
    },
    {
      label: "Date and time of Last submission :",
      value: lastDate.format("DD/MM/YYYY"),
    },
  ];

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

  // Populate the worksheet with the data
  firstSectionData.forEach(({ label, value }, index) => {
    worksheet.getCell(`A${index + 1}`).value = label; // First column (labels)
    worksheet.getCell(`B${index + 1}`).value = value; // Second column (values)
  });

  // added empty arrays to force row spacings
  const customTableAndLine = [[], ...customTable, []];
  customTableAndLine.forEach((row: string[], index) => {
    worksheet.addRow(row);
  });

  // Define the static part of the headers (before "Test Sections")
  const staticHeaders = [
    "Sr N",
    "Candidate ID",
    "First and Last Name",
    "Passport/ID",
    "Email ID",
    "Gender",
    ...customTableHeaders,
  ];

  // Define additional headers after "Test Sections"
  const additionalHeaders = ["Time Spent", "Date", "Score"];

  // Calculate the dynamic columns based on the testSectionsArray
  const testSectionHeaders = uniqueExercises.map(
    (section, index) => `Part ${index + 1}`
  );

  const tableColumnHeadersFirstPart = [
    ...staticHeaders,
    ...uniqueExercises.map((a) => "Test Sections"),
  ];
  // Add the main header row, merging static columns and "Test Sections"
  const tableColumnHeaders = [
    ...tableColumnHeadersFirstPart,
    ...additionalHeaders,
  ];
  worksheet.addRow(tableColumnHeaders);

  // 1 headers rows
  const startIndexTable =
    firstSectionData.length + customTableAndLine.length + 1;

  // // Merge "Test Sections" over dynamic number of columns
  // const tableColumns = staticHeaders.length + numberOfTestSections;

  // K10:M12 = 10,11,12,13
  // horizontally group Test Sections

  // if there are test section headers to even merge:
  if (testSectionHeaders.length > 1) {
    worksheet.mergeCells(
      startIndexTable,
      staticHeaders.length + 1,
      startIndexTable,
      tableColumnHeadersFirstPart.length
    );
  }

  // Add the dynamic second and third header rows for test sections and sub-columns
  worksheet.addRow([
    ...Array(staticHeaders.length).fill(""),
    ...testSectionHeaders,
    "",
    "",
    "",
  ]);
  worksheet.addRow([
    ...Array(staticHeaders.length).fill(""),
    ...uniqueExercises.map(() => "Grammar & Vocabulary"),
    "",
    "",
    "",
  ]);
  worksheet.addRow([
    ...Array(staticHeaders.length).fill(""),
    ...uniqueExercises.map(
      (exercise) => allStats.find((s: any) => s.exercise === exercise).type
    ),
    "",
    "",
    "",
  ]);

  // vertically group based on the part, exercise and type
  staticHeaders.forEach((header, index) => {
    worksheet.mergeCells(
      startIndexTable,
      index + 1,
      startIndexTable + 3,
      index + 1
    );
  });

  assigneesData.forEach((data, index) => {
    worksheet.addRow([
      index + 1,
      data.userId,
      data.user.name,
      data.user.demographicInformation?.passportId,
      data.user.email,
      data.user.demographicInformation?.gender,
      ...renderCustomTableData(data),
      ...uniqueExercises.map((exercise) => {
        const score = data.stats.find(
          (s: any) => s.exercise === exercise && s.user === data.userId
        ).score;
        return `${score.correct}/${score.total}`;
      }),
      `${Math.ceil(
        data.stats.reduce((acc: number, curr: any) => acc + curr.timeSpent, 0) /
          60
      )} minutes`,
      data.lastDate.format("DD/MM/YYYY HH:mm"),
      data.correct,
    ]);
  });

  worksheet.addRow([""]);
  worksheet.addRow([""]);

  for (let i = 0; i < tableColumnHeaders.length; i++) {
    worksheet.getColumn(i + 1).width = 30;
  }

  // Apply styles to the headers
  [startIndexTable].forEach((rowNumber) => {
    worksheet.getRow(rowNumber).eachCell((cell) => {
      if (cell.value) {
        cell.fill = {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFBFBFBF" }, // Grey color for headers
        };
        cell.font = { bold: true };
        cell.alignment = { vertical: "middle", horizontal: "center" };
      }
    });
  });

  worksheet.addRow(["Printed by: Confidential Information"]);
  worksheet.addRow(["info@encoach.com"]);

  // Convert workbook to Buffer (Node.js) or Blob (Browser)
  return workbook.xlsx.writeBuffer();
}

function corporateAssignment(
  user: CorporateUser,
  data: AssignmentData,
  users: User[]
) {
  return commonExcel({
    data,
    userName: user.corporateInformation?.companyInformation?.name || "",
    users,
    sectionName: "Corporate Name :",
    customTable: [],
    customTableHeaders: [],
    renderCustomTableData: () => [],
  });
}

async function mastercorporateAssignment(
  user: MasterCorporateUser,
  data: AssignmentData,
  users: User[]
) {
  const userGroups = await getStudentGroupsForUsersWithoutAdmin(
    user.id,
    data.assignees
  );
  const adminUsers = [...new Set(userGroups.map((g) => g.admin))];

  const userGroupsParticipants = userGroups.flatMap((g) => g.participants);
  const adminsData = await getSpecificUsers(adminUsers);
  const companiesData = adminsData.map((user) => {
    const name = getUserName(user);
    const users = userGroupsParticipants.filter((p) =>
      data.assignees.includes(p)
    );

    const stats = data.results
      .flatMap((r: any) => r.stats)
      .filter((s: any) => users.includes(s.user));
    const correct = stats.reduce(
      (acc: number, s: any) => acc + s.score.correct,
      0
    );
    const total = stats.reduce(
      (acc: number, curr: any) => acc + curr.score.total,
      0
    );

    return {
      name,
      correct,
      total,
    };
  });

  const customTable = [
    ...companiesData,
    {
      name: "Total",
      correct: companiesData.reduce((acc, curr) => acc + curr.correct, 0),
      total: companiesData.reduce((acc, curr) => acc + curr.total, 0),
    },
  ].map((c) => [c.name, `${c.correct}/${c.total}`]);

  const customTableHeaders = [
    { name: "Corporate", helper: (data: any) => data.user.corporateName },
  ];
  return commonExcel({
    data,
    userName: user.corporateInformation?.companyInformation?.name || "",
    users: users.map((u) => {
      const userGroup = userGroups.find((g) => g.participants.includes(u.id));
      const admin = adminsData.find((a) => a.id === userGroup?.admin);
      return {
        ...u,
        corporateName: getUserName(admin),
      };
    }),
    sectionName: "Master Corporate Name :",
    customTable: [["Corporate Summary"], ...customTable],
    customTableHeaders: customTableHeaders.map((h) => h.name),
    renderCustomTableData: (data) =>
      customTableHeaders.map((h) => h.helper(data)),
  });
}

async function post(req: NextApiRequest, res: NextApiResponse) {
  // verify if it's a logged user that is trying to export
  if (req.session.user) {
    const { id } = req.query as { id: string };

    const docSnap = await getDoc(doc(db, "assignments", id));
    const data = docSnap.data() as AssignmentData;
    if (!data) {
      res.status(400).end();
      return;
    }

    // if (
    //   data.excel &&
    //   data.excel.path &&
    //   data.excel.version === process.env.EXCEL_VERSION
    // ) {
    //   // if it does, return the excel url
    //   const fileRef = ref(storage, data.excel.path);
    //   const url = await getDownloadURL(fileRef);
    //   res.status(200).end(url);
    //   return;
    // }

    const docsSnap = await getDocs(
      query(collection(db, "users"), where(documentId(), "in", data.assignees))
    );
    const users = docsSnap.docs.map((d) => ({
      ...d.data(),
      id: d.id,
    })) as User[];

    const docUser = await getDoc(doc(db, "users", data.assigner));
    if (docUser.exists()) {
      // we'll need the user in order to get the user data (name, email, focus, etc);
      const user = docUser.data() as User;

      // generate the file ref for storage
      const fileName = `${Date.now().toString()}.xlsx`;
      const refName = `assignment_report/${fileName}`;
      const fileRef = ref(storage, refName);

      const getExcelFn = () => {
        switch (user.type) {
          case "teacher":
          case "corporate":
            return corporateAssignment(user as CorporateUser, data, users);
          case "mastercorporate":
            return mastercorporateAssignment(
              user as MasterCorporateUser,
              data,
              users
            );
          default:
            throw new Error("Invalid user type");
        }
      };
      const buffer = await getExcelFn();

      // upload the pdf to storage
      const snapshot = await uploadBytes(fileRef, buffer, {
        contentType:
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      });

      // update the stats entries with the pdf url to prevent duplication
      await updateDoc(docSnap.ref, {
        excel: {
          path: refName,
          version: process.env.EXCEL_VERSION,
        },
      });
      const url = await getDownloadURL(fileRef);
      res.status(200).end(url);

      return;
    }
  }

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