import sharp from "sharp";
import { connectDB } from "@/lib/db";
import { ClientPhotoFolder } from "@/models/ClientPhotoFolder";
import { PhotoRating } from "@/models/PhotoRating";
import { listFolderImages, fetchPreviewSafe } from "@/lib/nextcloud";

const CANVAS_WIDTH = 1080;
const CANVAS_HEIGHT = 1920;
const MAX_PHOTOS = 6;
const GAP = 8;

// Auswahl: höchstbewertete Fotos (Kunden-Sterne) aus allen an den Auftrag
// gehängten Foto-Ordnern zuerst, unbewertete Fotos füllen danach auf, falls
// noch nicht genug ausgewählt sind (z. B. bevor der Kunde bewertet hat).
// Foto-Ordner enthalten oft zwei Dateien pro Aufnahme — eine mit "_watermark"
// im Namen (nur für die Kunden-Vorschau vor Zahlungseingang gedacht, siehe
// api/photos/.../route.ts) und die normale Datei. Ohne Entduplizierung würde
// dieselbe Aufnahme zweimal im Rückblick landen (einmal mit, einmal ohne
// Wasserzeichen) statt möglichst viele unterschiedliche Motive zu zeigen.
function baseIdentity(fileName: string): string {
  return fileName.replace(/_watermark(\.[^.]+)$/i, "$1");
}

async function selectTopPhotos(orderId: string): Promise<{ folderId: string; folderPath: string; fileName: string }[]> {
  await connectDB();
  const folders = await ClientPhotoFolder.find({ orderId }).lean();
  if (folders.length === 0) return [];

  const allWithRatings: { folderId: string; folderPath: string; fileName: string; stars: number }[] = [];
  for (const folder of folders) {
    const images = await listFolderImages(folder.nextcloudPath);

    // Pro Motiv nur eine Datei behalten — die unwasserzeichnete, falls
    // vorhanden, sonst die wasserzeichenbehaftete als Fallback.
    const byIdentity = new Map<string, string>();
    for (const fileName of images) {
      const identity = baseIdentity(fileName);
      const isWatermarked = identity !== fileName;
      // Erste Sichtung eines Motivs übernehmen, eine spätere unwasserzeichnete
      // Variante überschreibt eine zuvor gesehene wasserzeichenbehaftete.
      if (!byIdentity.has(identity) || !isWatermarked) byIdentity.set(identity, fileName);
    }
    const dedupedImages = [...byIdentity.values()];

    const ratings = await PhotoRating.find({ folderId: folder._id, fileName: { $in: images } })
      .select("fileName stars")
      .lean();
    // Bewertungen werden immer gegen den tatsächlich angezeigten Dateinamen
    // erfasst (könnte die wasserzeichenbehaftete Variante sein) — beim
    // Nachschlagen daher über die Motiv-Identität vergleichen, nicht den
    // exakten Dateinamen.
    const starsByIdentity = new Map(ratings.map((r) => [baseIdentity(r.fileName), r.stars]));
    for (const fileName of dedupedImages) {
      allWithRatings.push({
        folderId: String(folder._id),
        folderPath: folder.nextcloudPath,
        fileName,
        stars: starsByIdentity.get(baseIdentity(fileName)) ?? 0,
      });
    }
  }

  allWithRatings.sort((a, b) => b.stars - a.stars);
  return allWithRatings.slice(0, MAX_PHOTOS).map(({ folderId, folderPath, fileName }) => ({ folderId, folderPath, fileName }));
}

function gridLayout(n: number): { cols: number; rows: number } {
  if (n <= 1) return { cols: 1, rows: 1 };
  if (n === 2) return { cols: 1, rows: 2 };
  if (n <= 4) return { cols: 2, rows: 2 };
  return { cols: 2, rows: 3 };
}

function brandingOverlaySvg(): string {
  return `
    <svg width="${CANVAS_WIDTH}" height="${CANVAS_HEIGHT}" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <linearGradient id="fade" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#000000" stop-opacity="0" />
          <stop offset="100%" stop-color="#000000" stop-opacity="0.55" />
        </linearGradient>
      </defs>
      <rect x="0" y="${CANVAS_HEIGHT - 220}" width="${CANVAS_WIDTH}" height="220" fill="url(#fade)" />
      <text x="${CANVAS_WIDTH / 2}" y="${CANVAS_HEIGHT - 90}" text-anchor="middle" font-family="sans-serif"
            font-size="48" letter-spacing="6" fill="#ffffff" font-weight="700">RAKKU</text>
      <text x="${CANVAS_WIDTH / 2}" y="${CANVAS_HEIGHT - 50}" text-anchor="middle" font-family="sans-serif"
            font-size="20" letter-spacing="4" fill="#ffffff" fill-opacity="0.85">PHOTOGRAPHY</text>
    </svg>
  `;
}

export async function generateStoryRecap(orderId: string): Promise<Buffer | null> {
  const selected = await selectTopPhotos(orderId);
  if (selected.length === 0) return null;

  const { cols, rows } = gridLayout(selected.length);
  const cellWidth = Math.floor((CANVAS_WIDTH - GAP * (cols + 1)) / cols);
  const cellHeight = Math.floor((CANVAS_HEIGHT - GAP * (rows + 1)) / rows);

  const composites: sharp.OverlayOptions[] = [];
  for (let i = 0; i < selected.length; i++) {
    const { folderPath, fileName } = selected[i];
    const raw = await fetchPreviewSafe(`${folderPath}/${fileName}`, Math.max(cellWidth, cellHeight) * 2);
    const resized = await sharp(raw)
      .resize(cellWidth, cellHeight, { fit: "cover" })
      .jpeg({ quality: 90 })
      .toBuffer();

    const col = i % cols;
    const row = Math.floor(i / cols);
    composites.push({
      input: resized,
      left: GAP + col * (cellWidth + GAP),
      top: GAP + row * (cellHeight + GAP),
    });
  }

  composites.push({ input: Buffer.from(brandingOverlaySvg()), left: 0, top: 0 });

  return sharp({
    create: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT, channels: 3, background: "#0a0a0a" },
  })
    .composite(composites)
    .jpeg({ quality: 92 })
    .toBuffer();
}
