import fs from 'fs';
import path from 'path';
import os from 'os';
import { execFile } from 'child_process';

/**
 * PDFix integration — runs the official "make-accessible" command from the
 * PDFix command line tool (https://pdfix.net/support/pdfix-command-line/):
 *
 *   pdfix_app make-accessible -i <input.pdf> -o <output.pdf> [-l <lang>] [-t <title>]
 *
 * Configure via environment variables:
 *   PDFIX_CLI_PATH     absolute path to pdfix_app(.exe)   (required to enable)
 *   PDFIX_EMAIL        licence e-mail   (-m/--email)      (optional)
 *   PDFIX_LICENSE_KEY  licence key      (-k/--key)        (optional)
 *   PDFIX_TIMEOUT_MS   per-document timeout, default 180000
 *
 * When the CLI is not configured/available the caller falls back to the
 * built-in pdf-lib pipeline so the application keeps working.
 */

export interface PdfixConfig {
  cliPath: string;
  email?: string;
  licenseKey?: string;
  timeoutMs: number;
}

export function getPdfixConfig(): PdfixConfig {
  return {
    cliPath: process.env.PDFIX_CLI_PATH || '',
    email: process.env.PDFIX_EMAIL || undefined,
    licenseKey: process.env.PDFIX_LICENSE_KEY || undefined,
    timeoutMs: Number(process.env.PDFIX_TIMEOUT_MS || 180000),
  };
}

/**
 * Tuned auto-tagging pipeline (see resources/make_accessible_custom.json):
 *   • add_tags.sequential_headings = true      — enforce sequential H1..H6
 *   • fix_headings.fix_heading_levels = true   — infer levels from font style
 *   • fix_headings.renumber_headings  = 1      — align to next level instead of
 *                                                inserting empty headings (was 2)
 *   • + set_content_language / set_tag_language — language on content & tags,
 *                                                missing from the stock pipeline
 * Applied through the CLI `batch` sub-command (the `make-accessible -c` flag
 * expects a different schema and silently produces no output).
 */
export function getCommandTemplatePath(): string {
  return process.env.PDFIX_COMMAND_PATH ||
    path.join(__dirname, '..', 'resources', 'make_accessible_custom.json');
}

export interface PdfixStatus {
  enabled: boolean;
  cliPath: string;
  /** Only means an e-mail + key are configured — validity is proven at run time. */
  licenseConfigured: boolean;
  reason?: string;
}

/** True when PDFix rejected the licence (CLI error 420). */
export function isAuthFailure(text: string): boolean {
  return /Authorization failed|Error:\s*420\b/i.test(text);
}

/** Is the PDFix CLI configured and present on disk? */
export function getPdfixStatus(): PdfixStatus {
  const cfg = getPdfixConfig();
  if (!cfg.cliPath) {
    return {
      enabled: false, cliPath: '', licenseConfigured: false,
      reason: 'PDFIX_CLI_PATH is not set — using the built-in pdf-lib fallback.',
    };
  }
  if (!fs.existsSync(cfg.cliPath)) {
    return {
      enabled: false, cliPath: cfg.cliPath, licenseConfigured: false,
      reason: `PDFix CLI not found at ${cfg.cliPath} — using the built-in pdf-lib fallback.`,
    };
  }
  return {
    enabled: true,
    cliPath: cfg.cliPath,
    licenseConfigured: Boolean(cfg.email && cfg.licenseKey),
    reason: cfg.email && cfg.licenseKey
      ? 'Licence credentials configured (validity is only confirmed when a document is processed).'
      : 'No PDFIX_EMAIL / PDFIX_LICENSE_KEY set — PDFix runs in trial mode.',
  };
}

export interface MakeAccessibleResult {
  ok: boolean;
  stdout: string;
  stderr: string;
  /** Which pipeline ran: the tuned batch config, or the stock make-accessible. */
  pipeline: 'tuned' | 'default';
  error?: string;
}

/** Write a run-specific copy of the tuned config with the language filled in. */
function materialiseCommandFile(lang: string): string | null {
  const template = getCommandTemplatePath();
  if (!fs.existsSync(template)) return null;
  try {
    const json = fs.readFileSync(template, 'utf-8').replace(/__LANG__/g, lang);
    const out = path.join(os.tmpdir(), `pdfix-cmd-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
    fs.writeFileSync(out, json, 'utf-8');
    return out;
  } catch {
    return null;
  }
}

/** PDFix `batch` writes a JSON report to stderr; surface a real failure from it. */
function batchReportError(stderr: string): string | null {
  try {
    const report = JSON.parse(stderr);
    const st = report?.status;
    if (!st) return null;
    if (String(st.exit_code) !== '0') {
      return `PDFix action "${st.action?.name ?? '?'}" failed: ${st.error ?? st.message ?? 'unknown error'}`;
    }
    return null;
  } catch {
    return null; // not JSON — nothing to report
  }
}

/**
 * Make a PDF accessible with PDFix.
 *
 * Uses the tuned pipeline via `batch -c <config>` when the config template is
 * available (better heading levels + content/tag language); otherwise falls
 * back to the stock `make-accessible` sub-command.
 * Resolves with ok:false (never throws) so callers can fall back cleanly.
 */
export function makeAccessible(
  inputPath: string,
  outputPath: string,
  opts: { title?: string; lang?: string } = {}
): Promise<MakeAccessibleResult> {
  const cfg = getPdfixConfig();
  const status = getPdfixStatus();
  if (!status.enabled) {
    return Promise.resolve({ ok: false, stdout: '', stderr: '', pipeline: 'default', error: status.reason });
  }

  // PDFix resolves paths against its own working directory — always pass absolute ones.
  const input = path.resolve(inputPath);
  const output = path.resolve(outputPath);
  const lang = opts.lang || 'en-US';

  const commandFile = materialiseCommandFile(lang);
  const pipeline: 'tuned' | 'default' = commandFile ? 'tuned' : 'default';

  // Global licence flags come before the sub-command.
  const args: string[] = [];
  if (cfg.email && cfg.licenseKey) args.push('--email', cfg.email, '--key', cfg.licenseKey);
  if (commandFile) {
    // Tuned pipeline. Language/title come from the config itself.
    args.push('batch', '-i', input, '-o', output, '-c', commandFile);
  } else {
    args.push('make-accessible', '-i', input, '-o', output, '-l', lang);
    if (opts.title) args.push('-t', opts.title);
  }

  const cleanup = () => {
    if (commandFile && fs.existsSync(commandFile)) {
      try { fs.unlinkSync(commandFile); } catch { /* best effort */ }
    }
  };

  return new Promise((resolve) => {
    execFile(
      cfg.cliPath,
      args,
      { timeout: cfg.timeoutMs, maxBuffer: 10 * 1024 * 1024, windowsHide: true },
      (err, stdout, stderr) => {
        const out = String(stdout || '');
        const errOut = String(stderr || '');
        cleanup();

        if (err) {
          const authFailed = isAuthFailure(out + errOut + err.message);
          resolve({
            ok: false, stdout: out, stderr: errOut, pipeline,
            error: authFailed
              ? 'PDFix LICENCE REJECTED (error 420, Authorization failed) — check PDFIX_EMAIL / PDFIX_LICENSE_KEY in backend/.env.'
              : err.message,
          });
          return;
        }
        const reportErr = batchReportError(errOut);
        if (reportErr) {
          resolve({ ok: false, stdout: out, stderr: errOut, pipeline, error: reportErr });
          return;
        }
        // The CLI can exit 0 without producing output on a licence/parse problem.
        if (!fs.existsSync(output) || fs.statSync(output).size === 0) {
          resolve({ ok: false, stdout: out, stderr: errOut, pipeline, error: 'PDFix produced no output file.' });
          return;
        }
        resolve({ ok: true, stdout: out, stderr: errOut, pipeline });
      }
    );
  });
}

/** Scratch path for intermediate files (e.g. partial page extraction). */
export function tempPdfPath(prefix: string): string {
  return path.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.pdf`);
}

/**
 * Extract the tag structure tree of a PDF as JSON (extract-data --doc-struct-tree).
 * Returns the parsed `struct_tree` object, or null when PDFix is unavailable,
 * the command fails, or the document has no tags.
 */
export function extractStructTree(inputPath: string): Promise<Record<string, unknown> | null> {
  const cfg = getPdfixConfig();
  const status = getPdfixStatus();
  if (!status.enabled) return Promise.resolve(null);

  const input = path.resolve(inputPath);
  const outJson = path.join(os.tmpdir(), `pdfix-tree-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);

  const args: string[] = [];
  if (cfg.email && cfg.licenseKey) args.push('--email', cfg.email, '--key', cfg.licenseKey);
  args.push('extract-data', '-i', input, '-o', outJson, '-f', '0', '--doc-struct-tree');

  return new Promise((resolve) => {
    execFile(cfg.cliPath, args, { timeout: 60000, maxBuffer: 64 * 1024 * 1024, windowsHide: true }, (err) => {
      try {
        if (err || !fs.existsSync(outJson)) { resolve(null); return; }
        const parsed = JSON.parse(fs.readFileSync(outJson, 'utf-8'));
        const tree = parsed?.struct_tree;
        resolve(tree && typeof tree === 'object' ? tree : null);
      } catch {
        resolve(null);
      } finally {
        if (fs.existsSync(outJson)) { try { fs.unlinkSync(outJson); } catch { /* best effort */ } }
      }
    });
  });
}
