import { ApiPermissions } from "@shared/apiPermissions";
import express, { Request, Response } from "express";
import { requireGuildPermission } from "../permissions";
import { clientError, ok } from "../responses";
import { GuildCases } from "../../data/GuildCases";
import { z } from "zod";
import { Case } from "../../data/entities/Case";
import { rateLimit } from "../rateLimits";
import { MINUTES } from "../../utils";
import moment from "moment-timezone";

const caseHandlingModeSchema = z.union([
  z.literal("replace"),
  z.literal("bumpExistingCases"),
  z.literal("bumpImportedCases"),
]);

type CaseHandlingMode = z.infer<typeof caseHandlingModeSchema>;

const caseNoteData = z.object({
  mod_id: z.string(),
  mod_name: z.string(),
  body: z.string(),
  created_at: z.string(),
});

const caseData = z.object({
  case_number: z.number(),
  user_id: z.string(),
  user_name: z.string(),
  mod_id: z.nullable(z.string()),
  mod_name: z.nullable(z.string()),
  type: z.number(),
  created_at: z.string(),
  is_hidden: z.boolean(),
  pp_id: z.nullable(z.string()),
  pp_name: z.nullable(z.string()),

  notes: z.array(caseNoteData),
});

const importExportData = z.object({
  cases: z.array(caseData),
});
type TImportExportData = z.infer<typeof importExportData>;

export function initGuildsImportExportAPI(guildRouter: express.Router) {
  const importExportRouter = express.Router();

  importExportRouter.get(
    "/:guildId/pre-import",
    requireGuildPermission(ApiPermissions.ManageAccess),
    async (req: Request, res: Response) => {
      const guildCases = GuildCases.getGuildInstance(req.params.guildId);
      const minNum = await guildCases.getMinCaseNumber();
      const maxNum = await guildCases.getMaxCaseNumber();

      return {
        minCaseNumber: minNum,
        maxCaseNumber: maxNum,
      };
    },
  );

  importExportRouter.post(
    "/:guildId/import",
    requireGuildPermission(ApiPermissions.ManageAccess),
    rateLimit(
      (req) => `import-${req.params.guildId}`,
      5 * MINUTES,
      "A single server can only import data once every 5 minutes",
    ),
    async (req: Request, res: Response) => {
      let data: TImportExportData;
      try {
        data = importExportData.parse(req.body.data);
      } catch (err) {
        const prettyMessage = `${err.issues[0].code}: expected ${err.issues[0].expected}, received ${
          err.issues[0].received
        } at /${err.issues[0].path.join("/")}`;
        return clientError(res, `Invalid import data format: ${prettyMessage}`);
        return;
      }

      let caseHandlingMode: CaseHandlingMode;
      try {
        caseHandlingMode = caseHandlingModeSchema.parse(req.body.caseHandlingMode);
      } catch (err) {
        return clientError(res, "Invalid case handling mode");
        return;
      }

      const guildCases = GuildCases.getGuildInstance(req.params.guildId);

      // Prepare cases
      if (caseHandlingMode === "replace") {
        // Replace existing cases
        await guildCases.deleteAllCases();
      } else if (caseHandlingMode === "bumpExistingCases") {
        // Bump existing numbers
        const maxNumberInData = data.cases.reduce((max, theCase) => Math.max(max, theCase.case_number), 0);
        await guildCases.bumpCaseNumbers(maxNumberInData);
      } else if (caseHandlingMode === "bumpImportedCases") {
        const maxExistingNumber = await guildCases.getMaxCaseNumber();
        for (const theCase of data.cases) {
          theCase.case_number += maxExistingNumber;
        }
      }

      // Import cases
      for (const theCase of data.cases) {
        const insertData: any = {
          ...theCase,
          is_hidden: theCase.is_hidden ? 1 : 0,
          guild_id: req.params.guildId,
          notes: undefined,
        };

        const caseInsertData = await guildCases.createInternal(insertData);
        for (const note of theCase.notes) {
          await guildCases.createNote(caseInsertData.identifiers[0].id, note);
        }
      }

      ok(res);
    },
  );

  const exportBatchSize = 500;
  importExportRouter.post(
    "/:guildId/export",
    requireGuildPermission(ApiPermissions.ManageAccess),
    rateLimit(
      (req) => `export-${req.params.guildId}`,
      5 * MINUTES,
      "A single server can only export data once every 5 minutes",
    ),
    async (req: Request, res: Response) => {
      const guildCases = GuildCases.getGuildInstance(req.params.guildId);

      const data: TImportExportData = {
        cases: [],
      };

      let n = 0;
      let cases: Case[];
      do {
        cases = await guildCases.getExportCases(n, exportBatchSize);
        n += cases.length;

        for (const theCase of cases) {
          data.cases.push({
            case_number: theCase.case_number,
            user_id: theCase.user_id,
            user_name: theCase.user_name,
            mod_id: theCase.mod_id,
            mod_name: theCase.mod_name,
            type: theCase.type,
            created_at: theCase.created_at,
            is_hidden: theCase.is_hidden,
            pp_id: theCase.pp_id,
            pp_name: theCase.pp_name,

            notes: theCase.notes.map((note) => ({
              mod_id: note.mod_id,
              mod_name: note.mod_name,
              body: note.body,
              created_at: note.created_at,
            })),
          });
        }
      } while (cases.length === exportBatchSize);

      const filename = `export_${req.params.guildId}_${moment().format("YYYY-MM-DD_HH-mm-ss")}.json`;
      const serialized = JSON.stringify(data, null, 2);

      res.setHeader("Content-Disposition", `attachment; filename=${filename}`);
      res.setHeader("Content-Type", "application/octet-stream");
      res.setHeader("Content-Length", serialized.length);
      res.send(serialized);
    },
  );

  guildRouter.use("/", importExportRouter);
}