import { NextResponse } from 'next/server';
import { writeFile, mkdir, rename, unlink } from 'fs/promises';
import path from 'path';
import { fromBuffer as fileTypeFromBuffer } from 'file-type';
import { enqueueTranscode, needsTranscode, probeCodecs, isFfmpegAvailable } from '@/lib/transcode';
import { UPLOAD_LIMITS } from '@/lib/config';

export const dynamic = 'force-dynamic';
export const maxDuration = 300;

// Allowed extensions and their families (size + handling differ per family).
type Family = 'image' | 'video' | 'pdf';
const EXT_FAMILY: Record<string, Family> = {
  png: 'image', jpg: 'image', jpeg: 'image', gif: 'image', webp: 'image',
  mp4: 'video', m4v: 'video', webm: 'video', mov: 'video', ogv: 'video', ogg: 'video',
  pdf: 'pdf',
};

// Strict per family, permissive within a family (mov ↔ mp4 are both ISO BMFF).
const ALLOWED_MIMES: Record<string, string[]> = {
  png:  ['image/png'],
  jpg:  ['image/jpeg'],
  jpeg: ['image/jpeg'],
  gif:  ['image/gif'],
  webp: ['image/webp'],
  mp4:  ['video/mp4', 'video/x-m4v', 'video/quicktime'],
  m4v:  ['video/mp4', 'video/x-m4v'],
  webm: ['video/webm'],
  mov:  ['video/quicktime', 'video/mp4'],
  ogv:  ['video/ogg', 'application/ogg'],
  ogg:  ['video/ogg', 'application/ogg', 'audio/ogg'],
  pdf:  ['application/pdf'],
};

const MAX_BYTES: Record<Family, number> = UPLOAD_LIMITS;

const UPLOAD_DIR = path.join(process.cwd(), 'data', 'uploads');
const TMP_DIR = path.join(UPLOAD_DIR, '.tmp');

function extractExt(name: string): string {
  const lastDot = name.lastIndexOf('.');
  if (lastDot < 0 || lastDot === name.length - 1) return '';
  return name.slice(lastDot + 1).toLowerCase();
}

export function normalizeFilename(originalName: string): string {
  const ext = extractExt(originalName);
  const safeExt = /^[a-z0-9]{1,5}$/.test(ext) ? ext : 'bin';
  const base = ext ? originalName.slice(0, originalName.length - ext.length - 1) : originalName;

  const slug = base
    .normalize('NFKD')
    .replace(/\p{Mn}/gu, '')
    .toLowerCase()
    .replace(/[\s/\\]+/g, '-')
    .replace(/[^a-z0-9_-]/g, '')
    .replace(/-+/g, '-')
    .replace(/^[-_]+|[-_]+$/g, '')
    .slice(0, 40) || 'file';

  const ts = Date.now().toString(36);
  const rand = Math.random().toString(36).slice(2, 8);
  return `${ts}-${rand}-${slug}.${safeExt}`;
}

export async function POST(request: Request) {
  let tmpPath: string | null = null;
  try {
    const formData = await request.formData();
    const file = formData.get('file') as File | null;

    if (!file) {
      return NextResponse.json({ error: 'No file received.' }, { status: 400 });
    }

    const ext = extractExt(file.name);
    const family = EXT_FAMILY[ext];
    if (!family) {
      return NextResponse.json(
        { error: `Estensione non permessa: .${ext || '(nessuna)'}` },
        { status: 400 }
      );
    }

    if (file.size > MAX_BYTES[family]) {
      const mb = (MAX_BYTES[family] / (1024 * 1024)).toFixed(0);
      return NextResponse.json(
        { error: `File troppo grande (max ${mb} MB per ${family}).` },
        { status: 413 }
      );
    }

    const buffer = Buffer.from(await file.arrayBuffer());

    // Magic-bytes sniffing: reject if the file content doesn't match its claimed extension.
    const detected = await fileTypeFromBuffer(buffer);
    if (!detected) {
      return NextResponse.json(
        { error: 'Tipo del file non riconoscibile dal contenuto.' },
        { status: 400 }
      );
    }
    const allowedMimes = ALLOWED_MIMES[ext] ?? [];
    if (!allowedMimes.includes(detected.mime)) {
      return NextResponse.json(
        { error: `Contenuto del file non corrisponde all'estensione (.${ext} dichiarato, rilevato ${detected.mime}).` },
        { status: 400 }
      );
    }

    const storedName = normalizeFilename(file.name);

    await mkdir(TMP_DIR, { recursive: true });
    tmpPath = path.join(TMP_DIR, storedName);
    await writeFile(tmpPath, buffer);

    // Image / PDF: rename atomically into the uploads dir and we're done.
    if (family !== 'video') {
      const finalPath = path.join(UPLOAD_DIR, storedName);
      await rename(tmpPath, finalPath);
      tmpPath = null;
      return NextResponse.json(
        { url: `/api/files?name=${encodeURIComponent(storedName)}` },
        { status: 201 }
      );
    }

    // Video: probe codecs. If already h264+aac/mp3 → fast path (rename).
    const codecs = await probeCodecs(tmpPath);
    if (!needsTranscode(codecs)) {
      const finalPath = path.join(UPLOAD_DIR, storedName);
      await rename(tmpPath, finalPath);
      tmpPath = null;
      return NextResponse.json(
        { url: `/api/files?name=${encodeURIComponent(storedName)}` },
        { status: 201 }
      );
    }

    // Needs transcoding: bail out early if ffmpeg is unavailable.
    if (!(await isFfmpegAvailable())) {
      return NextResponse.json(
        { error: 'Video richiede ricodifica ma ffmpeg non è disponibile sul server.' },
        { status: 503 }
      );
    }

    // The final file is always an .mp4 once transcoded — replace the extension.
    const finalMp4Name = storedName.replace(/\.[^.]+$/, '.mp4');
    const job = await enqueueTranscode({
      inputPath: tmpPath,
      outputName: finalMp4Name,
      originalUploadName: file.name,
    });
    // The input is now owned by the transcode worker; don't unlink in our catch.
    tmpPath = null;

    return NextResponse.json(
      {
        url: `/api/files?name=${encodeURIComponent(finalMp4Name)}`,
        transcoding: { jobId: job.id, status: job.status },
      },
      { status: 201 }
    );
  } catch (error) {
    console.error('Upload Error:', error);
    if (tmpPath) {
      try { await unlink(tmpPath); } catch {}
    }
    return NextResponse.json({ error: 'Failed to upload file.' }, { status: 500 });
  }
}
