import { randomUUID } from "node:crypto";
import { spawn } from "node:child_process";
import { tmpdir } from "node:os";
import path from "node:path";
import { writeFile } from "node:fs/promises";
import { OcrFailedError, OcrUnavailableError } from "@/lib/pdf/errors";

type CommandResult = {
  stdout: string;
  stderr: string;
};

function runCommand(command: string, args: string[]): Promise<CommandResult> {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      stdio: ["ignore", "pipe", "pipe"],
    });

    let stdout = "";
    let stderr = "";

    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
    });

    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });

    child.on("error", (error) => {
      if ((error as NodeJS.ErrnoException).code === "ENOENT") {
        reject(
          new OcrUnavailableError(
            "OCRmyPDF no esta disponible. Instala ocrmypdf, poppler-utils y tesseract-ocr (ver README).",
            { cause: error },
          ),
        );
        return;
      }

      reject(new OcrFailedError("No se pudo ejecutar OCRmyPDF.", { cause: error, stderr }));
    });

    child.on("close", (code) => {
      if (code === 0) {
        resolve({ stdout, stderr });
        return;
      }

      reject(
        new OcrFailedError("OCRmyPDF termino con error.", {
          stderr,
        }),
      );
    });
  });
}

function getOcrCommandArgs(inputPath: string, outputPath: string, lang: string) {
  return ["--skip-text", "--force-ocr", "-l", lang, inputPath, outputPath];
}

function normalizeExtractedText(rawText: string) {
  return rawText.replace(/\u0000/g, " ").replace(/\s+/g, " ").trim();
}

export function createTempPdfPath(prefix = "acta-ocr") {
  return path.join(tmpdir(), `${prefix}-${Date.now()}-${randomUUID()}.pdf`);
}

export async function writePdfBufferToTempFile(buffer: Buffer, prefix = "acta-input") {
  const tempPath = createTempPdfPath(prefix);
  await writeFile(tempPath, buffer);
  return tempPath;
}

export async function ocrPdfToSearchablePdf(inputPath: string, outputPath: string, lang = "spa+eng"): Promise<void> {
  if (!inputPath || !outputPath) {
    throw new OcrFailedError("La ruta de entrada/salida para OCR es invalida.");
  }

  if (path.resolve(inputPath) === path.resolve(outputPath)) {
    throw new OcrFailedError("La salida de OCR no puede sobrescribir el archivo de entrada.");
  }

  const primaryArgs = getOcrCommandArgs(inputPath, outputPath, lang);

  try {
    await runCommand("ocrmypdf", ["--version"]);
  } catch (error) {
    if (error instanceof OcrUnavailableError) {
      throw error;
    }

    throw new OcrFailedError("No fue posible validar OCRmyPDF.", { cause: error });
  }

  try {
    await runCommand("ocrmypdf", primaryArgs);
  } catch (error) {
    if (
      error instanceof OcrFailedError &&
      error.stderr &&
      /--skip-text/i.test(error.stderr) &&
      /--force-ocr/i.test(error.stderr) &&
      /choose only one|conflict|incompatible/i.test(error.stderr)
    ) {
      await runCommand("ocrmypdf", ["--skip-text", "-l", lang, inputPath, outputPath]);
      return;
    }

    throw error;
  }
}

export async function extractTextWithPdftotext(inputPath: string): Promise<string> {
  if (!inputPath) {
    throw new OcrFailedError("Ruta invalida para pdftotext.");
  }

  try {
    const result = await runCommand("pdftotext", ["-layout", "-enc", "UTF-8", inputPath, "-"]);
    return normalizeExtractedText(result.stdout);
  } catch (error) {
    if (error instanceof OcrUnavailableError) {
      throw new OcrFailedError("pdftotext no esta disponible. Instala poppler-utils.", { cause: error });
    }

    if (error instanceof OcrFailedError) {
      throw new OcrFailedError("pdftotext fallo al extraer texto del PDF OCR.", { cause: error, stderr: error.stderr });
    }

    throw new OcrFailedError("pdftotext fallo al procesar el PDF OCR.", { cause: error });
  }
}

export async function getPdfPageCountWithPdfinfo(inputPath: string): Promise<number> {
  if (!inputPath) {
    return 0;
  }

  try {
    const result = await runCommand("pdfinfo", [inputPath]);
    const match = result.stdout.match(/Pages:\s+(\d+)/i);
    return match ? Number.parseInt(match[1] ?? "0", 10) : 0;
  } catch {
    return 0;
  }
}
