const puppeteer = require('puppeteer');
const path = require('path');
const { exec } = require('child_process');

const fs = require('fs').promises;

const gs = require('ghostscript4js');

const { PDFDocument, rgb } = require('pdf-lib');

const { Catalog } = require('../models/catalog');
const { MyPage } = require('../models/mypage');

const tools = require('../tools/general');
const shared_consts = require('../tools/shared_nodejs');

const { compress } = require('compress-pdf');
const { RaccoltaCataloghi } = require('../models/raccoltacataloghi');

class GenPdf {
  constructor(idapp) {
    this.idapp = idapp;
    this.browser = null;
  }

  async launch() {
    this.browser = await puppeteer.launch({
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
  }

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

  inchesToPixels(inches, stampa) {
    if (stampa) {
      return Math.floor(inches * 300);
    } else {
      return Math.floor(inches * 96);
    }
  }

  mmToInches(mm) {
    return mm / 25.4;
  }

  async autoScroll(page) {
    console.log('inizia a scrollare...');

    // Esegui lo scroll fino a quando tutta la pagina non è stata scrollata
    await page.evaluate(async () => {
      await new Promise((resolve) => {
        let totalHeight = 0;
        const distance = 100; // distanza dello scroll ad ogni intervallo
        const delay = 40; // tempo di intervallo in ms

        const scroll = async () => {
          // Esegui lo scroll
          window.scrollBy(0, distance);
          totalHeight += distance;

          // Verifica se è stato scrollato tutto il corpo della pagina
          if (totalHeight < document.body.scrollHeight) {
            setTimeout(scroll, delay); // Se non è finito lo scroll, continua
          } else {
            resolve(); // Scroll terminato
          }
        };

        scroll(); // Avvia lo scroll
      });
    });
  }

  async generatePdfFromUrl(url, filenameOut, options = {}) {
    if (!this.browser) {
      throw new Error('Browser non avviato. Chiama launch() prima.');
    }

    const page = await this.browser.newPage();
    const maxTentativi = 3;

    try {
      console.log(`caricamento pagina: ${url}`);
      await page.goto(url, { waitUntil: 'networkidle0' });
      await page.waitForNavigation({ waitUntil: 'networkidle0' }).catch(() => {});

      page.on('console', (msg) => {
        if (msg.type() === 'error') {
          console.error('Errore nella pagina:', msg.text());
        }
      });

      await tools.attendiNSecondi(6);
      let success = false;
      let numTentativi1 = 0;
      console.log(`Cerco .pdf-section...`);
      while (numTentativi1 < maxTentativi) {
        try {
          await page.waitForSelector('.pdf-section', { timeout: 10000 });
          console.log(`          .pdf-section trovato !...`);
          success = true;
          break;
        } catch (e) {
          console.log(`Tentativo ${numTentativi1 + 1}/${maxTentativi} fallito, ASPETTO DI PIU 10 secondi`);
          await tools.attendiNSecondi(10);
        }
        numTentativi1++;
      }

      await this.autoScroll(page);

      await tools.attendiNSecondi(5);

      // Seleziona tutte le sezioni da stampare
      let sectionHandles = await page.$$('.pdf-section');
      let numTentativi = 0;
      while (sectionHandles.length === 0 && numTentativi < maxTentativi) {
        console.log(
          `Nessuna sezione .pdf-section trovata nella pagina, quindi ASPETTO DI PIU ${numTentativi + 1}/${maxTentativi}`
        );
        await tools.attendiNSecondi(5);
        sectionHandles = await page.$$('.pdf-section');
        numTentativi++;
      }

      if (sectionHandles.length === 0) {
        throw new Error(`Nessuna sezione .pdf-section trovata nella pagina dopo ${maxTentativi} tentativi`);
      }

      const pdfBuffers = [];

      for (const sectionHandle of sectionHandles) {
        // Nascondi tutte le sezioni
        await page.evaluate(() => {
          document.querySelectorAll('.pdf-section').forEach((el) => (el.style.display = 'none'));
        });

        // Mostra solo la sezione corrente
        await sectionHandle.evaluate((el) => (el.style.display = 'block'));

        // Calcola dimensioni della sezione
        const { width, height } = await sectionHandle.evaluate((el) => ({
          width: el.scrollWidth, // piccolo padding
          height: el.scrollHeight, // piccolo padding
        }));

        // console.log(`Larghezza: ${width}px, Altezza: ${height}px`);

        // Imposta viewport dinamico
        await page.setViewport({ width, height });

        // Genera pdf buffer per questa pagina
        const pdfBuffer = await page.pdf({
          printBackground: true,
          width: `${width}px`,
          height: `${height}px`,
          margin: { top: '0', bottom: '0', left: '0', right: '0' },
        });

        pdfBuffers.push(pdfBuffer);
      }

      // Unisci tutti i PDF
      const mergedPdf = await PDFDocument.create();

      for (const pdfBytes of pdfBuffers) {
        const pdf = await PDFDocument.load(pdfBytes);
        const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
        copiedPages.forEach((page) => mergedPdf.addPage(page));
      }

      const mergedPdfFile = await mergedPdf.save();

      const finalFilePath = path.resolve(process.cwd(), filenameOut);
      await fs.writeFile(finalFilePath, mergedPdfFile);

      // Ripristina tutte le sezioni visibili
      await page.evaluate(() => {
        document.querySelectorAll('.pdf-section').forEach((el) => (el.style.display = 'block'));
      });

      console.log(`PDF finale generato: ${finalFilePath}`);

      return finalFilePath;
    } catch (error) {
      console.error('Errore durante generazione PDF:', error);
      throw error;
    } finally {
      await page.close();
    }
  }

  async convertPDF_ChildProcess(inputFile, outputFile, widthpx, heightpx, compressionLevel = 'screen') {
    const { spawn } = require('child_process');
    const path = require('path');

    widthpx = widthpx * 2;
    heightpx = heightpx * 2;

    console.log('=== CONVERSIONE CON CHILD_PROCESS E COMPRESSIONE ===');

    // Verifica input
    if (!(await tools.isFileExistsAsync(inputFile))) {
      throw new Error(`File input non trovato: ${inputFile}`);
    }

    // Assicurati che la directory output esista
    const outputDir = path.dirname(outputFile);
    try {
      await fs.mkdir(outputDir, { recursive: true });
    } catch (error) {
      // Directory già esistente, ok
    }

    // Parametri di compressione ottimizzati
    const compressionSettings = {
      /*maximum: [
        '-dPDFSETTINGS=/screen',
        '-dDownsampleColorImages=true',
        '-dColorImageResolution=72',
        '-dDownsampleGrayImages=true',
        '-dGrayImageResolution=72',
        '-dDownsampleMonoImages=true',
        '-dMonoImageResolution=72',
      ],
      high: [
        '-dPDFSETTINGS=/ebook',
        '-dDownsampleColorImages=true',
        '-dColorImageResolution=150',
        '-dDownsampleGrayImages=true',
        '-dGrayImageResolution=150',
      ],*/
      printer: ['-dPDFSETTINGS=/printer', '-dDownsampleColorImages=true', '-dColorImageResolution=300'],
      screen: [
        '-dPDFSETTINGS=/screen',
        '-dDownsampleColorImages=true',
        '-dColorImageResolution=96',
        '-dDownsampleGrayImages=true',
        '-dGrayImageResolution=96',
      ],
    };

    return new Promise((resolve, reject) => {
      const args = [
        '-sDEVICE=pdfwrite',
        '-dCompatibilityLevel=1.4',
        '-dNOPAUSE',
        '-dQUIET',
        '-dBATCH',
        '-dSAFER',

        // Parametri di compressione
        ...(compressionSettings[compressionLevel] || compressionSettings['screen']),
        '-dCompressFonts=true',
        '-dSubsetFonts=true',
        '-dColorImageFilter=/DCTEncode',
        '-dGrayImageFilter=/DCTEncode',
        '-dEmbedAllFonts=true',

        // Dimensioni pagina
        `-g${widthpx}x${heightpx}`,
        '-dFIXEDMEDIA',
        // '-dPDFFitPage',

        // Output
        `-sOutputFile=${outputFile}`,
        inputFile,
      ];

      console.log('Spawning gs with compression args:', args.join(' '));

      const gsProcess = spawn('gs', args, {
        stdio: ['ignore', 'pipe', 'pipe'],
        shell: process.platform === 'win32',
      });

      let stdout = '';
      let stderr = '';

      gsProcess.stdout.on('data', (data) => {
        stdout += data.toString();
        if (stdout.length < 1000) {
          // Evita log troppo lunghi
          console.log('GS OUT:', data.toString().trim());
        }
      });

      gsProcess.stderr.on('data', (data) => {
        stderr += data.toString();
        if (stderr.length < 1000) {
          console.log('GS ERR:', data.toString().trim());
        }
      });

      gsProcess.on('close', async (code) => {
        console.log(`GS process closed with code: ${code}`);

        if (code === 0) {
          // Attendi e verifica
          setTimeout(async () => {
            try {
              const exists = await tools.isFileExistsAsync(outputFile);
              if (exists) {
                // Verifica dimensioni per confermare compressione
                try {
                  const originalStats = await tools.getFileStatsAsync(inputFile);
                  const newStats = await tools.getFileStatsAsync(outputFile);
                  const compressionRatio = (((originalStats.size - newStats.size) / originalStats.size) * 100).toFixed(
                    1
                  );

                  console.log(`📁 File originale: ${(originalStats.size / 1024 / 1024).toFixed(2)} MB`);
                  console.log(`📁 File compresso: ${(newStats.size / 1024 / 1024).toFixed(2)} MB`);
                  console.log(`🗜️ Compressione: ${compressionRatio}%`);
                  console.log('✅ SUCCESS: File generato e compresso');
                } catch (statsError) {
                  console.log('Warning: impossibile calcolare statistiche compressione');
                }

                resolve(outputFile);
              } else {
                console.log('❌ FAIL: File non generato nonostante exit code 0');
                reject(new Error('File non generato nonostante successo processo'));
              }
            } catch (error) {
              reject(error);
            }
          }, 1000);
        } else {
          reject(new Error(`Ghostscript failed with code ${code}: ${stderr}`));
        }
      });

      gsProcess.on('error', (error) => {
        console.log('GS process error:', error);
        reject(new Error(`Failed to start Ghostscript: ${error.message}`));
      });
    });
  }

  async generatePdfFromUrls(urls, outputFilename) {
    if (!this.browser) {
      throw new Error('Browser non avviato. Chiama launch() prima.');
    }
    const pdfBuffers = [];

    outputFilename = tools.getdirByIdApp(idapp) + '/';

    try {
      for (const url of urls) {
        // ............
      }

      const mergedPdf = await PDFDocument.create();

      for (const pdfBytes of pdfBuffers) {
        const pdf = await PDFDocument.load(pdfBytes);
        const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
        copiedPages.forEach((page) => mergedPdf.addPage(page));
      }

      const mergedPdfFile = await mergedPdf.save();

      const outputPath = path.resolve(process.cwd(), outputFilename);
      await fs.writeFile(outputPath, mergedPdfFile);

      console.log(`PDF unito generato: ${outputPath}`);
      return outputPath;
    } catch (error) {
      console.error('Errore durante la generazione PDF:', error);
      throw error;
    }
  }

  async getPathByPage(idpage) {
    const mypage = await MyPage.findById(idpage);

    if (mypage) {
      return mypage.path;
    }

    return '';
  }

  async generatePdfFromIdRaccolta(options) {
    try {
      if (!options) {
        console.error('Opzioni non passate !');
        return null;
      }
      const raccolta = await RaccoltaCataloghi.findById(options.idRaccolta);
      if (raccolta) {
        for (const catalogo of raccolta.lista_cataloghi) {
          await this.generatePdfFromIdCatalog({
            ...options,
            idCatalog: catalogo._id,
          });
        }
      }
    } catch (e) {
      console.error('err', e);
    }
  }
  async onlinePdfFromIdRaccolta(options) {
    try {
      if (!options) {
        console.error('Opzioni non passate !');
        return null;
      }
      const raccolta = await RaccoltaCataloghi.findById(options.idRaccolta);
      if (raccolta) {
        console.log('Pubblica ONLINE la Raccolta ' + raccolta.title + ' ...');
        for (const catalogo of raccolta.lista_cataloghi) {
          await this.onlinePdfFromIdCatalog({
            ...options,
            id_catalog: catalogo._id,
          });
        }

        console.log('FINE Pubblicazione ONLINE !');
      }
    } catch (e) {
      console.error('err', e);
    }
  }

  async onlinePdfFromIdCatalog(options) {
    const risout = await this.onlinePdf(options);
  }

  async generatePdfFromIdCatalog(options) {
    try {
      if (!options) {
        console.error('Opzioni non passate !');
        return null;
      }
      let filenamerelative = '';
      let fullnamepath = '';
      let fullnamepath_compr = '';
      let filenamerelative_compressed = '';
      let stampa = options.stampa;

      /*if (stampa) {
        options.width = '227.3mm';
        options.height = '313.5mm';
      } else {
        options.width = '210mm';
        options.height = '297mm';
      }*/

      const catalog = await Catalog.findById(options.idCatalog);
      if (catalog) {
        const myfilenameout = await this.getPathByPage(catalog.idPageAssigned);

        const url =
          tools.getHostByIdApp(this.idapp) +
          '/' +
          myfilenameout +
          '?' +
          'stampa=' +
          (stampa ? '1' : '0') +
          '&hideHeader=1';

        if (url) {
          let addstr = stampa ? '_stampabile' : '';

          filenamerelative = options.path + `${myfilenameout}${addstr}_generato.pdf`;

          fullnamepath = tools.getdirByIdApp(this.idapp) + '/' + filenamerelative;
        
          await tools.ensureDir(fullnamepath);
          
          await this.generatePdfFromUrl(url, fullnamepath, options);

          if (options.comprimi) {
            filenamerelative_compressed = options.path + `${myfilenameout}${addstr}_generato_compressed.pdf`;
            fullnamepath_compr = tools.getdirByIdApp(this.idapp) + '/' + filenamerelative_compressed;
            await this.compressPdf(fullnamepath, fullnamepath_compr, options.compressione);
          }
        }
      }

      const ris = {
        fileout: filenamerelative,
        fileout_compressed: filenamerelative_compressed,
        filesize: await tools.getSizeFile(fullnamepath),
        filesize_compressed: await tools.getSizeFile(fullnamepath_compr),
        error: '',
      };

      if (catalog) {
        if (stampa) {
          catalog.pdf_generato_stampa = ris.fileout;
          catalog.pdf_generato_stampa_compressed = ris.fileout_compressed;

          catalog.pdf_generato_stampa_size = ris.filesize;
          catalog.pdf_generato_stampa_compr_size = ris.filesize_compressed;

          catalog.data_generato_stampa = tools.getDateNow();
        } else {
          catalog.pdf_generato_compressed = ris.fileout_compressed;
          catalog.pdf_generato = ris.fileout;

          catalog.pdf_generato_size = ris.filesize;
          catalog.pdf_generato_compr_size = ris.filesize_compressed;

          catalog.data_generato = tools.getDateNow();
        }
        await catalog.save();
      }

      return ris;
    } catch (error) {
      console.error('Errore durante la generazione del PDF dal catalogo ID:', error);
      return {
        fileout: '',
        filesize: 0,
        error: 'Errore durante la generazione del PDF dal catalogo ID:' + error?.message,
      };
    }

    return { fileout: '', filesize: 0, error: '' };
  }

  async compressPdf(inputPath, outputPath, compressione = 'printer') {
    return new Promise((resolve, reject) => {
      // Risolvi i percorsi assoluti
      const inputFile = path.resolve(inputPath);
      const outputFile = path.resolve(outputPath);

      const validQualities = ['screen', 'ebook', 'printer', 'prepress', 'default'];
      if (!validQualities.includes(compressione)) compressione = 'screen';

      // Comando Ghostscript per compressione - impostazione per web (/screen)
      const gsCommand = `gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/${compressione} -dNOPAUSE -dBATCH -dQUIET -sOutputFile="${outputFile}" "${inputFile}"`;

      console.log('Eseguo comando:', gsCommand);

      exec(gsCommand, (error, stdout, stderr) => {
        if (error) {
          console.error(`Errore compressione PDF: ${error.message}`);
          return reject(error);
        }
        if (stderr) {
          console.error(`Ghostscript stderr: ${stderr}`);
        }
        console.log(`Compressione completata. File salvato in: ${outputFile}`);
        resolve(outputFile);
      });
    });
  }

  static async onlinePdf(options) {
    const idapp = options.idapp;
    const id_catalog = options.id_catalog;
    const id_raccolta = options.id_raccolta;
    const stampa = options.stampa;
    const compresso = options.compresso;
    let mydir = '';
    let risout = {};

    try {
      let myrec = null;

      // Aggiorna il PDF OnLine, copiando il file da Generato a OnLine
      if (id_catalog) {
        myrec = await Catalog.findOne({ _id: id_catalog });
      } else if (id_raccolta) {
        myrec = await RaccoltaCataloghi.findOne({ _id: id_raccolta });
      }

      if (myrec) {
        mydir = tools.getdirByIdApp(idapp) + '/';
        // const baseurl = this.getHostByIdApp(idapp);

        if (stampa) {
          myrec.pdf_online_stampa = myrec.pdf_generato_stampa?.replace('_generato', '');
        } else {
          myrec.pdf_online = myrec.pdf_generato?.replace('_generato', '');
        }

        const myfile = stampa ? myrec.pdf_generato_stampa : myrec.pdf_generato;

        if (myfile && (await tools.isFileExistsAsync(mydir + myfile))) {
          // Aggiorna il PDF OnLine, copiando il file da Generato a OnLine
          let fileOrigin = mydir + (stampa ? myrec.pdf_generato_stampa : myrec.pdf_generato);

          if (compresso) {
            let fileInCompressed = tools.removeFileExtension(fileOrigin) + `_compressed.pdf`;
            // Se esiste allora prende questo COmpresso !
            if (await tools.isFileExistsAsync(fileInCompressed)) {
              fileOrigin = fileInCompressed;
            } else {
            }
          }
          const fileDest = mydir + (stampa ? myrec.pdf_online_stampa : myrec.pdf_online);
          const fileDestNoDir = stampa ? myrec.pdf_online_stampa : myrec.pdf_online;

          console.log('   Metti online il file ' + fileOrigin);

          // copia il file
          await fs.copyFile(fileOrigin, fileDest);

          if (stampa) {
            myrec.pdf_online_stampa_size = await tools.getSizeFile(fileDest);
          } else {
            myrec.pdf_online_size = await tools.getSizeFile(fileDest);
          }

          if (stampa) {
            myrec.data_online_stampa = myrec.data_generato_stampa;
          } else {
            myrec.data_online = myrec.data_generato;
          }

          // Aggiorna il record
          await myrec.save();
        }

        risout = { record: myrec._doc };
      }

      return risout;
    } catch (e) {
      console.error('Err Online-pdf', e.message);
      return null;
    }
  }
}

module.exports = GenPdf;
