import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';

/**
 * Dynamic uploads server.
 *
 * Next.js caches the `public/` directory listing when the server boots, so
 * files uploaded *after* `next start` return 404 (and `next/image` answers
 * 502 because its upstream fetch fails). This route handler reads uploads
 * from disk on every request, so a freshly uploaded image is available with
 * no rebuild and no restart.
 *
 * It also generates the 40px/200px thumbnails on demand and caches them on
 * disk, so `/uploads/40/<file>` and `/uploads/200/<file>` always resolve even
 * when the pre-generation script has not been executed yet.
 */
export const dynamic = 'force-dynamic';

const THUMB_SIZES: Readonly<Record<string, number>> = { '40': 40, '200': 200 };
/**
 * Maximum height ratio (h/w) for cached thumbnails. The thumbnail is scaled
 * to fit inside a `width × width * MAX_HEIGHT_RATIO` bounding box without
 * cropping, so a non-square source produces a non-square thumbnail that
 * preserves the original aspect ratio. The frontend renders the result with
 * `object-contain`.
 */
const MAX_HEIGHT_RATIO = 1.4;
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/;
const CONTENT_TYPE_BY_EXTENSION: Readonly<Record<string, string>> = {
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.png': 'image/png',
  '.webp': 'image/webp',
  '.avif': 'image/avif',
  '.gif': 'image/gif',
};

const LOCAL_UPLOADS = path.join(process.cwd(), 'public', 'uploads');
/** Admin owns the canonical uploads directory; peers mirror it. */
const CANONICAL_UPLOADS = path.join(process.cwd(), '..', 'apps', 'admin', 'public', 'uploads');
const UPLOAD_ROOTS = [LOCAL_UPLOADS, CANONICAL_UPLOADS];

const IMMUTABLE = 'public, max-age=31536000, immutable';

async function readIfFile(filePath: string): Promise<Buffer | null> {
  try {
    const stats = await stat(filePath);
    if (!stats.isFile() || stats.size === 0) return null;
    return await readFile(filePath);
  } catch {
    return null;
  }
}

async function findOriginal(filename: string): Promise<{ root: string; buffer: Buffer } | null> {
  for (const root of UPLOAD_ROOTS) {
    const buffer = await readIfFile(path.join(root, filename));
    if (buffer) return { root, buffer };
  }
  return null;
}

async function buildThumbnail(source: Buffer, width: number): Promise<Buffer | null> {
  try {
    const { default: sharp } = await import('sharp');
    const maxHeight = Math.round(width * MAX_HEIGHT_RATIO);
    return await sharp(source)
      .resize({ width, height: maxHeight, fit: 'inside', withoutEnlargement: true })
      .toBuffer();
  } catch {
    return null;
  }
}

function imageResponse(body: Buffer, filename: string): Response {
  const contentType =
    CONTENT_TYPE_BY_EXTENSION[path.extname(filename).toLowerCase()] ?? 'application/octet-stream';
  return new Response(new Uint8Array(body), {
    status: 200,
    headers: {
      'Content-Type': contentType,
      'Content-Length': String(body.byteLength),
      'Cache-Control': IMMUTABLE,
    },
  });
}

export async function GET(
  _request: Request,
  context: { params: Promise<{ path?: string[] }> },
): Promise<Response> {
  const { path: segments = [] } = await context.params;

  if (segments.length === 0 || segments.length > 2) return new Response('Not found', { status: 404 });
  if (!segments.every((segment) => SAFE_SEGMENT.test(segment) && segment !== '..')) {
    return new Response('Not found', { status: 404 });
  }

  const sizeSegment = segments.length === 2 ? segments[0] : null;
  const filename = segments[segments.length - 1];
  if (sizeSegment !== null && !(sizeSegment in THUMB_SIZES)) {
    return new Response('Not found', { status: 404 });
  }
  if (!(path.extname(filename).toLowerCase() in CONTENT_TYPE_BY_EXTENSION)) {
    return new Response('Not found', { status: 404 });
  }

  // Original image requested.
  if (sizeSegment === null) {
    const original = await findOriginal(filename);
    if (!original) return new Response('Not found', { status: 404 });
    return imageResponse(original.buffer, filename);
  }

  // Cached thumbnail already on disk?
  for (const root of UPLOAD_ROOTS) {
    const cached = await readIfFile(path.join(root, sizeSegment, filename));
    if (cached) return imageResponse(cached, filename);
  }

  // Generate the thumbnail on demand and cache it next to the original.
  const original = await findOriginal(filename);
  if (!original) return new Response('Not found', { status: 404 });

  const thumbnail = await buildThumbnail(original.buffer, THUMB_SIZES[sizeSegment]);
  if (!thumbnail) return imageResponse(original.buffer, filename);

  try {
    const cacheDir = path.join(original.root, sizeSegment);
    await mkdir(cacheDir, { recursive: true });
    await writeFile(path.join(cacheDir, filename), thumbnail);
  } catch {
    // Serving the thumbnail matters more than caching it.
  }

  return imageResponse(thumbnail, filename);
}
