/**
 * Tier 2 helper — rasterize PDF pages to PNG buffers so scanned/image-only
 * invoices can be OCR'd (tesseract.js works on images, not PDF text streams).
 *
 * Primary: `pdftoppm` (Poppler) — fast & robust; present on the dev box and the
 * documented prod install (mirrors the whisper-cpp binary convention).
 * Fallback: pure-Node pdfjs-dist + node-canvas, so the feature degrades
 * gracefully if Poppler is missing. Both paths are fail-soft → return [] and the
 * pipeline yields a low-confidence/empty result rather than throwing.
 */

import { execFile } from 'child_process'
import { promisify } from 'util'
import { writeFileSync, readFileSync, readdirSync, unlinkSync, existsSync, mkdirSync, rmSync } from 'fs'
import { join } from 'path'

const execFileAsync = promisify(execFile)

const DEFAULT_DPI = 300
const MAX_PAGES = 8 // bound OCR time on very long PDFs; we log when we truncate

const PDFTOPPM_CANDIDATES = [
  process.env.PDFTOPPM_PATH,
  '/opt/homebrew/bin/pdftoppm',
  '/usr/local/bin/pdftoppm',
  '/usr/bin/pdftoppm',
  'pdftoppm'
].filter(Boolean) as string[]

const resolvePdftoppm = async (): Promise<string | null> => {
  for (const cand of PDFTOPPM_CANDIDATES) {
    try {
      await execFileAsync(cand, ['-v'])
      return cand
    } catch (e: any) {
      // pdftoppm -v exits non-zero but prints to stderr when it exists; treat
      // "command not found"/ENOENT as absent, anything else as present.
      if (e?.code === 'ENOENT') continue
      return cand
    }
  }
  return null
}

const rasterizeWithPdftoppm = async (bin: string, pdfBuffer: Buffer, dpi: number, maxPages: number): Promise<Buffer[]> => {
  const tmpDir = join(process.cwd(), '.tmp-ap-ocr')
  if (!existsSync(tmpDir)) mkdirSync(tmpDir, { recursive: true })
  const stamp = Date.now().toString(36) + Math.random().toString(36).slice(2, 6)
  const inPath = join(tmpDir, `${stamp}.pdf`)
  const outPrefix = join(tmpDir, `${stamp}-page`)
  writeFileSync(inPath, pdfBuffer)
  try {
    await execFileAsync(bin, ['-png', '-r', String(dpi), '-l', String(maxPages), inPath, outPrefix], {
      maxBuffer: 1024 * 1024 * 64
    })
    const produced = readdirSync(tmpDir)
      .filter(f => f.startsWith(`${stamp}-page`) && f.endsWith('.png'))
      .sort()
    const buffers = produced.map(f => readFileSync(join(tmpDir, f)))
    // cleanup
    try { unlinkSync(inPath) } catch {}
    for (const f of produced) { try { unlinkSync(join(tmpDir, f)) } catch {} }
    return buffers
  } catch (err) {
    try { unlinkSync(inPath) } catch {}
    throw err
  }
}

const rasterizeWithCanvas = async (pdfBuffer: Buffer, dpi: number, maxPages: number): Promise<Buffer[]> => {
  const pdfjs: any = await import('pdfjs-dist/legacy/build/pdf.mjs')
  const { createCanvas } = await import('canvas')
  const pdf = await pdfjs.getDocument({ data: new Uint8Array(pdfBuffer), isEvalSupported: false, verbosity: 0 }).promise
  const scale = dpi / 72
  const out: Buffer[] = []
  const pageCount = Math.min(pdf.numPages, maxPages)
  for (let p = 1; p <= pageCount; p++) {
    const page = await pdf.getPage(p)
    const viewport = page.getViewport({ scale })
    const canvas = createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height))
    const ctx = canvas.getContext('2d')
    await page.render({ canvasContext: ctx as any, viewport }).promise
    out.push(canvas.toBuffer('image/png'))
  }
  try { await pdf.cleanup() } catch {}
  return out
}

export interface RasterizeResult {
  pages: Buffer[]
  truncated: boolean
  engine: 'pdftoppm' | 'canvas' | 'none'
}

export const rasterizePdf = async (pdfBuffer: Buffer, opts: { dpi?: number; maxPages?: number } = {}): Promise<RasterizeResult> => {
  const dpi = opts.dpi ?? DEFAULT_DPI
  const maxPages = opts.maxPages ?? MAX_PAGES
  let pages: Buffer[] = []
  let engine: RasterizeResult['engine'] = 'none'

  const bin = await resolvePdftoppm()
  if (bin) {
    try {
      pages = await rasterizeWithPdftoppm(bin, pdfBuffer, dpi, maxPages)
      engine = 'pdftoppm'
    } catch (e: any) {
      console.warn('[Inbox] pdftoppm rasterize failed, trying canvas fallback:', e?.message || e)
    }
  }
  if (pages.length === 0) {
    try {
      pages = await rasterizeWithCanvas(pdfBuffer, dpi, maxPages)
      engine = 'canvas'
    } catch (e: any) {
      console.warn('[Inbox] canvas rasterize fallback failed:', e?.message || e)
      engine = 'none'
    }
  }

  // We can't know the true page count cheaply without parsing; flag truncation
  // when we hit the cap so the caller can log "only first N pages OCR'd".
  const truncated = pages.length >= MAX_PAGES
  if (truncated) console.warn(`[Inbox] rasterize capped at ${MAX_PAGES} pages`)
  return { pages, truncated, engine }
}

/**
 * Render a small page-1 thumbnail as a JPEG data URL (for the inbox cards).
 * Strapi does NOT generate thumbnails for PDFs, so we make our own. Fail-soft → ''.
 */
export const renderThumbDataUrl = async (pdfBuffer: Buffer, width = 220): Promise<string> => {
  try {
    const r = await rasterizePdf(pdfBuffer, { dpi: 110, maxPages: 1 })
    if (!r.pages[0]) return ''
    const sharp = (await import('sharp')).default
    const buf = await sharp(r.pages[0]).resize({ width, withoutEnlargement: true }).jpeg({ quality: 62 }).toBuffer()
    return 'data:image/jpeg;base64,' + buf.toString('base64')
  } catch (e: any) {
    console.warn('[Inbox] thumbnail render failed:', e?.message || e)
    return ''
  }
}

/** Best-effort temp-dir sweep (call occasionally; safe to ignore failures). */
export const cleanupRasterTmp = () => {
  const tmpDir = join(process.cwd(), '.tmp-ap-ocr')
  try { if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true }) } catch {}
}

export default rasterizePdf
