import "server-only";
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
import { prisma } from "@/app/lib/db";

const MAX_BYTES = 2 * 1024 * 1024;
const MAX_PIXELS = 20_000_000;
const ALLOWED_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);

export function getUploadDir() {
  const configured = process.env.UPLOAD_DIR?.trim();
  if (configured) return configured;
  return path.join(/*turbopackIgnore: true*/ process.cwd(), "data", "uploads");
}

export function mediaPublicUrl(filename: string) {
  return `/media/${filename}`;
}

export function isUploadedMediaUrl(url: string) {
  return /^\/media\/[a-f0-9-]+\.webp$/i.test(url);
}

export function filenameFromMediaUrl(url: string) {
  if (!isUploadedMediaUrl(url)) return null;
  return url.replace("/media/", "");
}

async function ensureUploadDir() {
  const dir = getUploadDir();
  await fs.mkdir(dir, { recursive: true });
  return dir;
}

export async function saveUploadedImage(file: File | null | undefined) {
  if (!file || !(file instanceof File) || file.size === 0) {
    return null;
  }

  if (file.size > MAX_BYTES) {
    throw new Error("Image must be 2 MB or smaller.");
  }

  if (!ALLOWED_TYPES.has(file.type)) {
    throw new Error("Only JPEG, PNG, and WebP images are allowed.");
  }

  const buffer = Buffer.from(await file.arrayBuffer());
  const image = sharp(buffer, { failOn: "error" }).rotate();
  const metadata = await image.metadata();
  const width = metadata.width ?? 0;
  const height = metadata.height ?? 0;

  if (!width || !height) {
    throw new Error("Could not read image dimensions.");
  }

  if (width * height > MAX_PIXELS) {
    throw new Error("Image resolution is too large.");
  }

  const filename = `${randomUUID()}.webp`;
  const uploadDir = await ensureUploadDir();
  const tempPath = path.join(uploadDir, `${filename}.tmp`);
  const finalPath = path.join(uploadDir, filename);

  const output = await image
    .webp({ quality: 82 })
    .toBuffer({ resolveWithObject: true });

  await fs.writeFile(tempPath, output.data);
  await fs.rename(tempPath, finalPath);

  const asset = await prisma.mediaAsset.create({
    data: {
      filename,
      originalName: file.name.slice(0, 180),
      mimeType: "image/webp",
      sizeBytes: output.data.byteLength,
      width: output.info.width ?? width,
      height: output.info.height ?? height,
    },
  });

  return {
    asset,
    url: mediaPublicUrl(filename),
    absolutePath: finalPath,
  };
}

export async function deleteUploadedMedia(url: string | null | undefined) {
  const filename = url ? filenameFromMediaUrl(url) : null;
  if (!filename) return;

  const absolutePath = path.join(getUploadDir(), filename);
  try {
    await fs.unlink(absolutePath);
  } catch {
    // Ignore missing files.
  }

  await prisma.mediaAsset.deleteMany({ where: { filename } });
}

export async function resolveUploadPath(filename: string) {
  if (!/^[a-f0-9-]+\.webp$/i.test(filename)) {
    return null;
  }
  const absolutePath = path.join(getUploadDir(), filename);
  try {
    await fs.access(absolutePath);
    return absolutePath;
  } catch {
    return null;
  }
}
