// app/api/admin/upload/route.ts
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
import { IncomingMessage } from "http";
import { spawn } from "child_process";
import { pipeline } from "stream/promises";
import formidable, { File as FormidableFile } from "formidable";
import { prisma } from "@/lib/prisma";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth-options";

// For 2GB uploads over Tailscale (~10-50 Mbps), need 30-45 minutes
export const maxDuration = 2700; // 45 minutes for very large uploads over slow connections

// Use ConfigurableUPLOADS_DIR from environment or default to /uploads
const UPLOADS_DIR = process.env.UPLOADS_DIR || "/uploads";

// Helper to extract video duration using FFprobe
async function getVideoDurationFromFile(filePath: string): Promise<number | null> {
  return new Promise((resolve) => {
    try {
      const ffprobe = spawn("ffprobe", [
        "-v", "error",
        "-show_entries", "format=duration",
        "-of", "default=noprint_wrappers=1:nokey=1:nokey=1",
        filePath,
      ]);

      let output = "";
      let timedOut = false;

      // Large 2GB files may take longer to scan; timeout after 30s
      const timeoutId = setTimeout(() => {
        timedOut = true;
        ffprobe.kill();
        resolve(null); // Return null instead of blocking
      }, 30000);

      ffprobe.stdout.on("data", (data) => {
        output += data.toString();
      });

      ffprobe.on("close", (code) => {
        clearTimeout(timeoutId);
        if (!timedOut && code === 0) {
          const duration = parseFloat(output.trim());
          if (!isNaN(duration) && isFinite(duration) && duration > 0) {
            resolve(Math.round(duration));
          } else {
            resolve(null);
          }
        } else {
          resolve(null);
        }
      });

      ffprobe.on("error", () => {
        clearTimeout(timeoutId);
        resolve(null);
      });
    } catch (err) {
      resolve(null);
    }
  });
}

function parseForm(req: IncomingMessage): Promise<{ fields: any; files: any }> {
  const form = formidable({ 
    multiples: false,
    maxFileSize: 2.5 * 1024 * 1024 * 1024, // 2.5GB max file size
    maxFieldsSize: 10 * 1024 * 1024, // 10MB for all fields combined
    maxFields: 50,
    keepExtensions: true,
  });
  return new Promise((resolve, reject) => {
    form.parse(req, (err, fields, files) => {
      if (err) {
        console.error('[Upload] Formidable parse error:', err.code, err.message);
        reject(err);
      }
      else resolve({ fields, files });
    });
  });
}

async function checkAdmin() {
  const session = await getServerSession(authOptions);
  if (!session?.user?.email) throw { status: 401, message: "Unauthorized" };
  const allowed = (process.env.ALLOWED_ADMINS || "")
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
  if (allowed.length && !allowed.includes(session.user.email))
    throw { status: 403, message: "Forbidden" };
  return session;
}

export async function POST(req: Request) {
  // track saved paths so we can cleanup on error
  let savedVideoDest: string | undefined;
  let savedThumbDest: string | undefined;

  try {
    console.log('[Upload] Request received, content-type:', req.headers.get("content-type"));
    
    const nodeReq = (req as any).req ?? (globalThis as any).__NEXT_INIT?.req ?? null;
    const session = await checkAdmin();
    
    console.log('[Upload] Admin check passed for:', session.user?.email);

    // helper to stream a File directly to disk (handles large files efficiently)
    async function saveVideoStream(file: File, origName?: string) {
      const filename = `${Date.now()}-${String(origName ?? "upload.mp4")}`;
      const dest = path.join(UPLOADS_DIR, "videos", filename);
      
      // Ensure directory exists
      await fs.promises.mkdir(path.dirname(dest), { recursive: true });
      
      // Create write stream to destination
      const writeStream = fs.createWriteStream(dest);
      
      try {
        // Stream the file directly to disk without buffering
        // file.stream() returns a Web ReadableStream, convert to Node.js stream
        const nodeStream = file.stream() as any;
        await pipeline(nodeStream, writeStream);
        
        // Ensure the file is readable by all processes (mode 644)
        await fs.promises.chmod(dest, 0o644);
        
        return { filename, dest };
      } catch (err) {
        // Clean up the partially written file if stream fails
        try {
          await fs.promises.unlink(dest);
        } catch (_) {}
        throw err;
      }
    }

    // helper to save a thumbnail buffer to UPLOADS_DIR/thumbnails
    async function saveThumbBuffer(buffer: Buffer, ext = "jpg") {
      const filename = `${Date.now()}-thumb.${ext.replace(/^\./, "")}`;
      const dest = path.join(UPLOADS_DIR, "thumbnails", filename);
      await fs.promises.mkdir(path.dirname(dest), { recursive: true });
      await fs.promises.writeFile(dest, buffer);
      // Ensure the file is readable by all processes (mode 644)
      await fs.promises.chmod(dest, 0o644);
      return { filename, dest };
    }

    let title: string | undefined;
    let playlistId: string | undefined;
    let durationSec: number | null = null;
    let savedFilename: string | undefined;

    // **IMPORTANT**: single thumbnailUrl used in both branches
    let thumbnailUrl: string | null = null;

    if (!nodeReq) {
      // Request.formData() flow (some Next.js environments)
      let formData: FormData | null = null;
      try {
        formData = await req.formData();
      } catch (e: any) {
        console.error("Failed to parse body as FormData.", e, "content-type=", req.headers.get("content-type"));
        return NextResponse.json(
          { error: "Failed to parse body as FormData. Ensure request is sent with multipart/form-data." },
          { status: 400 }
        );
      }

      const isManualCopy = formData.get("manualFileCopy") === "true";
      const file = isManualCopy ? null : (formData.get("file") as Blob | null);
      title = (formData.get("title") as string) || undefined;
      playlistId = (formData.get("playlistId") as string) || undefined;
      const durationField = formData.get("durationSec") as string | null;
      if (durationField) {
        const n = Number(durationField);
        if (!isNaN(n)) durationSec = Math.round(n);
      }

      // thumbnail (optional)
      const thumb = formData.get("thumbnail") as Blob | null;
      if (thumb) {
        try {
          const thumbArrayBuffer = await thumb.arrayBuffer();
          const thumbBuffer = Buffer.from(thumbArrayBuffer);
          // try to infer extension from name, fallback to jpg
          const fName = (thumb as any).name ?? "";
          const extMatch = fName.match(/\.([a-z0-9]+)$/i);
          const ext = extMatch ? extMatch[1] : "jpg";
          const saved = await saveThumbBuffer(thumbBuffer, ext);
          thumbnailUrl = `/api/thumbnails/${saved.filename}`;
          savedThumbDest = saved.dest;
        } catch (err) {
          console.warn("thumbnail save failed (formData)", err);
          // not fatal — we simply leave thumbnailUrl null
        }
      }

      if (!isManualCopy) {
        if (!file) return NextResponse.json({ error: "no file" }, { status: 400 });

        // Stream the large file directly to disk without buffering
        const origName = (file as any).name ?? `upload-${Date.now()}.mp4`;
        const saved = await saveVideoStream(file as File, origName);
        savedFilename = saved.filename;
        savedVideoDest = saved.dest;
      } else {
        // Manual copy mode: don't save file, just mark for manual copy
        console.log('[Upload] Manual file copy mode enabled');
        savedFilename = undefined;
        savedVideoDest = undefined;
      }
    } else {
      // formidable flow (Node IncomingMessage available)
      console.log('[Upload] Using formidable flow for file upload');
      const { fields, files } = await parseForm(nodeReq as IncomingMessage);
      console.log('[Upload] Formidable parsing complete, fields:', Object.keys(fields), 'files:', Object.keys(files));
      
      const f: FormidableFile | undefined =
        (files && (files.file as FormidableFile)) || (files && Object.values(files)[0]);
      if (!f) return NextResponse.json({ error: "no file found" }, { status: 400 });

      // handle thumbnail file if present in formidable files
      const thumbFile = (files && (files.thumbnail as FormidableFile)) || undefined;
      if (thumbFile) {
        try {
          const tPath = (thumbFile as any).filepath || (thumbFile as any).path;
          const originalThumbName = (thumbFile as any).originalFilename || path.basename(tPath);
          const tExt = path.extname(originalThumbName) || ".jpg";
          const thumbFilename = `${Date.now()}-thumb${tExt}`;
          const thumbDest = path.join(UPLOADS_DIR, "thumbnails", thumbFilename);
          await fs.promises.mkdir(path.dirname(thumbDest), { recursive: true });
          await fs.promises.copyFile(tPath, thumbDest);
          // Ensure the file is readable by all processes (mode 644)
          await fs.promises.chmod(thumbDest, 0o644);
          thumbnailUrl = `/api/thumbnails/${thumbFilename}`;
          savedThumbDest = thumbDest;
        } catch (err) {
          console.warn("thumbnail save failed (formidable)", err);
        }
      }

      // copy video file to UPLOADS_DIR/videos
      const filePath = (f as any).filepath || (f as any).path;
      const originalFilename = (f as any).originalFilename || (f as any).name || path.basename(filePath);
      const filename = `${Date.now()}-${originalFilename}`;
      const dest = path.join(UPLOADS_DIR, "videos", filename);
      
      console.log('[Upload] Copying video file', { size: (f as any).size, path: filePath, dest });
      
      await fs.promises.mkdir(path.dirname(dest), { recursive: true });
      await fs.promises.copyFile(filePath, dest);
      // Ensure the file is readable by all processes (mode 644)
      await fs.promises.chmod(dest, 0o644);
      
      console.log('[Upload] Video file copied successfully');

      title = fields.title ?? originalFilename;
      playlistId = fields.playlistId;
      const durationField = fields.durationSec ?? fields.duration ?? null;
      if (durationField) {
        const n = Number(durationField);
        if (!isNaN(n)) durationSec = Math.round(n);
      }

      savedFilename = filename;
      savedVideoDest = dest;
    }

    // validate playlist: avoid FK errors
    if (!playlistId) {
      // cleanup if needed
      if (savedVideoDest) await fs.promises.unlink(savedVideoDest).catch(() => {});
      if (savedThumbDest) await fs.promises.unlink(savedThumbDest).catch(() => {});
      return NextResponse.json({ error: "playlistId required" }, { status: 400 });
    }

    const playlist = await prisma.playlist.findUnique({ where: { id: playlistId } });
    if (!playlist) {
      if (savedVideoDest) await fs.promises.unlink(savedVideoDest).catch(() => {});
      if (savedThumbDest) await fs.promises.unlink(savedThumbDest).catch(() => {});
      return NextResponse.json({ error: "playlist not found" }, { status: 400 });
    }

    // Get current user
    const user = await prisma.user.findUnique({
      where: { email: session.user?.email ?? "" },
      select: { id: true },
    });
    if (!user) {
      if (savedVideoDest) await fs.promises.unlink(savedVideoDest).catch(() => {});
      if (savedThumbDest) await fs.promises.unlink(savedThumbDest).catch(() => {});
      return NextResponse.json({ error: "user not found" }, { status: 400 });
    }

    // If duration not provided by client, try to extract it from the saved video file
    if (durationSec === null && savedVideoDest) {
      try {
        const extractedDuration = await getVideoDurationFromFile(savedVideoDest);
        if (extractedDuration !== null) {
          durationSec = extractedDuration;
          console.log(`[Upload] Extracted duration: ${durationSec}s from ${savedVideoDest}`);
        } else {
          console.warn(`[Upload] Could not extract duration from ${savedVideoDest}`);
        }
      } catch (err) {
        console.warn(`[Upload] Duration extraction error:`, err);
      }
    }

    // For manual copy mode, check if we have a file
    const isManualMode = !savedVideoDest;

    // Calculate index: find max index in playlist and add 1
    const maxIndexVideo = await prisma.video.findFirst({
      where: { playlistId },
      orderBy: { index: 'desc' },
      select: { index: true },
    });
    const desiredIndex = (maxIndexVideo?.index ?? -1) + 1;

    // First, create the video record to get its ID
    const video = await prisma.video.create({
      data: {
        title: title ?? savedFilename ?? 'Untitled',
        url: '', // Will be set below or after rename
        thumbnail: thumbnailUrl,
        index: desiredIndex,
        playlistId,
        userId: user.id,
        transcodingStatus: isManualMode ? 'pending_manual_file' : 'uploaded',
        ...(durationSec !== null && { durationSec }),
      },
    });

    // If manual copy mode, set the URL and return
    if (isManualMode) {
      const videoUrl = `/uploads/videos/${video.id}.mp4`;
      await prisma.video.update({
        where: { id: video.id },
        data: { url: videoUrl },
      });
      console.log('[Upload] Manual copy mode: video created with ID', video.id, 'URL:', videoUrl);
      return NextResponse.json({ video: { ...video, url: videoUrl } }, { status: 201 });
    }

    // Now rename the uploaded file to use the video ID
    const finalFilename = `${video.id}.mp4`;
    const finalDest = path.join(UPLOADS_DIR, "videos", finalFilename);
    try {
      if (savedVideoDest) {
        await fs.promises.rename(savedVideoDest, finalDest);
      }
      // Update the URL in the database to reflect the final filename
      await prisma.video.update({
        where: { id: video.id },
        data: { url: `/uploads/videos/${finalFilename}` },
      });
    } catch (err) {
      console.error("Error renaming video file:", err);
      // If rename fails, cleanup and delete the record
      await prisma.video.delete({ where: { id: video.id } });
      throw err;
    }

    return NextResponse.json({ video: { ...video, url: `/uploads/videos/${finalFilename}` } }, { status: 201 });
  } catch (err: any) {
    console.error("upload error", {
      code: err?.code,
      message: err?.message,
      errno: err?.errno,
    }, err);

    // cleanup saved files if something failed
    if (savedVideoDest) {
      try {
        await fs.promises.unlink(savedVideoDest);
      } catch (_) {}
    }
    if (savedThumbDest) {
      try {
        await fs.promises.unlink(savedThumbDest);
      } catch (_) {}
    }

    const status = err?.status ?? 500;
    const message = err?.message ?? (err?.toString ? err.toString() : "server error");
    return NextResponse.json({ error: message }, { status });
  }
}
