import { mkdir, writeFile, unlink } from "fs/promises";
import path from "path";
import crypto from "crypto";
import sharp from "sharp";

// Bewusst außerhalb von /public — Kunden-Uploads sind nicht direkt per URL
// erreichbar, sondern nur über die authentifizierte Route in
// src/app/api/files/[orderId]/[filename]/route.ts.
const UPLOAD_ROOT = path.join(process.cwd(), "private-uploads", "orders");
export const ORDER_UPLOAD_ROOT = UPLOAD_ROOT;
const TODO_ROOT = path.join(process.cwd(), "private-uploads", "todos");
const AVATAR_ROOT = path.join(process.cwd(), "private-uploads", "avatars");
const RECEIPT_ROOT = path.join(process.cwd(), "private-uploads", "receipts");
const QM_ROOT = path.join(process.cwd(), "private-uploads", "qm");
// Bewusst INNERHALB von /public — anders als die Uploads oben ist die
// Portfolio-Galerie öffentlich, die Bilder müssen ohne Login direkt per URL
// erreichbar sein (next/image, Portfolio-Seite).
const PORTFOLIO_ROOT = path.join(process.cwd(), "public", "uploads", "portfolio");
const SEO_ROOT = path.join(process.cwd(), "public", "uploads", "seo");
const TESTIMONIAL_ROOT = path.join(process.cwd(), "public", "uploads", "testimonials");
const BLOG_ROOT = path.join(process.cwd(), "public", "uploads", "blog");

// Für Testimonial-/Blog-/SEO-Bilder: jeder Upload bekommt einen neuen
// zufälligen Dateinamen statt das alte Bild zu überschreiben (siehe
// save*Image unten) — ohne diesen Aufräumschritt bei jedem Ersetzen/Löschen
// bliebe das alte Bild für immer verwaist unter /public/uploads liegen.
// Nur Pfade innerhalb von /uploads/ werden angefasst (keine beliebigen
// Public-Dateien löschbar).
export async function deletePublicUploadImage(publicPath: string) {
  if (!publicPath || !publicPath.startsWith("/uploads/")) return;
  try {
    await unlink(path.join(process.cwd(), "public", publicPath));
  } catch {
    // Datei war schon weg — nicht schlimm.
  }
}

export const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15 MB pro Datei
export const MAX_FILES = 8;
export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/webp", "image/heic", "application/pdf"];

export type SavedAttachment = {
  filename: string;
  storedName: string;
  size: number;
  mimeType: string;
  uploadedAt: Date;
};

export async function saveOrderAttachments(orderId: string, files: File[]): Promise<SavedAttachment[]> {
  const dir = path.join(UPLOAD_ROOT, orderId);
  await mkdir(dir, { recursive: true });

  const saved: SavedAttachment[] = [];
  for (const file of files) {
    if (!file || !(file instanceof File) || file.size === 0) continue;
    if (file.size > MAX_FILE_SIZE) throw new Error(`Datei "${file.name}" ist größer als 15 MB.`);
    if (!ALLOWED_MIME_TYPES.includes(file.type)) throw new Error(`Dateityp von "${file.name}" wird nicht unterstützt.`);

    const ext = path.extname(file.name).slice(0, 10);
    const storedName = `${crypto.randomUUID()}${ext}`;
    const buffer = Buffer.from(await file.arrayBuffer());
    await writeFile(path.join(dir, storedName), buffer);

    saved.push({
      filename: file.name.slice(0, 200),
      storedName,
      size: file.size,
      mimeType: file.type,
      uploadedAt: new Date(),
    });
  }
  return saved;
}

export function orderUploadPath(orderId: string, storedName: string): string {
  return path.join(UPLOAD_ROOT, orderId, storedName);
}

// Anhänge an einem ToDo — gleiche Regeln wie Auftragsanhänge, eigener Ordner.
export async function saveTodoAttachments(todoId: string, files: File[]): Promise<SavedAttachment[]> {
  const dir = path.join(TODO_ROOT, todoId);
  await mkdir(dir, { recursive: true });
  const saved: SavedAttachment[] = [];
  for (const file of files) {
    if (!file || !(file instanceof File) || file.size === 0) continue;
    if (file.size > MAX_FILE_SIZE) throw new Error(`Datei "${file.name}" ist größer als 15 MB.`);
    if (!ALLOWED_MIME_TYPES.includes(file.type)) throw new Error(`Dateityp von "${file.name}" wird nicht unterstützt.`);
    const ext = path.extname(file.name).slice(0, 10);
    const storedName = `${crypto.randomUUID()}${ext}`;
    await writeFile(path.join(dir, storedName), Buffer.from(await file.arrayBuffer()));
    saved.push({
      filename: file.name.slice(0, 200),
      storedName,
      size: file.size,
      mimeType: file.type,
      uploadedAt: new Date(),
    });
  }
  return saved;
}

export function todoUploadPath(todoId: string, storedName: string): string {
  return path.join(TODO_ROOT, todoId, storedName);
}

export async function deleteTodoAttachmentFile(todoId: string, storedName: string) {
  try {
    await unlink(path.join(TODO_ROOT, todoId, storedName));
  } catch {
    // schon weg — egal
  }
}

export const MAX_AVATAR_SIZE = 5 * 1024 * 1024; // 5 MB
export const ALLOWED_AVATAR_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];

export async function saveAvatar(clientId: string, file: File): Promise<{ storedName: string }> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_AVATAR_SIZE) throw new Error("Bild ist größer als 5 MB.");
  if (!ALLOWED_AVATAR_MIME_TYPES.includes(file.type)) throw new Error("Nur JPG, PNG oder WebP erlaubt.");

  const dir = path.join(AVATAR_ROOT, clientId);
  await mkdir(dir, { recursive: true });
  const ext = path.extname(file.name).slice(0, 10) || ".jpg";
  const storedName = `${crypto.randomUUID()}${ext}`;
  const buffer = Buffer.from(await file.arrayBuffer());
  await writeFile(path.join(dir, storedName), buffer);
  return { storedName };
}

export function avatarUploadPath(clientId: string, storedName: string): string {
  return path.join(AVATAR_ROOT, clientId, storedName);
}

export const MAX_RECEIPT_SIZE = 10 * 1024 * 1024; // 10 MB
export const ALLOWED_RECEIPT_MIME_TYPES = ["image/jpeg", "image/png", "image/webp", "application/pdf"];

export async function saveReceipt(expenseId: string, file: File): Promise<{ storedName: string }> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_RECEIPT_SIZE) throw new Error("Beleg ist größer als 10 MB.");
  if (!ALLOWED_RECEIPT_MIME_TYPES.includes(file.type)) throw new Error("Nur JPG, PNG, WebP oder PDF erlaubt.");

  const dir = path.join(RECEIPT_ROOT, expenseId);
  await mkdir(dir, { recursive: true });
  const ext = path.extname(file.name).slice(0, 10) || ".pdf";
  const storedName = `${crypto.randomUUID()}${ext}`;
  const buffer = Buffer.from(await file.arrayBuffer());
  await writeFile(path.join(dir, storedName), buffer);
  return { storedName };
}

export function receiptUploadPath(expenseId: string, storedName: string): string {
  return path.join(RECEIPT_ROOT, expenseId, storedName);
}

export const MAX_QM_ATTACHMENT_SIZE = 15 * 1024 * 1024; // 15 MB
export const ALLOWED_QM_MIME_TYPES = ["image/jpeg", "image/png", "image/webp", "application/pdf"];

export async function saveQmAttachment(submissionId: string, file: File): Promise<{ storedName: string }> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_QM_ATTACHMENT_SIZE) throw new Error("Datei ist größer als 15 MB.");
  if (!ALLOWED_QM_MIME_TYPES.includes(file.type)) throw new Error("Nur JPG, PNG, WebP oder PDF erlaubt.");

  const dir = path.join(QM_ROOT, submissionId);
  await mkdir(dir, { recursive: true });
  const ext = path.extname(file.name).slice(0, 10) || ".pdf";
  const storedName = `${crypto.randomUUID()}${ext}`;
  const buffer = Buffer.from(await file.arrayBuffer());
  await writeFile(path.join(dir, storedName), buffer);
  return { storedName };
}

export function qmAttachmentUploadPath(submissionId: string, storedName: string): string {
  return path.join(QM_ROOT, submissionId, storedName);
}

export const MAX_PORTFOLIO_IMAGE_SIZE = 15 * 1024 * 1024; // 15 MB
export const ALLOWED_PORTFOLIO_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];

// Gibt den öffentlichen URL-Pfad zurück (/uploads/portfolio/...), nicht den
// Dateisystempfad — das Bild wird direkt von next/image o. Ä. darüber geladen.
//
// Bilder werden beim Upload serverseitig auf eine vernünftige Web-Größe
// verkleinert und als WebP re-encodiert (statt die oft mehrere MB großen
// Kamera-Originale 1:1 zu speichern) — das war die Hauptursache für das
// gemeldete Lag auf der Portfolio-Seite (Admin + öffentlich), siehe
// STATUS.md-Eintrag vom 07.08.2026. 2400px Kantenlänge reicht für jede
// Bildschirmgröße bei weitem, next/image erzeugt daraus bei Bedarf noch
// kleinere Varianten fürs jeweilige Gerät.
export async function savePortfolioImage(file: File): Promise<string> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_PORTFOLIO_IMAGE_SIZE) throw new Error(`Bild "${file.name}" ist größer als 15 MB.`);
  if (!ALLOWED_PORTFOLIO_MIME_TYPES.includes(file.type)) throw new Error(`Bildformat von "${file.name}" wird nicht unterstützt (nur JPG, PNG, WebP).`);

  await mkdir(PORTFOLIO_ROOT, { recursive: true });
  const storedName = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}.webp`;
  const buffer = Buffer.from(await file.arrayBuffer());
  const resized = await sharp(buffer)
    .rotate()
    .resize({ width: 2400, height: 2400, fit: "inside", withoutEnlargement: true })
    .webp({ quality: 82 })
    .toBuffer();
  await writeFile(path.join(PORTFOLIO_ROOT, storedName), resized);
  return `/uploads/portfolio/${storedName}`;
}

export const MAX_TESTIMONIAL_IMAGE_SIZE = 8 * 1024 * 1024; // 8 MB
export const ALLOWED_TESTIMONIAL_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];

// Rundes Profilbild zu einer Kundenstimme — öffentlich (läuft im Marquee auf
// der Startseite), daher wie Portfolio-Bilder unter /public statt im
// privaten Upload-Bereich. Quadratisch zugeschnitten und klein gehalten
// (200px reicht für eine ~64px-Anzeige plus Retina), da es sich um einen
// kleinen Avatar handelt, nicht ein Galeriebild.
export async function saveTestimonialImage(file: File): Promise<string> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_TESTIMONIAL_IMAGE_SIZE) throw new Error("Bild ist größer als 8 MB.");
  if (!ALLOWED_TESTIMONIAL_MIME_TYPES.includes(file.type)) throw new Error("Bildformat wird nicht unterstützt (nur JPG, PNG, WebP).");

  await mkdir(TESTIMONIAL_ROOT, { recursive: true });
  const storedName = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}.webp`;
  const buffer = Buffer.from(await file.arrayBuffer());
  const resized = await sharp(buffer)
    .rotate()
    .resize({ width: 200, height: 200, fit: "cover", position: "attention" })
    .webp({ quality: 85 })
    .toBuffer();
  await writeFile(path.join(TESTIMONIAL_ROOT, storedName), resized);
  return `/uploads/testimonials/${storedName}`;
}

export const MAX_BLOG_IMAGE_SIZE = 15 * 1024 * 1024; // 15 MB
export const ALLOWED_BLOG_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];

// Titelbild eines Blog-Beitrags — öffentlich, breites Format (Karten auf der
// Übersichtsseite + Header auf der Detailseite), daher wie Portfolio-Bilder
// deutlich größer skaliert als das kleine Testimonial-Rundbild.
export async function saveBlogImage(file: File): Promise<string> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_BLOG_IMAGE_SIZE) throw new Error(`Bild "${file.name}" ist größer als 15 MB.`);
  if (!ALLOWED_BLOG_MIME_TYPES.includes(file.type)) throw new Error(`Bildformat von "${file.name}" wird nicht unterstützt (nur JPG, PNG, WebP).`);

  await mkdir(BLOG_ROOT, { recursive: true });
  const storedName = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}.webp`;
  const buffer = Buffer.from(await file.arrayBuffer());
  const resized = await sharp(buffer)
    .rotate()
    .resize({ width: 2000, height: 1125, fit: "cover", position: "attention" })
    .webp({ quality: 82 })
    .toBuffer();
  await writeFile(path.join(BLOG_ROOT, storedName), resized);
  return `/uploads/blog/${storedName}`;
}

export const MAX_CONVENTION_IMAGE_SIZE = 15 * 1024 * 1024; // 15 MB
export const ALLOWED_CONVENTION_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];

// Foto einer Convention — wird auf der öffentlichen Übersicht als große
// Hintergrund-Karte der "nächsten Convention" gezeigt (breites 16:9-Format),
// daher wie Blog-/Portfolio-Bilder unter /public und web-tauglich skaliert.
export async function saveConventionImage(file: File): Promise<string> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_CONVENTION_IMAGE_SIZE) throw new Error(`Bild "${file.name}" ist größer als 15 MB.`);
  if (!ALLOWED_CONVENTION_MIME_TYPES.includes(file.type))
    throw new Error(`Bildformat von "${file.name}" wird nicht unterstützt (nur JPG, PNG, WebP).`);

  const CONVENTION_ROOT = path.join(process.cwd(), "public", "uploads", "conventions");
  await mkdir(CONVENTION_ROOT, { recursive: true });
  const storedName = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}.webp`;
  const buffer = Buffer.from(await file.arrayBuffer());
  const resized = await sharp(buffer)
    .rotate()
    .resize({ width: 2000, height: 1125, fit: "cover", position: "attention" })
    .webp({ quality: 82 })
    .toBuffer();
  await writeFile(path.join(CONVENTION_ROOT, storedName), resized);
  return `/uploads/conventions/${storedName}`;
}

export const MAX_CONVENTION_LOGO_SIZE = 8 * 1024 * 1024; // 8 MB
export const ALLOWED_CONVENTION_LOGO_MIME_TYPES = ["image/jpeg", "image/png", "image/webp", "image/svg+xml"];

// Convention-Logo — anders als das Foto NICHT beschnitten: viele Logos sind
// breit/länglich und haben Transparenz. Seitenverhältnis bleibt erhalten
// (fit "inside"), Ausgabe als WebP mit Alpha. SVG wird mit erhöhter Dichte
// gerastert, damit es scharf bleibt.
export async function saveConventionLogo(file: File): Promise<string> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_CONVENTION_LOGO_SIZE) throw new Error(`Logo "${file.name}" ist größer als 8 MB.`);
  if (!ALLOWED_CONVENTION_LOGO_MIME_TYPES.includes(file.type))
    throw new Error(`Logo-Format von "${file.name}" wird nicht unterstützt (nur JPG, PNG, WebP, SVG).`);

  const LOGO_ROOT = path.join(process.cwd(), "public", "uploads", "conventions");
  await mkdir(LOGO_ROOT, { recursive: true });
  const storedName = `logo-${Date.now()}-${crypto.randomUUID().slice(0, 8)}.webp`;
  const buffer = Buffer.from(await file.arrayBuffer());
  const resized = await sharp(buffer, { density: 240 })
    .rotate()
    .resize({ width: 720, height: 360, fit: "inside", withoutEnlargement: true })
    .webp({ quality: 90 })
    .toBuffer();
  await writeFile(path.join(LOGO_ROOT, storedName), resized);
  return `/uploads/conventions/${storedName}`;
}

export const MAX_SEO_IMAGE_SIZE = 8 * 1024 * 1024; // 8 MB
export const ALLOWED_SEO_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];

// Social-Preview-Bild (Open Graph / Twitter Card) — öffentlich erreichbar,
// da Social-Media-Crawler es ohne Login abrufen müssen.
export async function saveSeoImage(file: File): Promise<string> {
  if (!file || !(file instanceof File) || file.size === 0) throw new Error("Keine Datei ausgewählt.");
  if (file.size > MAX_SEO_IMAGE_SIZE) throw new Error(`Bild "${file.name}" ist größer als 8 MB.`);
  if (!ALLOWED_SEO_MIME_TYPES.includes(file.type)) throw new Error(`Bildformat von "${file.name}" wird nicht unterstützt (nur JPG, PNG, WebP).`);

  await mkdir(SEO_ROOT, { recursive: true });
  const ext = path.extname(file.name).slice(0, 10) || ".jpg";
  const storedName = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}${ext}`;
  const buffer = Buffer.from(await file.arrayBuffer());
  await writeFile(path.join(SEO_ROOT, storedName), buffer);
  return `/uploads/seo/${storedName}`;
}
