import { readFile, unlink } from "node:fs/promises";
import { extractTextFromPdfBuffer, type PdfTextExtraction } from "@/lib/pdf/extractText";
import {
  createTempPdfPath,
  extractTextWithPdftotext,
  getPdfPageCountWithPdfinfo,
  ocrPdfToSearchablePdf,
  writePdfBufferToTempFile,
} from "@/lib/pdf/ocr";
import { OcrFailedError, PdfEncryptedError, PdfNoTextDetectedError, PdfUnreadableError } from "@/lib/pdf/errors";

const DEFAULT_MIN_DOC_CHARS = 200;
const DEFAULT_MIN_CHARS_PER_PAGE = 50;
const MIN_TEXT_CHARS_TO_ALLOW_DIRECT_FALLBACK = 30;

export type AnalyzePdfResult = {
  text: string;
  methodUsed: "direct" | "ocr";
  numPages: number;
  warnings: string[];
};

type AnalyzePdfDeps = {
  extractTextFromPdfBuffer: (buffer: Buffer) => Promise<PdfTextExtraction>;
  ocrPdfToSearchablePdf: (inputPath: string, outputPath: string, lang: string) => Promise<void>;
  writePdfBufferToTempFile: (buffer: Buffer, prefix?: string) => Promise<string>;
  createTempPdfPath: (prefix?: string) => string;
  readFile: (path: string) => Promise<Buffer>;
  unlink: (path: string) => Promise<void>;
  extractTextWithPdftotext: (inputPath: string) => Promise<string>;
  getPdfPageCountWithPdfinfo: (inputPath: string) => Promise<number>;
};

export type AnalyzePdfOptions = {
  ocrLanguage?: string;
  minDocChars?: number;
  minCharsPerPage?: number;
  deps?: Partial<AnalyzePdfDeps>;
};

function getDefaults(): AnalyzePdfDeps {
  return {
    extractTextFromPdfBuffer,
    ocrPdfToSearchablePdf,
    writePdfBufferToTempFile,
    createTempPdfPath,
    readFile: (path) => readFile(path),
    unlink: (path) => unlink(path),
    extractTextWithPdftotext,
    getPdfPageCountWithPdfinfo,
  };
}

function getTextLength(text: string) {
  return text.trim().length;
}

function shouldApplyOcr(text: string, numPages: number, minDocChars: number, minCharsPerPage: number) {
  const totalChars = getTextLength(text);
  const safePages = Math.max(numPages, 1);
  const charsPerPage = totalChars / safePages;
  return totalChars < minDocChars || charsPerPage < minCharsPerPage;
}

async function safeDelete(path?: string, deleteFile?: (path: string) => Promise<void>) {
  if (!path || !deleteFile) {
    return;
  }
  
  await deleteFile(path).catch(() => undefined);
}

export async function analyzePdf(buffer: Buffer, options: AnalyzePdfOptions = {}): Promise<AnalyzePdfResult> {
  const deps = { ...getDefaults(), ...(options.deps ?? {}) };
  const minDocChars = options.minDocChars ?? DEFAULT_MIN_DOC_CHARS;
  const minCharsPerPage = options.minCharsPerPage ?? DEFAULT_MIN_CHARS_PER_PAGE;
  const ocrLanguage = options.ocrLanguage ?? "spa+eng";
  const warnings: string[] = [];

  let direct: PdfTextExtraction | null = null;

  try {
    direct = await deps.extractTextFromPdfBuffer(buffer);
  } catch (error) {
    if (error instanceof PdfEncryptedError) {
      throw error;
    }
    warnings.push("La extraccion directa fallo; se intento OCR.");
  }

  if (direct && !shouldApplyOcr(direct.text, direct.numPages, minDocChars, minCharsPerPage)) {
    return {
      text: direct.text,
      methodUsed: "direct",
      numPages: direct.numPages,
      warnings,
    };
  }

  if (direct) {
    warnings.push("Se detecto poco texto en extraccion directa; se intento OCR.");
  }

  let inputPath: string | undefined;
  let outputPath: string | undefined;

  try {
    inputPath = await deps.writePdfBufferToTempFile(buffer, "acta-input");
    outputPath = deps.createTempPdfPath("acta-output");

    await deps.ocrPdfToSearchablePdf(inputPath, outputPath, ocrLanguage);

    const ocrBuffer = await deps.readFile(outputPath);
    let ocrResult: PdfTextExtraction;

    try {
      ocrResult = await deps.extractTextFromPdfBuffer(ocrBuffer);
    } catch (error) {
      if (!(error instanceof PdfUnreadableError)) {
        throw error;
      }

      const fallbackText = await deps.extractTextWithPdftotext(outputPath);
      const fallbackPages = (await deps.getPdfPageCountWithPdfinfo(outputPath)) || direct?.numPages || 1;
      warnings.push("Se uso pdftotext como respaldo tras OCR.");
      ocrResult = {
        text: fallbackText,
        numPages: fallbackPages,
      };
    }

    if (shouldApplyOcr(ocrResult.text, ocrResult.numPages, minDocChars, minCharsPerPage)) {
      throw new PdfNoTextDetectedError();
    }

    return {
      text: ocrResult.text,
      methodUsed: "ocr",
      numPages: ocrResult.numPages,
      warnings,
    };
  } catch (error) {
    const normalizedError = error instanceof PdfUnreadableError ? new OcrFailedError("OCR genero un resultado no legible.", { cause: error }) : error;

    if (direct && getTextLength(direct.text) >= MIN_TEXT_CHARS_TO_ALLOW_DIRECT_FALLBACK) {
      warnings.push("OCR no estuvo disponible; se devolvio texto directo del PDF.");
      return {
        text: direct.text,
        methodUsed: "direct",
        numPages: direct.numPages,
        warnings,
      };
    }

    throw normalizedError;
  } finally {
    await Promise.all([safeDelete(inputPath, deps.unlink), safeDelete(outputPath, deps.unlink)]);
  }
}
