// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.
// NERF is a trademark of defconxt.

/**
 * report-exporter.js — Pandoc bridge for PDF, DOCX, and Markdown export.
 *
 * Converts NERF engagement reports (Markdown) into branded PDF and DOCX
 * output using pandoc as the rendering engine. Falls back gracefully when
 * optional PDF engines are missing.
 *
 * @module export/report-exporter
 */

import { execSync, execFileSync } from 'node:child_process';
import { existsSync, copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const TEMPLATES_DIR = join(__dirname, 'templates');
const DEFAULT_CSS = join(TEMPLATES_DIR, 'report.css');
const BRANDED_CSS = join(TEMPLATES_DIR, 'nerf-report.css');

const debug = process.env.NERF_DEBUG === '1'
  ? (/** @type {string} */ msg) => process.stderr.write(`[export] ${msg}\n`)
  : () => {};

/**
 * Check if pandoc is installed and return version info.
 * @returns {{ version: string, path: string } | null} Version info or null if not installed.
 */
export function checkPandoc() {
  try {
    const raw = execSync('pandoc --version', {
      encoding: 'utf-8',
      timeout: 5000,
      stdio: ['pipe', 'pipe', 'pipe'],
    });
    const firstLine = raw.split('\n')[0] || '';
    // "pandoc 3.5" -> "3.5"
    const version = firstLine.replace(/^pandoc\s+/, '').trim();
    const pandocPath = execFileSync('which', ['pandoc'], {
      encoding: 'utf-8',
      timeout: 3000,
      stdio: ['pipe', 'pipe', 'pipe'],
    }).trim();
    debug(`pandoc found: v${version} at ${pandocPath}`);
    return { version, path: pandocPath };
  } catch {
    debug('pandoc not found');
    return null;
  }
}

/**
 * Detect which PDF engine is available for pandoc.
 * Checks in order: weasyprint, wkhtmltopdf, pdflatex (LaTeX).
 * @returns {{ engine: string, flag: string } | null}
 */
export function detectPdfEngine() {
  const engines = [
    { name: 'weasyprint', flag: '--pdf-engine=weasyprint' },
    { name: 'wkhtmltopdf', flag: '--pdf-engine=wkhtmltopdf' },
    { name: 'pdflatex', flag: '--pdf-engine=pdflatex' },
    { name: 'xelatex', flag: '--pdf-engine=xelatex' },
  ];

  for (const { name, flag } of engines) {
    try {
      execFileSync('which', [name], {
        timeout: 3000,
        stdio: ['pipe', 'pipe', 'pipe'],
      });
      debug(`PDF engine found: ${name}`);
      return { engine: name, flag };
    } catch {
      // try next
    }
  }

  debug('no PDF engine found');
  return null;
}

/**
 * Build common pandoc arguments.
 * @param {string} inputPath
 * @param {string} outputPath
 * @param {object} [options]
 * @param {string} [options.title]
 * @param {string} [options.css]
 * @param {string[]} [options.extraArgs]
 * @returns {string[]}
 */
function buildBaseArgs(inputPath, outputPath, options = {}) {
  const args = [
    inputPath,
    '-o', outputPath,
    '--standalone',
    '--from=markdown+yaml_metadata_block',
  ];

  if (options.title) {
    args.push(`--metadata=title:${options.title}`);
  }

  if (options.extraArgs) {
    args.push(...options.extraArgs);
  }

  return args;
}

/**
 * Run pandoc with the given arguments.
 * @param {string[]} args
 * @param {number} [timeout=60000]
 * @returns {{ stdout: string, stderr: string }}
 */
function runPandoc(args, timeout = 60000) {
  const pandoc = checkPandoc();
  if (!pandoc) {
    throw new Error(
      'pandoc is not installed. Install it:\n' +
      '  Arch Linux: pacman -S pandoc\n' +
      '  Ubuntu/Debian: apt install pandoc\n' +
      '  macOS: brew install pandoc\n' +
      '  All platforms: https://pandoc.org/installing.html'
    );
  }

  debug(`pandoc ${args.join(' ')}`);

  try {
    const stdout = execFileSync('pandoc', args, {
      encoding: 'utf-8',
      timeout,
      stdio: ['pipe', 'pipe', 'pipe'],
      maxBuffer: 10 * 1024 * 1024, // 10MB
    });
    return { stdout: stdout || '', stderr: '' };
  } catch (err) {
    const stderr = err.stderr?.toString() || '';
    const message = stderr || err.message || 'pandoc execution failed';
    debug(`pandoc error: ${message}`);
    throw new Error(`pandoc export failed: ${message}`);
  }
}

/**
 * Export Markdown to PDF.
 *
 * Tries PDF engines in order: weasyprint, wkhtmltopdf, pdflatex, xelatex.
 * For weasyprint/wkhtmltopdf, applies the NERF CSS stylesheet.
 * For LaTeX engines, uses pandoc's default LaTeX template.
 *
 * @param {string} inputPath - Path to input Markdown file.
 * @param {string} outputPath - Path to output PDF file.
 * @param {object} [options]
 * @param {string} [options.title] - Document title metadata.
 * @param {string} [options.css] - Custom CSS path (defaults to NERF report.css).
 * @param {string} [options.pdfEngine] - Force a specific PDF engine.
 * @param {number} [options.timeout] - Pandoc timeout in ms (default 60000).
 * @returns {{ outputPath: string, engine: string }}
 */
export function exportToPdf(inputPath, outputPath, options = {}) {
  if (!existsSync(inputPath)) {
    throw new Error(`Input file not found: ${inputPath}`);
  }

  // Ensure output directory exists
  const outDir = dirname(outputPath);
  if (!existsSync(outDir)) {
    mkdirSync(outDir, { recursive: true });
  }

  // Determine PDF engine
  let engine;
  if (options.pdfEngine) {
    engine = { engine: options.pdfEngine, flag: `--pdf-engine=${options.pdfEngine}` };
  } else {
    engine = detectPdfEngine();
  }

  if (!engine) {
    throw new Error(
      'No PDF engine found. Install one of:\n' +
      '  weasyprint: pip install weasyprint\n' +
      '  wkhtmltopdf: pacman -S wkhtmltopdf / apt install wkhtmltopdf\n' +
      '  pdflatex (TeX Live): pacman -S texlive-core / apt install texlive-latex-base\n' +
      '  xelatex (TeX Live): pacman -S texlive-core / apt install texlive-xetex'
    );
  }

  const extraArgs = [engine.flag];
  const isHtmlEngine = ['weasyprint', 'wkhtmltopdf'].includes(engine.engine);

  if (isHtmlEngine) {
    // HTML-based engines use CSS for styling
    const cssPath = options.css || DEFAULT_CSS;
    if (existsSync(cssPath)) {
      extraArgs.push(`--css=${cssPath}`);
    }
    // Force HTML intermediate for CSS-based engines
    extraArgs.push('--to=html5');
  } else {
    // LaTeX engines: set margins and document class
    extraArgs.push(
      '-V', 'geometry:margin=2.5cm',
      '-V', 'documentclass=report',
      '-V', 'colorlinks=true',
      '-V', 'linkcolor=blue',
      '-V', 'urlcolor=blue',
    );
  }

  // Table of contents for longer reports
  extraArgs.push('--toc', '--toc-depth=3');

  const args = buildBaseArgs(inputPath, outputPath, {
    title: options.title,
    extraArgs,
  });

  runPandoc(args, options.timeout);
  debug(`PDF exported: ${outputPath} (engine: ${engine.engine})`);
  return { outputPath, engine: engine.engine };
}

/**
 * Export Markdown to DOCX.
 *
 * Uses a reference DOCX template if available for NERF branding.
 * Falls back to pandoc's default DOCX template.
 *
 * @param {string} inputPath - Path to input Markdown file.
 * @param {string} outputPath - Path to output DOCX file.
 * @param {object} [options]
 * @param {string} [options.title] - Document title metadata.
 * @param {string} [options.referenceDoc] - Custom reference DOCX path.
 * @param {number} [options.timeout] - Pandoc timeout in ms (default 60000).
 * @returns {{ outputPath: string }}
 */
export function exportToDocx(inputPath, outputPath, options = {}) {
  if (!existsSync(inputPath)) {
    throw new Error(`Input file not found: ${inputPath}`);
  }

  const outDir = dirname(outputPath);
  if (!existsSync(outDir)) {
    mkdirSync(outDir, { recursive: true });
  }

  const extraArgs = ['--toc', '--toc-depth=3'];

  // Use reference doc for branding if available
  const refDoc = options.referenceDoc || join(TEMPLATES_DIR, 'nerf-reference.docx');
  if (existsSync(refDoc)) {
    extraArgs.push(`--reference-doc=${refDoc}`);
    debug(`Using reference DOCX: ${refDoc}`);
  }

  const args = buildBaseArgs(inputPath, outputPath, {
    title: options.title,
    extraArgs,
  });

  runPandoc(args, options.timeout);
  debug(`DOCX exported: ${outputPath}`);
  return { outputPath };
}

/**
 * Export Markdown to formatted Markdown (normalize/copy).
 *
 * Useful as a fallback when pandoc is not available, or for
 * producing a cleaned-up Markdown version of the report.
 *
 * @param {string} inputPath - Path to input Markdown file.
 * @param {string} outputPath - Path to output Markdown file.
 * @returns {{ outputPath: string }}
 */
export function exportToMarkdown(inputPath, outputPath) {
  if (!existsSync(inputPath)) {
    throw new Error(`Input file not found: ${inputPath}`);
  }

  const outDir = dirname(outputPath);
  if (!existsSync(outDir)) {
    mkdirSync(outDir, { recursive: true });
  }

  // If pandoc is available, use it to normalize the markdown
  const pandoc = checkPandoc();
  if (pandoc) {
    const args = buildBaseArgs(inputPath, outputPath, {
      extraArgs: ['--to=gfm', '--wrap=auto'],
    });
    // Remove --standalone for markdown-to-markdown
    const idx = args.indexOf('--standalone');
    if (idx !== -1) args.splice(idx, 1);

    runPandoc(args);
    debug(`Markdown normalized: ${outputPath}`);
  } else {
    // Fallback: simple copy
    copyFileSync(inputPath, outputPath);
    debug(`Markdown copied: ${outputPath}`);
  }

  return { outputPath };
}

/**
 * Export Markdown to self-contained HTML.
 *
 * Uses pandoc with --to=html5 and branded CSS when available.
 * Falls back to wrapping raw markdown in an HTML5 shell with inline CSS.
 *
 * @param {string} inputPath - Path to input Markdown file.
 * @param {string} outputPath - Path to output HTML file.
 * @param {object} [options]
 * @param {string} [options.title] - Document title for the HTML page.
 * @param {string} [options.css] - Custom CSS path (defaults to nerf-report.css).
 * @param {number} [options.timeout] - Pandoc timeout in ms (default 60000).
 * @returns {{ outputPath: string, selfContained: boolean }}
 */
export function exportToHtml(inputPath, outputPath, options = {}) {
  if (!existsSync(inputPath)) {
    throw new Error(`Input file not found: ${inputPath}`);
  }

  const outDir = dirname(outputPath);
  if (!existsSync(outDir)) {
    mkdirSync(outDir, { recursive: true });
  }

  const title = options.title || 'Security Report';
  const cssPath = options.css || BRANDED_CSS;
  const pandoc = checkPandoc();

  if (pandoc) {
    // Determine self-containment flag based on pandoc version
    const majorVersion = parseInt(pandoc.version.split('.')[0], 10);
    const selfContainFlag = majorVersion >= 3 ? '--embed-resources' : '--self-contained';

    const extraArgs = [
      '--to=html5',
      selfContainFlag,
      `--metadata=pagetitle:${title}`,
    ];

    if (existsSync(cssPath)) {
      extraArgs.push(`--css=${cssPath}`);
    }

    const args = buildBaseArgs(inputPath, outputPath, {
      title,
      extraArgs,
    });

    runPandoc(args, options.timeout);
    debug(`HTML exported (pandoc, self-contained): ${outputPath}`);
    return { outputPath, selfContained: true };
  }

  // Fallback: wrap raw markdown in an HTML5 shell with inline CSS
  const markdownContent = readFileSync(inputPath, 'utf-8');
  const css = existsSync(cssPath) ? readFileSync(cssPath, 'utf-8') : '';

  const html = [
    '<!DOCTYPE html>',
    '<html>',
    '<head>',
    '<meta charset="utf-8">',
    `<title>${title}</title>`,
    `<style>${css}</style>`,
    '</head>',
    '<body class="nerf-report">',
    markdownContent,
    '</body>',
    '</html>',
  ].join('\n');

  writeFileSync(outputPath, html, 'utf-8');
  debug(`HTML exported (fallback, raw markdown): ${outputPath}`);
  return { outputPath, selfContained: false };
}

/**
 * Get available export formats and their readiness status.
 * @returns {{ format: string, available: boolean, engine: string | null, note: string }[]}
 */
export function getAvailableFormats() {
  const pandoc = checkPandoc();
  const pdfEngine = pandoc ? detectPdfEngine() : null;

  return [
    {
      format: 'pdf',
      available: !!(pandoc && pdfEngine),
      engine: pdfEngine?.engine || null,
      note: !pandoc
        ? 'pandoc not installed'
        : !pdfEngine
          ? 'no PDF engine (install weasyprint, wkhtmltopdf, or texlive)'
          : `using ${pdfEngine.engine}`,
    },
    {
      format: 'docx',
      available: !!pandoc,
      engine: null,
      note: pandoc ? 'ready' : 'pandoc not installed',
    },
    {
      format: 'markdown',
      available: true,
      engine: null,
      note: pandoc ? 'pandoc normalization' : 'copy (pandoc not installed)',
    },
    {
      format: 'html',
      available: true,
      engine: null,
      note: pandoc
        ? 'ready (self-contained via pandoc)'
        : 'basic (pandoc not installed, raw markdown in HTML shell)',
    },
  ];
}
