import { existsSync } from "fs";

import { Injectable, InternalServerErrorException, Logger } from "@nestjs/common";

const MIN_VALID_PDF_BYTES = 2_000;

const PDF_PRINT_STYLES = `
<style id="edr-pdf-print-fix">
  @media print {
    html, body {
      background: #fff !important;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }
  }
</style>`;

export interface PdfRenderOptions {
  /** Label used in logs to identify the document kind. */
  label?: string;
  /**
   * Degraded renderer used when Chromium is unavailable. Receives the
   * print-prepared HTML and must return a valid PDF buffer (≥ 2KB, `%PDF-`
   * header). When omitted, a generic single-page fallback is produced.
   */
  fallback?: (preparedHtml: string) => Buffer;
}

/**
 * Generic HTML → PDF renderer shared by every document producer (invoices,
 * receipts, warehouse release orders). Renders via headless Chromium when
 * available and degrades to a caller-supplied (or generic) hand-built PDF
 * otherwise. This is pure infrastructure — it knows nothing about invoices.
 */
@Injectable()
export class PdfRenderService {
  private readonly logger = new Logger(PdfRenderService.name);

  async htmlToPdfBuffer(html: string, opts: PdfRenderOptions = {}): Promise<Buffer> {
    const label = opts.label ?? "document";
    const preparedHtml = this.injectPdfPrintStyles(html);
    const executablePath = this.resolveExecutablePath();

    try {
      const puppeteer = await import("puppeteer");
      const launchOptions: import("puppeteer").LaunchOptions = {
        headless: true,
        args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"],
        ...(executablePath ? { executablePath } : {}),
      };

      const browser = await puppeteer.default.launch(launchOptions);
      try {
        const page = await browser.newPage();
        await page.setViewport({ width: 794, height: 1123, deviceScaleFactor: 1 });
        await page.setContent(preparedHtml, { waitUntil: "load", timeout: 60_000 });
        await page.emulateMediaType("print");
        await new Promise((resolve) => setTimeout(resolve, 250));

        const pdf = await page.pdf({
          format: "A4",
          printBackground: true,
          margin: { top: "16mm", bottom: "18mm", left: "14mm", right: "14mm" },
        });

        const buffer = Buffer.from(pdf);
        if (!this.isValidPdf(buffer)) {
          throw new Error(`Puppeteer produced invalid ${label} PDF (${buffer.length} bytes)`);
        }
        this.logger.log(
          `${label} PDF rendered (${buffer.length} bytes) via ${executablePath ?? "bundled Chromium"}`,
        );
        return buffer;
      } finally {
        await browser.close();
      }
    } catch (error) {
      this.logger.error(`${label} PDF failed (executable=${executablePath ?? "default"}): ${error}`);
      const fallback = (opts.fallback ?? ((h) => this.genericFallbackPdf(h)))(preparedHtml);
      if (this.isValidPdf(fallback)) {
        this.logger.warn(
          `Using ${label} PDF fallback (${fallback.length} bytes). Install Chromium or set PUPPETEER_EXECUTABLE_PATH for full layout rendering.`,
        );
        return fallback;
      }
      throw new InternalServerErrorException(
        `${label} PDF could not be generated. Ensure Chromium is installed or set PUPPETEER_EXECUTABLE_PATH.`,
      );
    }
  }

  private injectPdfPrintStyles(html: string): string {
    if (html.includes("edr-pdf-print-fix")) return html;
    if (html.includes("</head>")) {
      return html.replace("</head>", `${PDF_PRINT_STYLES}</head>`);
    }
    return `${PDF_PRINT_STYLES}${html}`;
  }

  private resolveExecutablePath(): string | undefined {
    const fromEnv = process.env.PUPPETEER_EXECUTABLE_PATH?.trim();
    if (fromEnv && existsSync(fromEnv)) return fromEnv;

    const candidates = [
      "/usr/bin/chromium",
      "/usr/bin/chromium-browser",
      "/usr/bin/google-chrome-stable",
      "/usr/bin/google-chrome",
    ];
    return candidates.find((path) => existsSync(path));
  }

  isValidPdf(buffer: Buffer): boolean {
    return buffer.length >= MIN_VALID_PDF_BYTES && buffer.subarray(0, 5).toString("ascii") === "%PDF-";
  }

  /** Minimal valid one-page PDF carrying a plain-text rendering of the document. */
  private genericFallbackPdf(html: string): Buffer {
    const text = html
      .replace(/<script[\s\S]*?<\/script>/gi, "")
      .replace(/<style[\s\S]*?<\/style>/gi, "")
      .replace(/<[^>]+>/g, " ")
      .replace(/&nbsp;/gi, " ")
      .replace(/&amp;/gi, "&")
      .replace(/&lt;/gi, "<")
      .replace(/&gt;/gi, ">")
      .replace(/[^\x20-\x7e]/g, " ")
      .replace(/\s+/g, " ")
      .trim()
      .slice(0, 900);

    const escape = (value: string) => value.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
    const lines = (text.match(/.{1,90}/g) ?? ["Document"]).slice(0, 40);
    const stream =
      "BT\n/F1 10 Tf\n36 800 Td\n12 TL\n" +
      lines.map((line, i) => `${i === 0 ? "" : "T*\n"}(${escape(line)}) Tj\n`).join("") +
      "ET";

    const objects = [
      "<< /Type /Catalog /Pages 2 0 R >>",
      "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
      "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>",
      "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
      `<< /Length ${Buffer.byteLength(stream, "latin1")} >>\nstream\n${stream}\nendstream`,
    ];

    let pdf = "%PDF-1.4\n";
    const offsets: number[] = [];
    objects.forEach((object, index) => {
      offsets.push(Buffer.byteLength(pdf, "latin1"));
      pdf += `${index + 1} 0 obj\n${object}\nendobj\n`;
    });
    while (Buffer.byteLength(pdf, "latin1") < MIN_VALID_PDF_BYTES) pdf += "% pad\n";
    const xrefOffset = Buffer.byteLength(pdf, "latin1");
    pdf += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
    for (const offset of offsets) pdf += `${String(offset).padStart(10, "0")} 00000 n \n`;
    pdf += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`;
    return Buffer.from(pdf, "latin1");
  }
}
