import { spawn } from "node:child_process";
import ffmpegPath from "ffmpeg-static";
import sharp from "sharp";
import * as xlsx from "xlsx";
import { parse as parseCsv } from "csv-parse/sync";
import { puterChatComplete } from "../puter-inference.js";
import type { RenderResult, ConvertBundle } from "./types.js";

// ─── Format helpers ────────────────────────────────────────────────────────────

function isImageFormat(mimeOrFormat: string): boolean {
  const lower = mimeOrFormat.toLowerCase();
  return (
    lower === "image/png" ||
    lower === "image/jpeg" ||
    lower === "image/jpg" ||
    lower === "image/webp" ||
    lower === "image/gif" ||
    lower === "image/svg+xml" ||
    lower === "png" ||
    lower === "jpg" ||
    lower === "jpeg" ||
    lower === "webp" ||
    lower === "gif" ||
    lower === "svg"
  );
}

function isAVFormat(mimeOrFormat: string): boolean {
  const lower = mimeOrFormat.toLowerCase();
  return (
    lower.startsWith("audio/") ||
    lower.startsWith("video/") ||
    lower === "mp3" ||
    lower === "mp4" ||
    lower === "wav" ||
    lower === "ogg" ||
    lower === "flac" ||
    lower === "aac" ||
    lower === "webm" ||
    lower === "mkv" ||
    lower === "avi" ||
    lower === "mov"
  );
}

function isDataFormat(mimeOrFormat: string): boolean {
  const lower = mimeOrFormat.toLowerCase();
  return (
    lower === "text/csv" ||
    lower === "application/json" ||
    lower === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
    lower === "csv" ||
    lower === "json" ||
    lower === "xlsx"
  );
}

function getTargetMime(targetFormat: string): string {
  const map: Record<string, string> = {
    png: "image/png",
    jpg: "image/jpeg",
    jpeg: "image/jpeg",
    webp: "image/webp",
    gif: "image/gif",
    svg: "image/svg+xml",
    mp3: "audio/mpeg",
    mp4: "video/mp4",
    wav: "audio/wav",
    ogg: "audio/ogg",
    flac: "audio/flac",
    aac: "audio/aac",
    webm: "video/webm",
    mkv: "video/x-matroska",
    avi: "video/x-msvideo",
    mov: "video/quicktime",
    csv: "text/csv",
    json: "application/json",
    xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    txt: "text/plain",
    html: "text/html",
    md: "text/markdown",
  };
  return map[targetFormat.toLowerCase()] ?? "application/octet-stream";
}

function getSharpFormat(targetFormat: string): keyof sharp.FormatEnum {
  const fmt = targetFormat.toLowerCase();
  if (fmt === "jpg") return "jpeg";
  return fmt as keyof sharp.FormatEnum;
}

// ─── Image conversion ──────────────────────────────────────────────────────────

async function convertImage(
  fileBuffer: Buffer,
  sourceMime: string,
  targetFormat: string,
  originalFilename: string,
): Promise<RenderResult> {
  const sharpFormat = getSharpFormat(targetFormat);
  const options: sharp.SharpOptions =
    sourceMime === "image/svg+xml" ? { density: 300 } : {};

  const outputBuffer = await sharp(fileBuffer, options)
    .toFormat(sharpFormat)
    .toBuffer();

  const baseName = originalFilename.replace(/\.[^.]+$/, "");
  const bundle: ConvertBundle = {
    type: "convert-bundle",
    outputFilename: `${baseName}.${targetFormat.toLowerCase()}`,
    outputMime: getTargetMime(targetFormat),
    outputBase64: outputBuffer.toString("base64"),
    method: "direct",
  };

  return {
    filename: "convert-bundle.json",
    contentType: "application/json",
    buffer: Buffer.from(JSON.stringify(bundle)),
  };
}

// ─── Audio/video conversion ────────────────────────────────────────────────────

async function convertAV(
  fileBuffer: Buffer,
  sourceMime: string,
  targetFormat: string,
  originalFilename: string,
): Promise<RenderResult> {
  // Derive source format from MIME for -f flag
  const sourceFmtMap: Record<string, string> = {
    "audio/mpeg": "mp3",
    "audio/mp3": "mp3",
    "audio/wav": "wav",
    "audio/ogg": "ogg",
    "audio/flac": "flac",
    "audio/aac": "aac",
    "video/mp4": "mp4",
    "video/webm": "webm",
    "video/x-matroska": "matroska",
    "video/x-msvideo": "avi",
    "video/quicktime": "mov",
  };
  const sourceFmt = sourceFmtMap[sourceMime.toLowerCase()] ?? sourceMime.split("/").pop() ?? "mp3";
  const targetFmt = targetFormat.toLowerCase() === "mkv" ? "matroska" : targetFormat.toLowerCase();

  const outputBuffer = await new Promise<Buffer>((resolve, reject) => {
    const chunks: Buffer[] = [];
    const proc = spawn(ffmpegPath as unknown as string, [
      "-f", sourceFmt,
      "-i", "pipe:0",
      "-f", targetFmt,
      "pipe:1",
    ]);

    proc.stdout.on("data", (chunk: Buffer) => chunks.push(chunk));
    proc.stderr.on("data", () => {
      // suppress ffmpeg stderr noise
    });
    proc.on("error", reject);
    proc.on("close", (code) => {
      if (code !== 0) {
        reject(new Error(`ffmpeg exited with code ${code}`));
        return;
      }
      resolve(Buffer.concat(chunks));
    });

    proc.stdin.write(fileBuffer);
    proc.stdin.end();
  });

  const baseName = originalFilename.replace(/\.[^.]+$/, "");
  const bundle: ConvertBundle = {
    type: "convert-bundle",
    outputFilename: `${baseName}.${targetFormat.toLowerCase()}`,
    outputMime: getTargetMime(targetFormat),
    outputBase64: outputBuffer.toString("base64"),
    method: "direct",
  };

  return {
    filename: "convert-bundle.json",
    contentType: "application/json",
    buffer: Buffer.from(JSON.stringify(bundle)),
  };
}

// ─── Data format conversion ────────────────────────────────────────────────────

type JsonRow = Record<string, unknown>;

function csvToJsonRows(fileBuffer: Buffer): JsonRow[] {
  return parseCsv(fileBuffer, { columns: true, skip_empty_lines: true }) as JsonRow[];
}

function jsonRowsToCsv(rows: JsonRow[]): Buffer {
  if (rows.length === 0) return Buffer.from("");
  const headers = Object.keys(rows[0]!);
  const lines = [
    headers.map((h) => JSON.stringify(h)).join(","),
    ...rows.map((row) =>
      headers.map((h) => {
        const val = row[h];
        if (val === null || val === undefined) return "";
        return JSON.stringify(String(val));
      }).join(",")
    ),
  ];
  return Buffer.from(lines.join("\n"));
}

function xlsxToJsonRows(fileBuffer: Buffer): JsonRow[] {
  const wb = xlsx.read(fileBuffer, { type: "buffer" });
  const firstSheet = wb.SheetNames[0];
  if (!firstSheet) return [];
  return xlsx.utils.sheet_to_json<JsonRow>(wb.Sheets[firstSheet]!);
}

function jsonRowsToXlsx(rows: JsonRow[]): Buffer {
  const ws = xlsx.utils.json_to_sheet(rows);
  const wb = xlsx.utils.book_new();
  xlsx.utils.book_append_sheet(wb, ws, "Sheet1");
  return Buffer.from(xlsx.write(wb, { type: "buffer", bookType: "xlsx" }) as Buffer);
}

async function convertData(
  fileBuffer: Buffer,
  sourceMime: string,
  targetFormat: string,
  originalFilename: string,
): Promise<RenderResult> {
  const srcNorm = sourceMime.toLowerCase();
  const tgtNorm = targetFormat.toLowerCase();

  let outputBuffer: Buffer;

  if ((srcNorm === "text/csv" || srcNorm === "csv") && (tgtNorm === "json")) {
    const rows = csvToJsonRows(fileBuffer);
    outputBuffer = Buffer.from(JSON.stringify(rows, null, 2));
  } else if ((srcNorm === "application/json" || srcNorm === "json") && (tgtNorm === "csv")) {
    const rows = JSON.parse(fileBuffer.toString("utf-8")) as JsonRow[];
    outputBuffer = jsonRowsToCsv(Array.isArray(rows) ? rows : [rows]);
  } else if (
    (srcNorm === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || srcNorm === "xlsx") &&
    (tgtNorm === "json")
  ) {
    const rows = xlsxToJsonRows(fileBuffer);
    outputBuffer = Buffer.from(JSON.stringify(rows, null, 2));
  } else if ((srcNorm === "application/json" || srcNorm === "json") && (tgtNorm === "xlsx")) {
    const rows = JSON.parse(fileBuffer.toString("utf-8")) as JsonRow[];
    outputBuffer = jsonRowsToXlsx(Array.isArray(rows) ? rows : [rows]);
  } else if ((srcNorm === "text/csv" || srcNorm === "csv") && (tgtNorm === "xlsx")) {
    const rows = csvToJsonRows(fileBuffer);
    outputBuffer = jsonRowsToXlsx(rows);
  } else if (
    (srcNorm === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || srcNorm === "xlsx") &&
    (tgtNorm === "csv")
  ) {
    const rows = xlsxToJsonRows(fileBuffer);
    outputBuffer = jsonRowsToCsv(rows);
  } else {
    throw new Error(`Unsupported data conversion: ${sourceMime} → ${targetFormat}`);
  }

  const baseName = originalFilename.replace(/\.[^.]+$/, "");
  const bundle: ConvertBundle = {
    type: "convert-bundle",
    outputFilename: `${baseName}.${tgtNorm}`,
    outputMime: getTargetMime(tgtNorm),
    outputBase64: outputBuffer.toString("base64"),
    method: "direct",
  };

  return {
    filename: "convert-bundle.json",
    contentType: "application/json",
    buffer: Buffer.from(JSON.stringify(bundle)),
  };
}

// ─── AI-bridge fallback ────────────────────────────────────────────────────────

async function convertViaAiBridge(
  fileBuffer: Buffer,
  sourceMime: string,
  targetFormat: string,
  originalFilename: string,
): Promise<RenderResult> {
  const systemPrompt = `Convert the following ${sourceMime} content to ${targetFormat} format. Return ONLY the converted content, no explanation.`;

  // Determine if the source is text-based
  const isTextBased =
    sourceMime.startsWith("text/") ||
    sourceMime === "application/json" ||
    sourceMime === "image/svg+xml";

  let userContent: string;
  if (isTextBased) {
    userContent = fileBuffer.toString("utf-8");
  } else {
    userContent = `Convert this binary ${sourceMime} file "${originalFilename}" to ${targetFormat} format. Provide the best-effort conversion or transformation.`;
  }

  const result = await puterChatComplete([
    { role: "system", content: systemPrompt },
    { role: "user", content: userContent },
  ]);

  const outputBuffer = Buffer.from(result, "utf-8");
  const baseName = originalFilename.replace(/\.[^.]+$/, "");
  const bundle: ConvertBundle = {
    type: "convert-bundle",
    outputFilename: `${baseName}.${targetFormat.toLowerCase()}`,
    outputMime: getTargetMime(targetFormat),
    outputBase64: outputBuffer.toString("base64"),
    method: "ai-bridge",
  };

  return {
    filename: "convert-bundle.json",
    contentType: "application/json",
    buffer: Buffer.from(JSON.stringify(bundle)),
  };
}

// ─── Main renderer ─────────────────────────────────────────────────────────────

export async function renderConvert(
  input: Record<string, unknown>,
): Promise<RenderResult> {
  const fileBase64 =
    typeof input.fileBase64 === "string" ? input.fileBase64 : "";
  const sourceMime =
    typeof input.sourceMime === "string" ? input.sourceMime : "application/octet-stream";
  const targetFormat =
    typeof input.targetFormat === "string" ? input.targetFormat : "txt";
  const originalFilename =
    typeof input.originalFilename === "string"
      ? input.originalFilename
      : `file.${targetFormat}`;

  const fileBuffer = Buffer.from(fileBase64, "base64");

  // Route by format category
  if (isImageFormat(sourceMime) && isImageFormat(targetFormat)) {
    return convertImage(fileBuffer, sourceMime, targetFormat, originalFilename);
  }

  if (isAVFormat(sourceMime) || isAVFormat(targetFormat)) {
    return convertAV(fileBuffer, sourceMime, targetFormat, originalFilename);
  }

  if (isDataFormat(sourceMime) || isDataFormat(targetFormat)) {
    return convertData(fileBuffer, sourceMime, targetFormat, originalFilename);
  }

  // All other pairs: AI-bridge fallback
  return convertViaAiBridge(fileBuffer, sourceMime, targetFormat, originalFilename);
}
