import archiver from "archiver";
import { PassThrough, Readable } from "stream";

import { getSessionUser } from "@/lib/auth";
import {
  getMailboxFolderBackupManifestByEmail,
  streamMailboxFolderBackupPartByEmail,
} from "@/lib/official-mail";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

function sanitizeArchiveEntrySegment(value: string) {
  return value
    .normalize("NFKC")
    .replace(/[\\/:*?"<>|\u0000-\u001f]/g, "_")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, 100) || "제목 없음";
}

function formatArchiveEntryDate(date: Date) {
  if (Number.isNaN(date.getTime())) return "날짜없음";
  const pad = (value: number) => String(value).padStart(2, "0");

  return [
    date.getFullYear(),
    pad(date.getMonth() + 1),
    pad(date.getDate()),
    "_",
    pad(date.getHours()),
    pad(date.getMinutes()),
    pad(date.getSeconds()),
  ].join("");
}

function contentDisposition(fileName: string) {
  const fallback = fileName.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
  return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
}

function backupErrorResponse(error: unknown) {
  const detail = error instanceof Error ? error.message : "mailbox-folder-backup-failed";
  const message =
    detail === "folder-not-found"
      ? "백업할 메일함을 찾을 수 없습니다."
      : detail === "mailbox-folder-backup-part-not-found"
        ? "백업 파일을 찾을 수 없습니다."
        : detail === "mailbox-auth-missing" || detail === "mailbox-auth-invalid"
          ? "메일함 연결 정보를 확인할 수 없습니다."
          : "메일함 백업을 준비하지 못했습니다. 잠시 후 다시 시도해주세요.";

  return Response.json({ error: detail, message, ok: false }, { status: 400 });
}

export async function GET(request: Request) {
  const user = await getSessionUser();
  if (!user) return Response.json({ error: "unauthorized", ok: false }, { status: 401 });

  const searchParams = new URL(request.url).searchParams;
  const folder = searchParams.get("folder")?.trim() ?? "";
  const download = searchParams.get("download") === "1";

  if (!folder) {
    return Response.json(
      { error: "folder-required", message: "백업할 메일함을 선택해주세요.", ok: false },
      { status: 400 },
    );
  }

  try {
    const manifest = await getMailboxFolderBackupManifestByEmail(user.email, folder);

    if (!download) {
      return Response.json({ manifest, ok: true });
    }

    const partIndex = Number(searchParams.get("part") ?? "1");
    const part = manifest.parts.find((candidate) => candidate.index === partIndex);

    if (!part) {
      throw new Error("mailbox-folder-backup-part-not-found");
    }

    const output = new PassThrough();
    const archive = archiver("zip", {
      forceZip64: true,
      zlib: { level: 1 },
    });

    archive.on("error", (error) => output.destroy(error));
    archive.on("warning", (error) => {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") output.destroy(error);
    });
    archive.pipe(output);

    queueMicrotask(() => {
      void (async () => {
        try {
          await streamMailboxFolderBackupPartByEmail(
            user.email,
            folder,
            partIndex,
            async (message) => {
              const entryName = [
                formatArchiveEntryDate(message.receivedAt),
                String(message.remoteUid).padStart(8, "0"),
                sanitizeArchiveEntrySegment(message.subject),
              ].join("_");

              const archiveEntryName = `${entryName}.eml`;
              await new Promise<void>((resolve, reject) => {
                const handleEntry = (entry: { name: string }) => {
                  if (entry.name !== archiveEntryName) return;
                  archive.off("entry", handleEntry);
                  archive.off("error", handleError);
                  resolve();
                };
                const handleError = (error: Error) => {
                  archive.off("entry", handleEntry);
                  reject(error);
                };
                archive.once("error", handleError);
                archive.on("entry", handleEntry);
                archive.append(message.source, {
                  date: message.receivedAt,
                  name: archiveEntryName,
                });
              });
            },
          );
          await archive.finalize();
        } catch (error) {
          archive.abort();
          output.destroy(error instanceof Error ? error : new Error("mailbox-folder-backup-failed"));
        }
      })();
    });

    return new Response(Readable.toWeb(output) as unknown as ReadableStream, {
      headers: {
        "Cache-Control": "private, no-store",
        "Content-Disposition": contentDisposition(part.fileName),
        "Content-Type": "application/zip",
        "X-Content-Type-Options": "nosniff",
      },
    });
  } catch (error) {
    return backupErrorResponse(error);
  }
}
