import "server-only";

import { randomUUID, createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { AppError } from "@/server/errors";

export const kycDocumentTypes = ["IDENTITY_FRONT", "IDENTITY_BACK", "PROOF_OF_ADDRESS", "SUPPORTING_DOCUMENT"] as const;
export type KycDocumentType = (typeof kycDocumentTypes)[number];

const MAX_FILE_SIZE = 8 * 1024 * 1024;
const MAX_FILES_PER_REQUEST = 5;
const STORAGE_ROOT = path.join(process.cwd(), ".local", "kyc-documents");

type PreparedKycUpload = {
  documentType: KycDocumentType;
  originalName: string;
  mimeType: string;
  size: number;
  checksum: string;
  storageKey: string;
  bytes: Buffer;
};

export async function prepareKycUploads(input: {
  files: File[];
  userId: string;
  caseId: string;
  documentType: KycDocumentType;
}) {
  if (!input.files.length) {
    throw new AppError("Aucun fichier reçu.", 422, "KYC_FILE_REQUIRED");
  }
  if (input.files.length > MAX_FILES_PER_REQUEST) {
    throw new AppError(`Maximum ${MAX_FILES_PER_REQUEST} fichiers par envoi.`, 422, "KYC_TOO_MANY_FILES");
  }

  const prepared: PreparedKycUpload[] = [];
  for (const file of input.files) {
    prepared.push(await prepareKycUpload({ file, userId: input.userId, caseId: input.caseId, documentType: input.documentType }));
  }

  return prepared;
}

export async function persistKycUploads(files: PreparedKycUpload[]) {
  for (const file of files) {
    const absolutePath = storagePath(file.storageKey);
    await mkdir(path.dirname(absolutePath), { recursive: true, mode: 0o700 });
    await writeFile(absolutePath, file.bytes, { flag: "wx", mode: 0o600 });
  }
}

export async function readKycUpload(storageKey: string) {
  return readFile(storagePath(storageKey));
}

export function kycUploadMimeType(storageKey: string) {
  const extension = path.extname(storageKey).toLowerCase();
  if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
  if (extension === ".png") return "image/png";
  if (extension === ".webp") return "image/webp";
  if (extension === ".pdf") return "application/pdf";
  return "application/octet-stream";
}

async function prepareKycUpload(input: {
  file: File;
  userId: string;
  caseId: string;
  documentType: KycDocumentType;
}): Promise<PreparedKycUpload> {
  if (input.file.size <= 0) {
    throw new AppError("Le fichier est vide.", 422, "KYC_EMPTY_FILE");
  }
  if (input.file.size > MAX_FILE_SIZE) {
    throw new AppError("Le fichier dépasse la limite de 8 Mo.", 413, "KYC_FILE_TOO_LARGE");
  }

  const bytes = Buffer.from(await input.file.arrayBuffer());
  const detected = detectFile(bytes);
  if (!detected) {
    throw new AppError("Format refusé. Formats acceptés : JPG, PNG, WEBP ou PDF.", 422, "KYC_FILE_TYPE_UNSUPPORTED");
  }

  if (input.file.type && !acceptedMimeAliases(detected.mimeType).includes(input.file.type)) {
    throw new AppError("Le type du fichier ne correspond pas à son contenu.", 422, "KYC_FILE_SIGNATURE_MISMATCH");
  }

  const checksum = createHash("sha256").update(bytes).digest("hex");
  const storageKey = path.posix.join(input.userId, input.caseId, `${randomUUID()}.${detected.extension}`);

  return {
    documentType: input.documentType,
    originalName: safeOriginalName(input.file.name),
    mimeType: detected.mimeType,
    size: input.file.size,
    checksum,
    storageKey,
    bytes
  };
}

function storagePath(storageKey: string) {
  const resolved = path.join(STORAGE_ROOT, storageKey);
  if (!resolved.startsWith(STORAGE_ROOT)) {
    throw new AppError("Chemin de stockage invalide.", 422, "KYC_STORAGE_KEY_INVALID");
  }
  return resolved;
}

function detectFile(bytes: Buffer) {
  if (bytes.length >= 4 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
    return { mimeType: "image/jpeg", extension: "jpg" };
  }
  if (bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
    return { mimeType: "image/png", extension: "png" };
  }
  if (bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") {
    return { mimeType: "image/webp", extension: "webp" };
  }
  if (bytes.length >= 5 && bytes.subarray(0, 5).toString("ascii") === "%PDF-") {
    return { mimeType: "application/pdf", extension: "pdf" };
  }
  return null;
}

function acceptedMimeAliases(mimeType: string) {
  if (mimeType === "image/jpeg") return ["image/jpeg", "image/jpg"];
  return [mimeType];
}

function safeOriginalName(name: string) {
  return name.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 140) || "document";
}
