import * as fs from 'fs';
import * as Handlebars from 'handlebars';
import {
  Controller,
  Get,
  Param,
  Query,
  Res,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import type { Response } from 'express';
import * as QRCode from 'qrcode';

import { PdfService } from './pdf.service';
import { TemplateService } from './../template/template.service';
import { JobsService } from './../jobs/jobs.service';
import { templates } from './template-registry';
import { CertificatesService } from './../certificates/certificates.service';
import { certificateTemplates } from './../certificates/certificate-templates';
import puppeteer from 'puppeteer';

@Controller('pdf')
export class PdfController {
  private readonly logger = new Logger(PdfController.name);

  constructor(
    private readonly pdfService: PdfService,
    private readonly templateService: TemplateService,
    private readonly jobsService: JobsService,
    private readonly certificateService: CertificatesService,
  ) {}

  // Helper function to format dates as "22 JAN 2024"
  private formatCarbonDate(dateStr: string | Date): string {
    if (!dateStr) return '';

    const months = [
      'JAN',
      'FEB',
      'MAR',
      'APR',
      'MAY',
      'JUN',
      'JUL',
      'AUG',
      'SEP',
      'OCT',
      'NOV',
      'DEC',
    ];
    const date = new Date(dateStr);

    const day = date.getDate();
    const month = months[date.getMonth()];
    const year = date.getFullYear();

    return `${day} ${month} ${year}`;
  }

  // Helper function to add spaces to EA codes like "28,31,33" -> "28, 31, 33"
  private formatEaCodes(codes: string): string {
    if (!codes) return '';
    return codes
      .split(',')
      .map((c) => c.trim())
      .join(', ');
  }

  /**
   * ✅ GET /pdf/:stage/:templateName?companyId=24517&templateId=9
   */
  @Get(':stage/:templateName')
  async generatePdf(
    @Param('stage') stage: string,
    @Param('templateName') templateName: string,
    @Query('companyId') companyId: number,
    @Query('templateId') templateId: number,
    @Query('debug') debug: boolean,
    @Res() res: Response,
  ) {
    if (!companyId) return res.status(400).send('❌ companyId is required');

    this.logger.log(
      `📄 Generating PDF for stage=${stage}, template=${templateName}, companyId=${companyId}, templateId=${templateId}`,
    );

    const template = await this.templateService.findByFilePath(templateName);
    if (!template) return res.status(404).send('❌ Template not found in DB');

    const stageKey = template.stageName.toLowerCase().replace(' ', '') as
      | 'stage1'
      | 'stage2';
    if (!['stage1', 'stage2'].includes(stageKey))
      return res.status(400).send('❌ Invalid stage');

    const resolvedTemplateId = templateId ?? template.id;
    const data = await this.pdfService.getCompanyData(
      companyId,
      resolvedTemplateId,
    );

    const templateContent = data?.templateContent ?? [];
    const templateIdValue = data?.template?.id ?? resolvedTemplateId;

    if (debug) {
      return res.json({
        message: '✅ Debug mode — showing data instead of PDF',
        companyId,
        stage,
        templateName,
        templateId: templateIdValue,
        templateContentCount: templateContent.length,
        templateContent,
      });
    }

    const templateKeyStr = template.filePath.replace('.hbs', '');
    const templateExists =
      templates[stageKey] && templates[stageKey][templateKeyStr];

    if (!templateExists) {
      return res
        .status(404)
        .send(`❌ Template '${templateKeyStr}' not found in registry`);
    }

    const html = this.pdfService.renderTemplate(
      stageKey,
      templateKeyStr as keyof (typeof templates)['stage1'],
      data,
    );

    const pdfBuffer = await this.pdfService.generatePdf(html);

    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': `inline; filename="${templateName}-${companyId}.pdf"`,
      'Content-Length': pdfBuffer.length,
    });

    this.logger.log('✅ PDF successfully sent to client');
    return res.send(pdfBuffer);
  }

  /**
   * ✅ Generate PDF for a Job
   */
  @Get('job/:jobId/:stage/:templateName')
  async generateJobPdfRoute(
    @Param('jobId') jobId: string,
    @Param('stage') stage: 'stage1' | 'stage2',
    @Param('templateName') templateName: string,
    @Query('companyId') companyId: number,
    @Res() res: Response,
  ) {
    console.log('🟢 Route generateJobPdfRoute called with:', {
      jobId,
      stage,
      templateName,
      companyId,
    });

    let job;
    if (isNaN(Number(jobId))) {
      job = await this.jobsService.findByJobCode(jobId);
    } else {
      job = await this.jobsService.findOne(Number(jobId));
    }

    if (!job)
      throw new NotFoundException(`Job not found with ID/code: ${jobId}`);

    const pdfBuffer = await this.pdfService.generateJobPdf(
      job,
      stage,
      templateName,
    );

    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': `inline; filename="job-${jobId}-${templateName}.pdf"`,
      'Content-Length': pdfBuffer.length,
    });

    console.log('✅ Job PDF successfully sent to client');
    return res.send(pdfBuffer);
  }

  /**
   * ✅ Generate PDF for multiple jobs dynamically
   * Example: /pdf/job-multi/stage1/form-04-5-stage-1-audit-plan-ims?companyId=24517&jobIds=FSMS:2753,EMS:5954
   */
  @Get('job-multi/:stage/:templateName')
  async generateMultiJobPdf(
    @Param('stage') stage: 'stage1' | 'stage2',
    @Param('templateName') templateName: string,
    @Query('companyId') companyId: number,
    @Query('jobIds') jobIds: string, // comma-separated
    @Res() res: Response,
  ) {
    if (!companyId) return res.status(400).send('❌ companyId is required');
    if (!jobIds) return res.status(400).send('❌ jobIds is required');

    const template = await this.templateService.findByFilePath(templateName);
    if (!template) return res.status(404).send('❌ Template not found');

    const stageKey = template.stageName.toLowerCase().replace(' ', '') as
      | 'stage1'
      | 'stage2';
    if (!['stage1', 'stage2'].includes(stageKey))
      return res.status(400).send('❌ Invalid stage');

    const jobIdArray = jobIds.split(',').map((j) => j.trim());

    const allJobData: any[] = [];
    for (const jId of jobIdArray) {
      let job;
      if (isNaN(Number(jId))) {
        job = await this.jobsService.findByJobCode(jId);
      } else {
        job = await this.jobsService.findOne(Number(jId));
      }
      if (!job) continue;
      allJobData.push(job);
    }

    if (!allJobData.length)
      return res.status(404).send('❌ No valid jobs found');

    const data = await this.pdfService.getCompanyData(companyId, template.id);

    // Merge static content + dynamic jobs content, but **separate each job**
    const finalData = {
      ...data,
      templateContent: [
        ...(data.templateContent || []),
        ...allJobData.map((job) => ({
          jobId: job.id,
          jobCode: job.jobCodes?.join(', ') || '',
          content: job.template?.templateContent || [],
        })),
      ],
      jobs: allJobData,
    };

    const html = this.pdfService.renderTemplate(
      stageKey,
      template.filePath,
      finalData,
    );
    const pdfBuffer = await this.pdfService.generatePdf(html);

    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': `inline; filename="${templateName}-${companyId}.pdf"`,
      'Content-Length': pdfBuffer.length,
    });

    return res.send(pdfBuffer);
  }

  // pdf.controller.ts
  @Get('static/:file')
  async generateStaticPdf(
    @Param('file') fileName: string,
    @Res() res: Response,
  ) {
    const pdfBuffer = await this.pdfService.generateStaticPdf(fileName);

    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': `inline; filename="${fileName}.pdf"`,
    });

    return res.end(pdfBuffer);
  }
  @Get('certificate/:certificateNo/:slug/:filePath')
  async generateCertificatePdf(
    @Param('certificateNo') certificateNo: string,
    @Param('slug') slug: string,
    @Param('filePath') filePath: string,
    @Query('companyId') companyId: number,
    @Query('domain') domain: 'local' | 'international' = 'local', // optional override
    @Res() res: Response,
  ) {
    if (!companyId) return res.status(400).send('❌ companyId is required');

    // Fetch certificate from DB
    const certificate =
      await this.certificateService.findByCertificateNo(certificateNo);
    if (!certificate) {
      throw new NotFoundException(`Certificate not found: ${certificateNo}`);
    }

    // Use certificate's verification domain if available
    const qrDomain: 'local' | 'international' =
      (certificate.verification_domain as 'local' | 'international') || 'local';

    // Generate QR code dynamically with domain
    let qrCodeDataUrl: string | null = null;
    if (certificate.qrcode_token) {
      qrCodeDataUrl = await this.certificateService.generateQrCode(
        certificate.qrcode_token,
        certificate.fingerprint,
        qrDomain, // dynamic domain
      );
    }

    // Lookup template
    const templatePath = certificateTemplates[slug];
    if (!templatePath) {
      throw new NotFoundException(
        `Certificate template not registered: ${slug}`,
      );
    }

    const fullPath = `src/${templatePath}`;
    if (!fs.existsSync(fullPath)) {
      throw new NotFoundException(`Template file missing: ${fullPath}`);
    }

    const templateContent = fs.readFileSync(fullPath, 'utf8');
    const compiled = Handlebars.compile(templateContent);

    const formattedCertificate = {
      ...certificate,
      ea_codes: this.formatEaCodes(certificate.ea_codes),
      originally_registered: this.formatCarbonDate(
        certificate.originally_registered,
      ),
      issue_date: this.formatCarbonDate(certificate.issue_date),
      expire_date: this.formatCarbonDate(certificate.expire_date),
    };
    // Compile HTML with QR code
    const html = compiled({
      certificate: formattedCertificate,
      company: certificate.company,
      standard: certificate.standard,
      qrCode: qrCodeDataUrl,
    });

    // Puppeteer options for A4 PDF
    const browser = await puppeteer.launch({
      headless: true,
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
      defaultViewport: null,
    });

    try {
      const page = await browser.newPage();
     await page.setContent(html, { waitUntil: 'domcontentloaded' });
      await page.evaluateHandle('document.fonts.ready');

      // Wait for all images (including QR) to load
      await page.evaluate(async () => {
        const images = Array.from(document.images);
        await Promise.all(
          images.map((img) => {
            if (img.complete) return;
            return new Promise<void>((resolve) => {
              img.onload = () => resolve();
              img.onerror = () => resolve();
            });
          }),
        );
      });

      // Generate PDF in **A4 size**, zero margins
      const pdfBuffer = await page.pdf({
        format: 'A4',
        printBackground: true,
        margin: { top: '0mm', bottom: '0mm', left: '0mm', right: '0mm' },
      });

      // Send PDF to client
      res.set({
        'Content-Type': 'application/pdf',
        'Content-Disposition': `inline; filename="${certificateNo}.pdf"`,
      });

      return res.send(pdfBuffer);
    } finally {
      await browser.close();
    }
  }
}
