import "server-only";

import { promises as fs } from "node:fs";

import { extractAudio, extractVideoMetadata } from "@/lib/media-processing";
import { MediaProcessingError } from "@/lib/server/media-errors";
import { createErrorResponse } from "@/lib/server";
import { cleanupFiles, writeUploadToTemp } from "@/lib/uploads";
import { validateOptionalTimeRange, validateVideoUploadFile } from "@/lib/validations";
import type { AudioOutputFormat } from "@/types/media-processing";

export const runtime = "nodejs";

type ExtractionMode = "full" | "range";

function parseExtractionMode(rawValue: FormDataEntryValue | null): ExtractionMode {
  if (rawValue === "full" || rawValue === "range") {
    return rawValue;
  }

  throw new MediaProcessingError({
    message: "Unsupported extraction mode.",
    code: "invalid_input",
    statusCode: 400
  });
}

function parseOutputFormat(rawValue: FormDataEntryValue | null): AudioOutputFormat {
  if (rawValue === "mp3" || rawValue === "wav" || rawValue === "m4a") {
    return rawValue;
  }

  if (rawValue === "aac") {
    return "m4a";
  }

  throw new MediaProcessingError({
    message: "Unsupported audio output format.",
    code: "invalid_input",
    statusCode: 400
  });
}

function parseTimeValue(rawValue: FormDataEntryValue | null, fieldLabel: string) {
  if (typeof rawValue !== "string" || rawValue.trim() === "") {
    throw new MediaProcessingError({
      message: `Missing required ${fieldLabel}.`,
      code: "invalid_input",
      statusCode: 400
    });
  }

  const parsedValue = Number(rawValue);
  if (!Number.isFinite(parsedValue) || parsedValue < 0) {
    throw new MediaProcessingError({
      message: `Invalid ${fieldLabel}.`,
      code: "invalid_input",
      statusCode: 400
    });
  }

  return parsedValue;
}

function mapContentType(format: AudioOutputFormat) {
  switch (format) {
    case "mp3":
      return "audio/mpeg";
    case "wav":
      return "audio/wav";
    case "m4a":
    default:
      return "audio/mp4";
  }
}

function formatTimeLabel(value: number) {
  return value.toFixed(3).replace(".", "_");
}

export async function POST(request: Request) {
  let uploadPath: string | null = null;
  let outputPath: string | null = null;

  try {
    const formData = await request.formData();
    const fileEntry = formData.get("file");

    if (!(fileEntry instanceof File)) {
      throw new MediaProcessingError({
        message: "Missing video file.",
        code: "invalid_input",
        statusCode: 400
      });
    }

    const mode = parseExtractionMode(formData.get("mode"));
    const outputFormat = parseOutputFormat(formData.get("format"));
    const startSeconds = mode === "range" ? parseTimeValue(formData.get("startSeconds"), "start time") : null;
    const endSeconds = mode === "range" ? parseTimeValue(formData.get("endSeconds"), "end time") : null;

    validateOptionalTimeRange(startSeconds, endSeconds);
    validateVideoUploadFile(fileEntry);

    const uploadedFile = await writeUploadToTemp(fileEntry);
    uploadPath = uploadedFile.tempPath;

    const metadata = await extractVideoMetadata(uploadedFile.tempPath, uploadedFile.originalName);

    if (!metadata.hasAudio) {
      throw new MediaProcessingError({
        message: "No audio stream was detected in this video.",
        code: "invalid_file",
        statusCode: 400
      });
    }

    if (mode === "range") {
      const durationLimit = Math.max(metadata.durationSeconds, 0);
      if (startSeconds == null || endSeconds == null || startSeconds >= durationLimit || endSeconds > durationLimit) {
        throw new MediaProcessingError({
          message: "Selected range must stay within the source duration.",
          code: "invalid_input",
          statusCode: 400
        });
      }
    }

    const extractionResult = await extractAudio({
      inputPath: uploadedFile.tempPath,
      outputFormat,
      startSeconds,
      endSeconds
    });
    outputPath = extractionResult.outputPath;

    const audioBuffer = await fs.readFile(extractionResult.outputPath);
    const exportedDurationSeconds =
      extractionResult.startSeconds != null && extractionResult.endSeconds != null
        ? extractionResult.endSeconds - extractionResult.startSeconds
        : metadata.durationSeconds;
    const fileSuffix =
      extractionResult.startSeconds != null && extractionResult.endSeconds != null
        ? `${formatTimeLabel(extractionResult.startSeconds)}-${formatTimeLabel(extractionResult.endSeconds)}`
        : "full";
    const fileName = `${uploadedFile.safeBaseName}-audio-${fileSuffix}.${outputFormat}`;

    return new Response(audioBuffer, {
      headers: {
        "content-type": mapContentType(outputFormat),
        "content-length": String(audioBuffer.byteLength),
        "content-disposition": `attachment; filename="${fileName}"`,
        "x-videohaus-file-name": fileName,
        "x-videohaus-exported-duration": exportedDurationSeconds.toFixed(3),
        "x-videohaus-export-format": outputFormat,
        "cache-control": "no-store"
      }
    });
  } catch (error) {
    return createErrorResponse(error, "Failed to extract audio.");
  } finally {
    await cleanupFiles([uploadPath, outputPath]);
  }
}
