/**
 * Lưu file người dùng tải lên (hiện chỉ có ảnh check-in).
 *
 * File nằm ngoài thư mục `public/` và được phục vụ qua route có kiểm tra đăng
 * nhập. Ảnh chụp tại điểm bán là dữ liệu kinh doanh — để trong `public/` nghĩa là
 * bất kỳ ai đoán đúng đường dẫn đều xem được.
 *
 * Khi lên production nhiều máy chủ, đổi hai hàm dưới đây sang S3/R2 là đủ; phần
 * còn lại của hệ thống chỉ biết tới đường dẫn dạng chuỗi.
 */
import { mkdir, writeFile, readFile, unlink } from "node:fs/promises";
import { join, extname, normalize } from "node:path";
import { randomUUID } from "node:crypto";

const UPLOAD_ROOT = process.env.UPLOAD_DIR ?? join(process.cwd(), "uploads");

/** Ảnh check-in (định dạng máy ảnh điện thoại) và tệp đính kèm hồ sơ khách hàng (PDF). */
const ALLOWED_TYPES = new Map([
  ["image/jpeg", ".jpg"],
  ["image/png", ".png"],
  ["image/webp", ".webp"],
  ["image/heic", ".heic"],
  ["application/pdf", ".pdf"],
  ["application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx"],
  ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx"],
  ["application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx"],
]);

/**
 * Nội dung thật của tệp có khớp loại đã khai báo không. Loại tệp do trình duyệt gửi lên có thể bị sửa tay,
 * nên với các định dạng có chữ ký cố định thì kiểm ngay đầu tệp (WEBP/HEIC không kiểm — ảnh máy ảnh điện thoại nhiều biến thể).
 */
function contentMatchesType(bytes: Uint8Array, type: string): boolean {
  const startsWith = (signature: number[]) => signature.every((byte, index) => bytes[index] === byte);
  if (type === "image/jpeg") return startsWith([0xff, 0xd8, 0xff]);
  if (type === "image/png") return startsWith([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
  if (type === "application/pdf") return Buffer.from(bytes.subarray(0, 1024)).includes("%PDF-");
  // Word / Excel / PowerPoint đời mới là tệp nén ZIP.
  if (type.startsWith("application/vnd.openxmlformats-officedocument.")) return startsWith([0x50, 0x4b, 0x03, 0x04]);
  return true;
}

export const MAX_UPLOAD_BYTES = 8 * 1024 * 1024;

export type SaveResult = { ok: true; path: string } | { ok: false; error: string };

/**
 * Lưu ảnh và trả về đường dẫn tương đối để ghi vào cơ sở dữ liệu.
 * Tên file do hệ thống sinh, không dùng tên gốc — tên do người dùng đặt có thể
 * chứa ký tự điều hướng thư mục.
 */
export async function saveImage(file: File, folder: string): Promise<SaveResult> {
  if (file.size === 0) return { ok: false, error: "File rỗng." };
  if (file.size > MAX_UPLOAD_BYTES) {
    return { ok: false, error: `Ảnh vượt quá ${MAX_UPLOAD_BYTES / 1024 / 1024} MB.` };
  }

  const extension = ALLOWED_TYPES.get(file.type);
  if (!extension) {
    return { ok: false, error: "Chỉ nhận ảnh (JPG, PNG, WEBP, HEIC), PDF, Word, Excel hoặc PowerPoint." };
  }

  const content = Buffer.from(await file.arrayBuffer());
  if (!contentMatchesType(content, file.type)) {
    return { ok: false, error: "Nội dung tệp không đúng định dạng đã chọn (tệp hỏng hoặc bị đổi đuôi)." };
  }

  const safeFolder = folder.replace(/[^a-z0-9-]/gi, "");
  const fileName = `${randomUUID()}${extension}`;
  const directory = join(UPLOAD_ROOT, safeFolder);

  await mkdir(directory, { recursive: true });
  await writeFile(join(directory, fileName), content);

  return { ok: true, path: `${safeFolder}/${fileName}` };
}

/** Ghi tệp với tên cố định — chỉ dùng cho dữ liệu mẫu (chạy lại seed ghi đè đúng tệp cũ, không để tệp mồ côi tích tụ). */
export async function storeFixedFile(relativePath: string, content: Buffer): Promise<void> {
  const normalized = normalize(relativePath);
  if (normalized.includes("..")) throw new Error("Đường dẫn không hợp lệ.");
  await mkdir(join(UPLOAD_ROOT, normalized, ".."), { recursive: true });
  await writeFile(join(UPLOAD_ROOT, normalized), content);
}

/** Xoá tệp đã lưu (khi xoá bản ghi tương ứng). Không có tệp hoặc đường dẫn lạ thì bỏ qua, không báo lỗi. */
export async function deleteStored(relativePath: string): Promise<void> {
  const normalized = normalize(relativePath).replace(/^(\.\.[/\\])+/, "");
  if (normalized.includes("..") || !normalized.includes("/")) return;
  try {
    await unlink(join(UPLOAD_ROOT, normalized));
  } catch {
    // Tệp đã mất từ trước — không có gì để xoá.
  }
}

/**
 * Đọc file đã lưu. Trả về null khi đường dẫn không hợp lệ hoặc file không tồn tại
 * — không phân biệt hai trường hợp, để không tiết lộ file nào có thật.
 */
export async function readImage(
  relativePath: string,
): Promise<{ buffer: Buffer; contentType: string } | null> {
  // Chặn `../` thoát khỏi thư mục lưu trữ.
  const normalized = normalize(relativePath).replace(/^(\.\.[/\\])+/, "");
  if (normalized.includes("..")) return null;

  const contentType = [...ALLOWED_TYPES.entries()].find(
    ([, ext]) => ext === extname(normalized).toLowerCase(),
  )?.[0];
  if (!contentType) return null;

  try {
    const buffer = await readFile(join(UPLOAD_ROOT, normalized));
    return { buffer, contentType };
  } catch {
    return null;
  }
}
