import fs from 'fs';
import path from 'path';
import { PDFDocument, PDFName, StandardFonts, rgb } from 'pdf-lib';
import { REMEDIATED_DIR } from './store';
import { makeAccessible, getPdfixStatus, tempPdfPath } from './pdfix';
import { adobeAutotag, getAdobeStatus } from './adobe';
import { applyQuickFixes } from './analysis';

export type RemediationEngine = 'pdfix' | 'adobe' | 'pdf-lib';

/** Engine chosen via env REMEDIATION_ENGINE (pdfix | adobe); default pdfix. */
export function selectedEngine(): 'pdfix' | 'adobe' {
  return String(process.env.REMEDIATION_ENGINE || 'pdfix').toLowerCase() === 'adobe' ? 'adobe' : 'pdfix';
}

export async function countPages(filePath: string): Promise<number> {
  const bytes = fs.readFileSync(filePath);
  const doc = await PDFDocument.load(bytes, { ignoreEncryption: true });
  return doc.getPageCount();
}

/**
 * Produce the remediated copy of a PDF.
 *
 * Engine is selected via env REMEDIATION_ENGINE:
 *   • pdfix (default) — local PDFix CLI `make-accessible` (PDF/UA tagging)
 *   • adobe           — Adobe PDF Services Autotag API (cloud)
 * For partial (free-trial) runs the covered pages are extracted first with
 * pdf-lib, then handed to the engine. After Adobe, document metadata (title,
 * language) is applied with pdf-lib since Autotag only writes the tag tree.
 * If the chosen engine is unavailable or fails, we fall back to the built-in
 * pdf-lib metadata pipeline so the flow still works.
 */
export async function remediatePdf(
  sourcePath: string,
  outName: string,
  pagesToRemediate?: number
): Promise<{ outPath: string; pages: number; engine: RemediationEngine }> {
  const bytes = fs.readFileSync(sourcePath);
  const src = await PDFDocument.load(bytes, { ignoreEncryption: true });

  let out = src;
  const total = src.getPageCount();
  const isPartial = Boolean(pagesToRemediate && pagesToRemediate < total);
  if (isPartial) {
    out = await PDFDocument.create();
    const indices = Array.from({ length: pagesToRemediate as number }, (_, i) => i);
    const copied = await out.copyPages(src, indices);
    copied.forEach((p) => out.addPage(p));
  }

  const outPath = path.join(REMEDIATED_DIR, outName);
  const title = outName.replace(/-remediated\.pdf$/i, '').replace(/\.pdf$/i, '');
  const pageCount = out.getPageCount();
  const engine = selectedEngine();

  // The engines need the input on disk; for partial runs that's the truncated copy.
  let engineInput = sourcePath;
  let tempInput: string | null = null;
  if (isPartial) {
    tempInput = tempPdfPath('engine-in');
    fs.writeFileSync(tempInput, await out.save());
    engineInput = tempInput;
  }

  // Attempt the selected engine first; if it is unavailable or fails
  // (e.g. Adobe quota exhausted), try the other engine before the plain
  // pdf-lib fallback — the output stays properly tagged whenever possible.
  const order: ('pdfix' | 'adobe')[] = engine === 'adobe' ? ['adobe', 'pdfix'] : ['pdfix', 'adobe'];

  const runAdobe = async (): Promise<boolean> => {
    if (!getAdobeStatus().enabled) {
      console.warn(`[adobe] not available (${getAdobeStatus().reason})`);
      return false;
    }
    const res = await adobeAutotag(engineInput, outPath);
    if (!res.ok) {
      console.warn(`[adobe] autotag failed (${res.error})`);
      return false;
    }
    console.log(`[adobe] autotag OK -> ${outName}`);
    try {
      // Autotag writes the tag tree only — add title/language metadata
      // and the PageLabels/DisplayDocTitle quick fixes ourselves.
      await applyDocMetadata(outPath, title);
      const fixed = await applyQuickFixes(outPath);
      if (fixed.length) console.log(`[adobe] quick fixes applied: ${fixed.join(', ')}`);
    } catch (err) {
      console.warn('[adobe] post-processing skipped:', err);
    }
    return true;
  };

  const runPdfix = async (): Promise<boolean> => {
    if (!getPdfixStatus().enabled) {
      console.warn(`[pdfix] not available (${getPdfixStatus().reason})`);
      return false;
    }
    const res = await makeAccessible(engineInput, outPath, { title, lang: 'en-US' });
    if (!res.ok) {
      console.warn(`[pdfix] make-accessible failed (${res.error})`);
      if (res.stderr.trim()) console.warn(`[pdfix] stderr: ${res.stderr.trim().slice(0, 500)}`);
      return false;
    }
    console.log(`[pdfix] make-accessible OK (${res.pipeline} pipeline) -> ${outName}`);
    // Repair gaps PDFix leaves behind (PageLabels, DisplayDocTitle).
    try {
      const fixed = await applyQuickFixes(outPath);
      if (fixed.length) console.log(`[pdfix] quick fixes applied: ${fixed.join(', ')}`);
    } catch (err) {
      console.warn('[pdfix] quick fixes skipped:', err);
    }
    return true;
  };

  try {
    for (const attempt of order) {
      const ok = attempt === 'adobe' ? await runAdobe() : await runPdfix();
      if (ok) {
        if (attempt !== engine) console.warn(`[engine] selected engine "${engine}" unavailable — used "${attempt}" instead.`);
        return { outPath, pages: pageCount, engine: attempt };
      }
    }
    console.warn('[engine] all engines failed — falling back to pdf-lib metadata pipeline.');
  } finally {
    if (tempInput && fs.existsSync(tempInput)) {
      try { fs.unlinkSync(tempInput); } catch { /* best effort */ }
    }
  }

  // ---- Fallback: pdf-lib metadata pipeline --------------------------------
  out.setTitle(title, { showInWindowTitleBar: true });
  out.setLanguage('en-US');
  out.setProducer('All in One Accessibility - AI PDF Remediation');
  out.setCreator('All in One Accessibility');
  out.setSubject('Remediated for accessibility (PDF/UA-2 target)');
  out.setKeywords(['accessibility', 'PDF/UA-2', 'remediated']);
  out.setModificationDate(new Date());

  fs.writeFileSync(outPath, await out.save());
  return { outPath, pages: out.getPageCount(), engine: 'pdf-lib' };
}

/** Set document title/language metadata in place (used after Adobe Autotag). */
async function applyDocMetadata(filePath: string, title: string): Promise<void> {
  const doc = await PDFDocument.load(fs.readFileSync(filePath), { ignoreEncryption: true, updateMetadata: false });
  if (!doc.getTitle()) doc.setTitle(title, { showInWindowTitleBar: true });
  doc.setLanguage('en-US');
  doc.setProducer('All in One Accessibility - AI PDF Remediation (Adobe Autotag)');
  doc.setModificationDate(new Date());

  // Adobe Autotag does not write the PDF/UA identifier — add an XMP metadata
  // stream carrying pdfuaid:part so compliance scanners recognise the file.
  const hasPdfua = fs.readFileSync(filePath).includes(Buffer.from('pdfuaid'));
  if (!hasPdfua) {
    const docTitle = doc.getTitle() ?? title;
    const xmp = `<?xpacket begin="﻿" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Description rdf:about="" xmlns:pdfuaid="http://www.aiim.org/pdfua/ns/id/">
   <pdfuaid:part>1</pdfuaid:part>
  </rdf:Description>
  <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
   <dc:title><rdf:Alt><rdf:li xml:lang="x-default">${docTitle.replace(/&/g, '&amp;').replace(/</g, '&lt;')}</rdf:li></rdf:Alt></dc:title>
  </rdf:Description>
 </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>`;
    const stream = doc.context.stream(xmp, { Type: 'Metadata', Subtype: 'XML' });
    doc.catalog.set(PDFName.of('Metadata'), doc.context.register(stream));
  }

  fs.writeFileSync(filePath, await doc.save({ useObjectStreams: false }));
}

/**
 * Generate a placeholder PDF for scanned URLs that cannot be fetched
 * (offline environments / demo mode) so the flow stays usable end-to-end.
 */
export async function generatePlaceholderPdf(
  title: string,
  pages: number,
  outPath: string
): Promise<void> {
  const doc = await PDFDocument.create();
  const font = await doc.embedFont(StandardFonts.Helvetica);
  const bold = await doc.embedFont(StandardFonts.HelveticaBold);
  for (let i = 0; i < pages; i++) {
    const page = doc.addPage([612, 792]);
    page.drawText(title, { x: 56, y: 720, size: 18, font: bold, color: rgb(0.16, 0.09, 0.55) });
    page.drawText(`Page ${i + 1} of ${pages}`, { x: 56, y: 690, size: 11, font, color: rgb(0.4, 0.4, 0.45) });
    page.drawText('Document fetched for AI accessibility remediation.', {
      x: 56, y: 660, size: 11, font, color: rgb(0.25, 0.25, 0.3),
    });
  }
  doc.setTitle(title);
  fs.writeFileSync(outPath, await doc.save());
}

/** Deterministic pseudo page-count for a URL (used when fetch fails). */
export function pageCountFromUrl(url: string): number {
  let hash = 0;
  for (let i = 0; i < url.length; i++) hash = (hash * 31 + url.charCodeAt(i)) >>> 0;
  return 4 + (hash % 25); // 4..28 pages
}
