import { NextRequest, NextResponse } from 'next/server';
import { stat, readFile } from 'node:fs/promises';
import { join, normalize, sep, extname } from 'node:path';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

/**
 * Catch-all-Route, die zur LAUFZEIT geschriebene Dateien aus `public/assets/`
 * ausliefert. Hintergrund: Next.js 16 (Turbopack-Production-Build) indexiert
 * `public/` zur Build-Zeit — Dateien, die nach `next build` reinkommen (Hero-Bilder
 * von fal.ai, User-Uploads), werden vom statischen File-Server als 404 behandelt.
 *
 * Dieser Handler ersetzt das statische Serving für `/assets/*` und liest die Datei
 * direkt vom Filesystem unter `process.cwd() + public/assets/...`. Der Volume-Mount
 * im Container zeigt dorthin → Hot-Path funktioniert.
 *
 * SECURITY:
 *  - Whitelist erlaubter Top-Level-Verzeichnisse (siehe ALLOWED_DIRS)
 *  - Path-Traversal-Schutz via normalize + Prefix-Check
 *  - Nur Bild-/Asset-MIME-Types werden mit Content-Type gesetzt; unbekannte Extensions
 *    bekommen application/octet-stream + Content-Disposition=attachment (kein XSS-Risiko
 *    durch versehentlich servierte .html im upload-Dir)
 *
 * PERFORMANCE:
 *  - Hash-basierte Filenames (sha256-Slice in hero-image.ts, sha-256+UUID in upload-image)
 *    sind content-addressed → unveränderlich → langes Caching erlaubt
 *  - 304-Handling via ETag (mtime-basiert, billig)
 */

const ALLOWED_DIRS = new Set(['hero', 'upload']);

const MIME_BY_EXT: Record<string, string> = {
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.webp': 'image/webp',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
};

interface RouteContext {
  params: Promise<{ path: string[] }>;
}

const ASSETS_ROOT = join(process.cwd(), 'public', 'assets');

export async function GET(req: NextRequest, context: RouteContext) {
  const { path } = await context.params;

  // 1. Whitelist-Check: erstes Segment muss ein erlaubtes Verzeichnis sein
  if (!path || path.length < 2 || !ALLOWED_DIRS.has(path[0])) {
    return new NextResponse(null, { status: 404 });
  }

  // 2. Path-Traversal-Schutz: nach normalize muss der Pfad immer noch UNTER ASSETS_ROOT liegen.
  // Trailing-Separator-Check (ASSETS_ROOT + sep) verhindert dass z.B. /app/public/assets-evil
  // als gültig durchgeht (Prefix-Match-Trick).
  const candidate = normalize(join(ASSETS_ROOT, ...path));
  if (!candidate.startsWith(ASSETS_ROOT + sep)) {
    return new NextResponse(null, { status: 404 });
  }

  // 3. Stat: Datei muss existieren und reguläre Datei sein (kein Symlink, kein Dir)
  let st;
  try {
    st = await stat(candidate);
  } catch {
    return new NextResponse(null, { status: 404 });
  }
  if (!st.isFile()) {
    return new NextResponse(null, { status: 404 });
  }

  // 4. ETag aus mtime+size — billig und stabil für content-addressed Files (Hash-Filename heißt
  // gleicher Content = gleicher Pfad = gleiche mtime nach erstem Schreiben).
  const etag = `"${st.size.toString(16)}-${st.mtimeMs.toString(16)}"`;
  if (req.headers.get('if-none-match') === etag) {
    return new NextResponse(null, {
      status: 304,
      headers: { etag, 'cache-control': 'public, max-age=31536000, immutable' },
    });
  }

  // 5. MIME-Type aus Extension. Unbekannte Extensions bekommen octet-stream + attachment-Disposition
  // (verhindert dass jemand z.B. eine hochgeladene .html im Browser inline rendert → XSS-Vektor).
  const ext = extname(candidate).toLowerCase();
  const mime = MIME_BY_EXT[ext];
  const isKnownAsset = !!mime;

  const buf = await readFile(candidate);
  const headers: Record<string, string> = {
    'content-type': mime ?? 'application/octet-stream',
    'content-length': String(buf.byteLength),
    'cache-control': 'public, max-age=31536000, immutable',
    etag,
  };
  if (!isKnownAsset) {
    headers['content-disposition'] = 'attachment';
  }

  return new NextResponse(new Uint8Array(buf), { status: 200, headers });
}
