import {
  Injectable,
  NotFoundException,
  OnModuleDestroy,
  OnModuleInit,
  Logger,
} from '@nestjs/common';
import * as fs from 'fs';
import * as Handlebars from 'handlebars';
import * as puppeteer from 'puppeteer';
import dayjs from 'dayjs';
import { templates } from './template-registry';
import { CompaniesService } from '../companies/companies.service';
import { CompanyAuditsService } from '../company-audits/company-audits.service';
import { CompanyAuditStagesService } from '../company-audit-stages/company-audit-stages.service';
import { Template } from 'src/template/entities/template.entity';
import { TemplateContentService } from '../template_contents/template_contents.service'; // ✅ Import
import { TemplateService } from '../template/template.service';
import { JobsService } from '../jobs/jobs.service';

@Injectable()
export class PdfService implements OnModuleInit, OnModuleDestroy {
  [x: string]: any;
  private readonly logger = new Logger(PdfService.name);
  private browser: puppeteer.Browser | null = null;
  private concurrentPages = 5;
  private activePages = 0;
  private queue: (() => void)[] = [];
  private compiledCache = new Map<string, Handlebars.TemplateDelegate>();

  constructor(
    private readonly companiesService: CompaniesService,
    private readonly auditService: CompanyAuditsService,
    private readonly stageService: CompanyAuditStagesService,
    private readonly jobsService: JobsService,
    private readonly templateService: TemplateService,

    private readonly templateContentsService: TemplateContentService, // ✅ Inject
  ) {}

  async onModuleInit() {
    this.registerHandlebarsHelpers(); // Register helpers when service starts
    await this.launchBrowser();
  }

  async onModuleDestroy() {
    if (this.browser) await this.browser.close();
  }

  // 🟢 STEP 2: Add this new method anywhere inside the class
  private registerHandlebarsHelpers() {
    Handlebars.registerHelper('eq', (a: any, b: any) => a === b);

    Handlebars.registerHelper(
      'includesStandard',
      function (selectedStandards: any[], standardName: string) {
        if (!Array.isArray(selectedStandards)) return false;

        // Normalize both sides to make comparison safer
        const selectedNames = selectedStandards.map((s) =>
          (s.name || s).trim().toLowerCase(),
        );
        return selectedNames.includes(standardName.trim().toLowerCase());
      },
    );

    Handlebars.registerHelper('splitLines', (text: string) => {
      if (!text) return [];
      return text
        .split(/\r?\n/)
        .map((line) => line.trim())
        .filter(Boolean);
    });

    Handlebars.registerHelper('formatDate', (dateString: string) => {
      if (!dateString) return 'N/A';
      const d = dayjs(dateString);
      return d.isValid() ? d.format('DD MMMM YYYY') : 'N/A';
    });

    Handlebars.registerHelper('array', function (...args) {
      return args.slice(0, -1);
    });

    // ✅ New helper for scope splitting by comma
    Handlebars.registerHelper('splitScope', (text: string) => {
      if (!text) return [];
      return text
        .split(/,|\r?\n/) // split by comma or new line
        .map((line) => line.trim())
        .filter(Boolean);
    });
    Handlebars.registerHelper(
      'renderScope',
      function (
        scopeText: string,
        templateType: 'STAGE1' | 'STAGE2' | 'CERTIFICATE',
        arrowEnabled?: boolean,
      ) {
        if (!scopeText) return '';

        const items = scopeText
          .split(/,|\r?\n/)
          .map((i) => i.trim())
          .filter(Boolean);

        // 🟢 Stage 1 & 2 → ALWAYS arrows
        if (templateType === 'STAGE1' || templateType === 'STAGE2') {
          return new Handlebars.SafeString(`
        <ul class="scope-list">
          ${items.map((i) => `<li>➤ ${i}</li>`).join('')}
        </ul>
      `);
        }

        // 🟢 Certificate → dynamic
        if (arrowEnabled) {
          return new Handlebars.SafeString(`
        <ul class="scope-list">
          ${items.map((i) => `<li>➤ ${i}</li>`).join('')}
        </ul>
      `);
        }

        // 🟢 Certificate → plain text (NO arrows)
        return new Handlebars.SafeString(`
      <div class="scope-list-pre">
        ${items.join('<br>')}
      </div>
    `);
      },
    );

    this.logger.log('✅ Handlebars helpers registered');
  }

  // async onModuleDestroy() {
  //   if (this.browser) await this.browser.close();
  // }

  private async launchBrowser() {
    this.logger.log('🚀 Launching Puppeteer browser...');
    this.browser = await puppeteer.launch({
      headless: true,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
      ],
      defaultViewport: { width: 1200, height: 800 },
    });

    this.browser.on('disconnected', async () => {
      this.logger.warn('⚠️ Browser disconnected. Relaunching...');
      this.browser = null;
      await this.launchBrowser();
    });
  }

  async findByFilePath(filePath: string): Promise<Template | null> {
    return this.templateRepository.findOne({
      where: { filePath, isActive: true },
    });
  }

  // pdf.service.ts
  async getCompanyData(companyId: number, templateId?: number) {
    const company = await this.companiesService.findOne(companyId);
    if (!company) throw new NotFoundException(`Company ${companyId} not found`);

    const audits = this.auditService?.findByCompanyId
      ? await this.auditService.findByCompanyId(companyId)
      : [];

    const stages = this.stageService?.findByCompanyId
      ? await this.stageService.findByCompanyId(companyId)
      : [];

    // ✅ (Optional) Fetch job info if relevant for the PDF
    const jobs = this.jobsService?.findByCompanyId
      ? await this.jobsService.findByCompanyId(companyId)
      : [];

    // ✅ Fetch template and content (if a templateId was passed)
    let template: Template | null = null;
    if (templateId) {
      template = await this.templateService.findOne(templateId, false); // pass false if using the optional throw version
    }
    let templateContent = [];
    if (templateId && this.templateService?.findOne) {
      template = await this.templateService.findOne(templateId);

      // If your template content lives in a separate module
      if (this.templateContentService?.findByTemplateId) {
        const contentResponse =
          await this.templateContentService.findByTemplateId(templateId);
        templateContent = contentResponse?.templateContent || [];
      }
    }

    // ✅ Unique standards list
    const standardsList = Array.from(
      new Set(
        (company.standards || []).map((s) => s.name?.trim()).filter(Boolean),
      ),
    );

    // ✅ Format audit dates
    const formattedStages = stages.map((stage) => ({
      ...stage,
      auditDate: stage.auditDate
        ? dayjs(stage.auditDate).format('DD MMMM YYYY')
        : '',
    }));

    // ✅ Return all modules used by your template
    return {
      company: { ...company, standardsList },
      audits,
      stages: formattedStages,
      jobs,
      template,
      templateContent, // 🟢 Now your HTML {{#each templateContent}} will render correctly
    };
  }

  loadTemplate(stage: 'stage1' | 'stage2', templateName: string): string {
    const templatePath = templates[stage][templateName];
    if (!fs.existsSync(templatePath)) {
      throw new NotFoundException(`Template not found: ${templatePath}`);
    }
    return fs.readFileSync(templatePath, 'utf-8');
  }

  renderTemplate(
    stage: 'stage1' | 'stage2',
    templateName: string,
    data: any,
  ): string {
    console.log(
      '📌 PDF data passed to Handlebars:',
      JSON.stringify(data, null, 2),
    );

    const cacheKey = `${stage}:${templateName}`;
    const isDev = process.env.NODE_ENV !== 'production'; // check if dev environment

    // Load & compile template every time in development
    if (isDev || !this.compiledCache.has(cacheKey)) {
      const templateContent = this.loadTemplate(stage, templateName);
      const compiled = Handlebars.compile(templateContent);
      console.log(`✅ Loaded template content for ${templateName}`);

      // Cache only in production
      if (!isDev) {
        this.compiledCache.set(cacheKey, compiled);
        this.logger.log(`✅ Compiled and cached template: ${cacheKey}`);
      }
      return compiled(data);
    }

    // Use cached template in production
    const compiled = this.compiledCache.get(cacheKey)!;
    return compiled(data);
  }

  private async acquirePage(): Promise<puppeteer.Page> {
    if (this.activePages >= this.concurrentPages)
      await new Promise<void>((resolve) => this.queue.push(resolve));

    if (!this.browser || !this.browser.isConnected()) {
      this.logger.warn('⚠️ Browser not connected, re-launching...');
      await this.launchBrowser();
    }

    this.activePages++;
    return await this.browser!.newPage();
  }

  private releasePage(page: puppeteer.Page) {
    page.close().catch(() => {});
    this.activePages--;
    this.queue.shift()?.();
  }

  async generatePdf(html: string): Promise<Buffer> {
    const page = await this.acquirePage();
    try {
      await page.setContent(html, { waitUntil: 'domcontentloaded' });
      await page.addStyleTag({
        content: `
          @font-face { font-family: 'Source Sans Pro'; src: url('http://localhost:3000/static/fonts/SourceSansPro-Regular.woff2') format('woff2'); font-weight: 400; }
          @font-face { font-family: 'Source Sans Pro'; src: url('http://localhost:3000/static/fonts/SourceSansPro-Medium.woff2') format('woff2'); font-weight: 500; }
          @font-face { font-family: 'Source Sans Pro'; src: url('http://localhost:3000/static/fonts/SourceSansPro-SemiBold.woff2') format('woff2'); font-weight: 600; }
          body { font-family: 'Source Sans Pro', Arial, sans-serif; font-size: 10pt; }
        `,
      });
      await page.evaluateHandle('document.fonts.ready');

      const pdfBuffer = await page.pdf({
        width: '250mm', // match your custom page width
        height: '307mm', // match your custom page height
        printBackground: true,
        margin: { top: '3mm', right: '0mm', bottom: '3mm', left: '0mm' }, // optional
        preferCSSPageSize: false, // ❌ CSS page size ignored because you set custom width/height
      });

      this.logger.log('✅ PDF generated successfully');
      return Buffer.from(pdfBuffer);
    } catch (err) {
      this.logger.error('❌ PDF generation failed:', err);
      throw err;
    } finally {
      this.releasePage(page);
    }
  }

  // ✅ Fetch template content for a job
  async getJobTemplateContent(job: any) {
    let templateContent: any[] | null = null;
    if (job.template?.id) {
      templateContent = await this.templateContentsService.findByTemplate(
        job.template.id,
      );
    }
    return templateContent;
  }

  // ✅ Generate PDF for a Job including template content
  async generateJobPdf(
    job: any,
    stage: 'stage1' | 'stage2',
    templateName: string,
  ): Promise<Buffer> {
    console.log('🟢 generateJobPdf called');
    console.log('Job received:', job);
    console.log('Stage:', stage);
    console.log('Template Name:', templateName);

    const companyId = job.company?.id;
    if (!companyId) {
      throw new NotFoundException('Company not found for this job');
    }

    // ✅ Use templateSnapshot if available; fallback to template
    const templateToUse = job.templateSnapshot ?? job.template;

    // ✅ Get company data
    const companyData = await this.getCompanyData(companyId, templateToUse?.id);
    console.log('Fetched company data:', JSON.stringify(companyData, null, 2));

    // ✅ Use templateSnapshot contents if available, otherwise fetch from service
    const templateContents = job.templateSnapshot?.contents?.length
      ? job.templateSnapshot.contents
      : await this.getJobTemplateContent(job);
    console.log('Using template contents:', templateContents);

    // ✅ Map template content
    let formattedTemplateContent = (
      templateContents ||
      companyData.templateContent ||
      []
    ).map((item: any) => ({
      startTime: item.startTime,
      endTime: item.endTime,
      functionalArea: item.functional_area || 'Function Area',
      content: item.content
        ? item.content
            .split('\n')
            .map((line: string) => line.replace(/^- /, '').trim())
            .filter(Boolean)
        : [],
    }));

    // ✅ Convert time string to minutes
    const toMinutes = (time: string) => {
      const [h, m] = time.split(':').map(Number);
      return h * 60 + m;
    };

    // ✅ Sort template content by start time
    formattedTemplateContent.sort((a: any, b: any) => {
      return toMinutes(a.startTime) - toMinutes(b.startTime);
    });

    // ✅ FILTER BY SCHEDULE SLOT
    const scheduleSlot: 'MORNING' | 'AFTERNOON' = job.scheduleSlot || 'MORNING';

    if (scheduleSlot === 'MORNING') {
      formattedTemplateContent = formattedTemplateContent.filter(
        (item: any) => toMinutes(item.endTime) <= 13 * 60, // up to 13:00
      );
    }

    if (scheduleSlot === 'AFTERNOON') {
      formattedTemplateContent = formattedTemplateContent.filter(
        (item: any) => toMinutes(item.startTime) >= 14 * 60, // from 14:00
      );
    }

    // 🟢 Prepare data for Handlebars with templateSnapshot
    const dataForTemplate = {
      job,
      company: companyData.company,
      audits: companyData.audits,
      stages: companyData.stages,
      template: templateToUse, // 🟢 ensure snapshot is used
      templateContent: formattedTemplateContent,

      // 🟢 Job-level fields
      jobCodes: job.jobCodes || [],
      stage: job.stage || '',
      standards: job.standards || [],
      leadAuditor: job.leadAuditor || null,
      date: job.date || null,
      docReview: job.docReview || job.date || null, // 🟢 fallback to date
      numEmployees: job.numEmployees || null,
      naceEacCodes: job.naceEacCodes || '',
      md: job.md || '',
      mdRisk: job.mdRisk || '',
      expectedSurveillanceDate: job.expectedSurveillanceDate || '',
      expectedIADate: job.expectedIADate || '',
      expectedMRMDate: job.expectedMRMDate || '',
       expectedInitial_Stage1_Date: job.expectedInitial_Stage1_Date ||'',
        prepareDate: job.prepareDate ||'',
        approvedDate: job.approvedDate ||'',
      expectedInitialInquiryDate: job.expectedInitialInquiryDate || '',

      // 🟢 Add templateSnapshot explicitly if needed in HTML
      templateSnapshot: job.templateSnapshot ?? null,
    };

    console.log(
      '🧩 Full data passed to Handlebars:',
      JSON.stringify(dataForTemplate, null, 2),
    );

    // Render HTML using Handlebars
    const html = this.renderTemplate(stage, templateName, dataForTemplate);
    console.log('Rendered HTML length:', html.length);

    // Generate PDF
    const pdfBuffer = await this.generatePdf(html);
    console.log('PDF buffer generated, size:', pdfBuffer.length);

    return pdfBuffer;
  }

  // -----------------------------------------------------------------------------
  // 🆕 NEW METHOD — Generate PDF from static HTML file stored in assets folder
  // -----------------------------------------------------------------------------
  // pdf.service.ts
  async generateStaticPdf(fileName: string): Promise<Buffer> {
    const filePath = `src/assets/pdfs/${fileName}.html`;

    if (!fs.existsSync(filePath)) {
      throw new NotFoundException(`Static PDF template not found: ${filePath}`);
    }

    const html = fs.readFileSync(filePath, 'utf-8');

    // Re-use your existing PDF generator
    return this.generatePdf(html);
  }
}
