import { existsSync } from 'fs';

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

const MIN_VALID_PDF_BYTES = 2_000;

const PDF_PRINT_STYLES = `
<style id="contract-pdf-print-fix">
  @media print {
    html, body {
      background: #fff !important;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }
    .cover {
      min-height: auto !important;
      page-break-after: always;
    }
    .cover-title {
      margin: 24mm 0 20mm !important;
    }
  }
</style>`;

@Injectable()
export class ContractPdfService {
  private readonly logger = new Logger(ContractPdfService.name);

  async htmlToPdfBuffer(html: string): Promise<Buffer> {
    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, 400));

        const pdf = await page.pdf({
          format: 'A4',
          printBackground: true,
          displayHeaderFooter: true,
          headerTemplate: '<span></span>',
          footerTemplate:
            '<div style="width:100%;font-size:8px;color:#64748b;text-align:center;font-family:Arial,sans-serif;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
          margin: { top: '18mm', bottom: '22mm', left: '14mm', right: '14mm' },
        });

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

  private injectPdfPrintStyles(html: string): string {
    if (html.includes('contract-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((p) => existsSync(p));
  }

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

  private htmlToBasicPdfBuffer(html: string): Buffer {
    const text = this.htmlToPlainText(html);
    const lines = this.wrapLines(text, 92).slice(0, 72);
    const body = lines
      .map((line, index) => {
        const prefix = index === 0 ? '50 790 Td' : '0 -12 Td';
        return `${prefix} (${this.escapePdfText(line)}) Tj`;
      })
      .join('\n');
    const stream = `BT\n/F1 10 Tf\n12 TL\n${body}\nET`;

    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[] = [0];
    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 += '% fallback padding\n';
    }
    const xrefOffset = Buffer.byteLength(pdf, 'latin1');
    pdf += `xref\n0 ${objects.length + 1}\n`;
    pdf += '0000000000 65535 f \n';
    for (const offset of offsets.slice(1)) {
      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');
  }

  private htmlToPlainText(html: string): string {
    return html
      .replace(/<script[\s\S]*?<\/script>/gi, '')
      .replace(/<style[\s\S]*?<\/style>/gi, '')
      .replace(/<\/(h1|h2|h3|p|div|tr|table|section|header|footer)>/gi, '\n')
      .replace(/<br\s*\/?>/gi, '\n')
      .replace(/<[^>]+>/g, ' ')
      .replace(/&nbsp;/gi, ' ')
      .replace(/&amp;/gi, '&')
      .replace(/&lt;/gi, '<')
      .replace(/&gt;/gi, '>')
      .replace(/&quot;/gi, '"')
      .replace(/&#39;/g, "'")
      .replace(/[^\x09\x0a\x0d\x20-\x7e]/g, '-')
      .split('\n')
      .map((line) => line.replace(/\s+/g, ' ').trim())
      .filter(Boolean)
      .join('\n');
  }

  private wrapLines(text: string, width: number): string[] {
    const wrapped: string[] = [];
    for (const rawLine of text.split('\n')) {
      const words = rawLine.split(' ');
      let line = '';
      for (const word of words) {
        const next = line ? `${line} ${word}` : word;
        if (next.length > width && line) {
          wrapped.push(line);
          line = word;
        } else {
          line = next;
        }
      }
      if (line) wrapped.push(line);
    }
    return wrapped.length ? wrapped : ['Document'];
  }

  private escapePdfText(value: string): string {
    return value.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
  }
}
