import { promises as fs } from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import sharp from "sharp";

/**
 * Penyimpanan gambar trip — publik, di disk lokal.
 *
 * Beda dengan `secure-storage.ts` (KYC): gambar trip TIDAK dienkripsi karena
 * memang tampil ke semua pengunjung. Tiap gambar dikompres SEKALI saat upload
 * (resize + WebP), jadi saat render server tinggal serve file statik kecil —
 * tidak ada fetch URL eksternal, tidak ada masalah DNS.
 */

/** Batas ukuran file mentah dari user (sebelum kompresi). */
export const MAX_TRIP_IMAGE_UPLOAD_BYTES = 12 * 1024 * 1024;

/** MIME yang diterima saat upload. Output selalu dikonversi ke WebP. */
export const ALLOWED_TRIP_IMAGE_MIME = new Set([
  "image/jpeg",
  "image/png",
  "image/webp",
]);

/** Prefix URL publik untuk gambar trip yang dikelola sendiri. */
export const TRIP_IMAGE_URL_PREFIX = "/api/trip-images/";

/** Sisi terpanjang hasil kompresi — cukup tajam untuk hero & cukup ringan. */
const MAX_DIMENSION = 1920;
/** Kualitas WebP — 80 = sweet spot kualitas/ukuran. */
const WEBP_QUALITY = 80;

/** Nama file valid di disk: `<32-hex>.webp`. Dipakai untuk cegah path traversal. */
const FILE_NAME_RE = /^[a-f0-9]{32}\.webp$/;

function rootDir(): string {
  const fromEnv = process.env.TRIP_UPLOAD_DIR;
  if (fromEnv && fromEnv.trim().length > 0) return fromEnv;
  return path.join(process.cwd(), "uploads", "trips");
}

export function isValidTripImageName(name: string): boolean {
  return FILE_NAME_RE.test(name);
}

/** Resolve nama file ke path absolut di dalam upload dir. Throw kalau mencurigakan. */
function resolveName(name: string): string {
  if (!isValidTripImageName(name)) {
    throw new Error("Nama file gambar tidak valid");
  }
  const dir = rootDir();
  const abs = path.join(dir, name);
  if (!abs.startsWith(dir + path.sep)) {
    throw new Error("Nama file keluar dari direktori upload");
  }
  return abs;
}

export type StoredTripImage = {
  /** Nama file di disk, mis. `ab12…ef.webp`. */
  name: string;
  /** URL publik yang disimpan ke `TripImage.url`. */
  url: string;
  /** Ukuran file hasil kompresi (byte). */
  size: number;
};

/**
 * Kompres + simpan satu gambar trip. Input mentah (JPG/PNG/WebP) di-resize agar
 * muat dalam {@link MAX_DIMENSION}², dikonversi ke WebP, dan metadata (EXIF/GPS)
 * dibuang. Gambar 10MB+ dari kamera HP biasanya menyusut jadi ratusan KB tanpa
 * kehilangan kualitas yang terlihat.
 *
 * `sharp` melempar kalau buffer bukan gambar valid — itu jadi lapis validasi
 * konten yang lebih kuat dari sekadar percaya `file.type`.
 */
export async function processAndSaveTripImage(
  data: Buffer
): Promise<StoredTripImage> {
  if (data.length === 0) throw new Error("File kosong");
  if (data.length > MAX_TRIP_IMAGE_UPLOAD_BYTES) {
    throw new Error("File terlalu besar");
  }

  let optimized: Buffer;
  try {
    optimized = await sharp(data)
      // `.rotate()` tanpa argumen menerapkan orientasi dari EXIF lalu membuang
      // metadata — foto HP tidak miring & lokasi GPS user tidak ikut tersimpan.
      .rotate()
      .resize({
        width: MAX_DIMENSION,
        height: MAX_DIMENSION,
        fit: "inside",
        withoutEnlargement: true,
      })
      .webp({ quality: WEBP_QUALITY })
      .toBuffer();
  } catch {
    throw new Error("File bukan gambar yang valid");
  }

  const name = `${crypto.randomBytes(16).toString("hex")}.webp`;
  const abs = resolveName(name);
  await fs.mkdir(path.dirname(abs), { recursive: true });
  await fs.writeFile(abs, optimized, { mode: 0o644 });

  return {
    name,
    url: `${TRIP_IMAGE_URL_PREFIX}${name}`,
    size: optimized.length,
  };
}

export async function readTripImage(name: string): Promise<Buffer> {
  return fs.readFile(resolveName(name));
}

export async function deleteTripImage(name: string): Promise<void> {
  await fs.rm(resolveName(name), { force: true });
}

/** True kalau URL menunjuk gambar trip yang dikelola sendiri (bukan URL eksternal lama). */
export function isManagedTripImageUrl(url: string): boolean {
  if (!url.startsWith(TRIP_IMAGE_URL_PREFIX)) return false;
  return isValidTripImageName(url.slice(TRIP_IMAGE_URL_PREFIX.length));
}

/** List semua nama file gambar yang ada di disk (untuk cron cleanup). */
export async function listTripImageNames(): Promise<string[]> {
  try {
    const entries = await fs.readdir(rootDir());
    return entries.filter(isValidTripImageName);
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code === "ENOENT") return [];
    throw err;
  }
}

/** mtime file. Null kalau file tidak ada. */
export async function tripImageMtime(name: string): Promise<Date | null> {
  try {
    const st = await fs.stat(resolveName(name));
    return st.mtime;
  } catch {
    return null;
  }
}
