const axios = require('axios');
const cheerio = require('cheerio');

const Product = require('../models/product');

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

const fs = require('fs').promises; // 👈 Usa il modulo promises

const { MyScrapingBook } = require('../models/myscrapingbook');

class AmazonBookScraper {
  constructor() {
    this.baseUrl = 'https://www.amazon.it/dp/';
  }

  getUserAgentRandom() {
    // Lista di User-Agent comuni per i vari browser
    const userAgents = [
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.59 Safari/537.36',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
      'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0',
      'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; AS; rv:11.0) like Gecko',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36',
      'Mozilla/5.0 (Windows NT 6.1; rv:56.0) Gecko/20100101 Firefox/56.0',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
    ];

    // Seleziona un User-Agent casuale dalla lista
    const randomIndex = Math.floor(Math.random() * userAgents.length);
    return userAgents[randomIndex];
  }

  async fetchPageISBN10(isbn10, isbn) {
    if (!isbn10) return false;

    const url = `${this.baseUrl}${isbn10}`;
    const retryLimit = 2; // Numero massimo di tentativi
    const timeout = 7000; // Timeout di 5 secondi per la richiesta
    const delay = 10000; // Ritardo tra i tentativi (10 secondi)

    for (let attempt = 1; attempt <= retryLimit; attempt++) {
      try {
        const { data } = await axios.get(url, {
          headers: {
            'User-Agent': this.getUserAgentRandom(),
            // Aggiungi altri header se necessario
          },
          timeout, // Timeout di 5 secondi per la richiesta
        });
        return { html: data, url };
      } catch (err) {
        console.error(`Errore fetching ${url} per isbn10 ${isbn10} (ISBN:${isbn}) (tentativo ${attempt}):`, err.message);

        if (attempt < retryLimit) {
          console.log(`Riprovo tra ${delay / 1000} secondi...`);
          await new Promise((resolve) => setTimeout(resolve, delay)); // Ritardo prima di riprovare
        } else {
          console.error(
            `Impossibile recuperare la pagina per ISBN10 ${isbn10} (ISBN:${isbn}) dopo ${retryLimit} tentativi.`
          );
          await Product.findOneAndUpdate(
            { isbn: isbn },
            { $set: { scraped: true, scraped_error: true } },
            { upsert: true, new: true, includeResultMetadata: true }
          ).lean();

          return null;
        }
      }
    }
  }

  isbn13to10(isbn13) {
    try {
      if (!(isbn13.startsWith('978') || isbn13.startsWith('979')) || isbn13.length !== 13) return null;

      const core = isbn13.slice(3, 12); // i 9 numeri centrali

      let sum = 0;
      for (let i = 0; i < 9; i++) {
        sum += (10 - i) * parseInt(core[i], 10);
      }
      let check = 11 - (sum % 11);
      if (check === 10) check = 'X';
      else if (check === 11) check = '0';
      else check = check.toString();
      return core + check;
    } catch (err) {
      console.error('Errore calcolando ISBN-10 da ISBN-13:', err.message);
      return null;
    }
  }

  getTitleByProduct(product) {
    try {
      return product?.productInfo.name.trim();
    } catch (e) {
      return '';
    }
  }

  async extractData(myproduct, html, url, isbn10) {
    const $ = cheerio.load(html);

    const product = await Product.findOne({ _id: myproduct._id }).lean();

    const title_ondb = this.getTitleByProduct(product);

    // Titolo
    let title = $('#productTitle').text().trim() || null;
    // Sottotitolo (Amazon lo mette nel titolo)

    let dettagli = $('#productSubtitle').text().trim() || null;

    const ris = this.extractSubtitle(title, title_ondb);

    let sottotitolo = title_ondb ? ris?.subtitle : '';

    let titoloOriginale = '';

    if (ris?.titoloOriginale) {
      // E' presente il vero titolo del Libro !
      titoloOriginale = ris.titoloOriginale;
    }

    if (sottotitolo && title_ondb) {
      // significa che il titolo è dentro al sottotitolo, quindi prendi per buono quello nostro
      title = title_ondb;
    }

    let numpagine = null;
    let misure = null;
    let edizione = null;
    let publisher = null;
    let data_pubblicazione = null;
    let descrizione_lunga = null;
    let prezzo = null;
    let stelline = null;
    let autore = null;

    numpagine = this.extractNumeroDiPagine($);
    const dim = this.extractDimensions($);
    misure = this.convertDimensionsToMisureMacro(dim);
    const data_pubb = this.extractDataPubblicazione($);
    descrizione_lunga = this.extractDescrizioneLunga($);
    autore = this.extractAutore($);
    stelline = this.extractStelline($);
    prezzo = this.extractPrezzo($);
    data_pubblicazione = this.parseItalianDate(data_pubb);

    publisher = this.extractEditore($);

    if (data_pubb) {
      edizione = this.extractMonthYear(data_pubb);
    }

    const fonte = 'AMAZON';

    const data = {
      isbn: myproduct.isbn,
      isbn10,
      titolo: title,
      fonte,
      ...(titoloOriginale ? { titoloOriginale } : {}),
      ...(sottotitolo ? { sottotitolo } : { sottotitolo: '' }),
      ...(autore ? { autore } : { autore: '' }),
      ...(stelline ? { stelline } : {}),
      ...(numpagine ? { numpagine } : {}),
      ...(misure ? { misure } : {}),
      ...(edizione ? { edizione } : {}),
      ...(descrizione_lunga ? { descrizione_lunga } : {}),
      ...(publisher ? { editore: publisher } : {}),
      url: `<a href="${url}" target="_blank">VAI AL SITO ${fonte}</a>`,
      ...(data_pubblicazione ? { data_pubblicazione } : {}),
      ...(prezzo ? { prezzo } : {}),
    };

    // Aggiungilo al record
    await this.addOrUpdateMyScrapingBook(data);

    return data;
  }

  async findRecordMyScrapingBookByIsbn(isbn) {
    const record = await MyScrapingBook.findOne({ isbn }).lean();
    return record;
  }

  async addOrUpdateMyScrapingBook(data) {
    try {
      const {
        isbn,
        isbn10,
        titolo,
        titoloOriginale,
        fonte,
        url,
        sottotitolo,
        autore,
        numpagine,
        misure,
        edizione,
        editore,
        data_pubblicazione,
        stelline,
        prezzo,
        descrizione_lunga,
      } = data;

      // Cerca un record esistente per ISBN
      let record = await MyScrapingBook.findOne({ isbn }).lean();

      // Se il record esiste, aggiorna i campi
      await MyScrapingBook.updateOne(
        { isbn },
        {
          $set: {
            isbn10,
            titolo,
            fonte,
            titoloOriginale,
            sottotitolo,
            autore,
            pagine: numpagine,
            misure,
            edizione,
            editore,
            date_pub: data_pubblicazione,
            url,
            stelline,
            prezzo,
            descrizione_lunga,
            date_extraction: new Date(),
          },
        },
        { upsert: true }
      );
    } catch (err) {
      console.error('Errore gestendo MyScrapingBook:', err);
    }
  }

  async scrapeISBN(myproduct, isbn, options) {
    try {
      const product = await Product.findOne({ _id: myproduct._id }).lean();
      const datastored = await this.findRecordMyScrapingBookByIsbn(isbn);

      let data = null;

      let updated = null;
      let risupdate = null;

      // prima cerca sulla tabella che ho scaricato
      if (options.forzaricarica || !datastored) {
        const isbn10 = this.isbn13to10(isbn);
        const res = await this.fetchPageISBN10(isbn10, isbn);
        if (!res) {
          await Product.findOneAndUpdate(
            { _id: myproduct._id },
            { $set: { scraped: true, scraped_error: true } },
            { upsert: true, new: true, includeResultMetadata: true }
          ).lean();

          return null;
        }
        const html = res.html;
        if (!html) return null;

        data = await this.extractData(myproduct, html, res.url, isbn10);
      } else {
        data = { ...datastored };
      }

      if (!options?.update) return data;

      let recModificato = {};

      const arrvariazioni = myproduct.arrvariazioni || [];
      let index = -1;

      if (arrvariazioni.length === 1) {
        index = 0;
      } else {
        index = arrvariazioni.findIndex((v) => v.versione === shared_consts.PRODUCTTYPE.NUOVO);
        if (index < 0) index = 0;
      }

      let aggiornaDataPubb = false;

      let aggiornaPages = false;
      let aggiornaMisure = false;
      let aggiornaEdizione = false;

      let aggiornaProductInfo = false;
      let aggiornaSottotitolo = false;

      if (index !== -1) {
        const variante = arrvariazioni[index];

        // Determina se aggiornare pagine
        aggiornaPages = (!options.aggiornasoloSeVuoti || !variante.pagine || variante.pagine === 0) && data.numpagine;
        if (aggiornaPages) {
          variante.pagine = Number(data.numpagine);
          recModificato['pagine'] = variante.pagine;
        }

        // Determina se aggiornare misure
        aggiornaMisure = (!options.aggiornasoloSeVuoti || !variante.misure) && data.misure;
        if (aggiornaMisure) {
          variante.misure = data.misure;
          recModificato['misure'] = variante.misure;
        }

        // Determina se aggiornare edizione
        aggiornaEdizione = (!options.aggiornasoloSeVuoti || !variante.edizione) && data.edizione;
        if (aggiornaEdizione) {
          variante.edizione = data.edizione;
          recModificato['edizione'] = variante.edizione;
        }
      }

      // Determina se aggiornare data pubblicazione
      const currentDatePub = myproduct.productInfo.date_pub;
      aggiornaDataPubb =
        (!options.aggiornasoloSeVuoti || !tools.isDateValid(currentDatePub)) &&
        tools.isDateValid(data.data_pubblicazione);
      if (aggiornaDataPubb && data.data_pubblicazione) {
        product.productInfo.date_pub = new Date(data.data_pubblicazione);
      }

      aggiornaSottotitolo = (!options.aggiornasoloSeVuoti || !myproduct.productInfo.sottotitolo) && data.sottotitolo;
      if (aggiornaSottotitolo && data.sottotitolo) {
        product.productInfo.sottotitolo = data.sottotitolo;
      }

      aggiornaSottotitolo = false; // !! PER ORA LO DISATTIVO PERCHE' non esiste sempre il sottotitolo in un libro.

      // Aggiorna arrvariazioni se pagine o misure sono cambiati
      const aggiornadati = aggiornaPages || aggiornaMisure || aggiornaEdizione;

      aggiornaProductInfo = aggiornaDataPubb || aggiornaSottotitolo;

      if (aggiornadati) {
        updated = await Product.findOneAndUpdate(
          { _id: myproduct._id },
          { $set: { arrvariazioni, scraped: true, scraped_updated: true, scraped_date: new Date() } },
          { upsert: true, new: true, includeResultMetadata: true }
        );
      } else if (aggiornaProductInfo) {
        if (!tools.isObjectEmpty(productInfo)) {
          // Aggiorna il flag che ho modificato i dati
          updated = await Product.findOneAndUpdate(
            { _id: myproduct._id },
            { $set: { scraped: true, scraped_updated: true, scraped_date: new Date() } },
            { upsert: true, new: true, includeResultMetadata: true }
          );
        }
      }
      if (!updated) {
        const upd = await Product.findOneAndUpdate(
          { _id: myproduct._id },
          { $set: { scraped: true, scraped_date: new Date() } },
          { upsert: true, new: true, returnDocument: 'after' }
        );
        // console.log('upd', upd);
      }

      if (aggiornaProductInfo) {
        // Aggiorna productInfo se contiene dati
        if (!tools.isObjectEmpty(productInfo)) {
          risupdate = await Product.findOneAndUpdate(
            { _id: myproduct._id },
            { $set: { productInfo: product.productInfo } },
            { new: true, upsert: true, includeResultMetadata: true }
          ).lean();
        }
        // console.log('risupdate', risupdate);
      }

      const concatenatedProduct = {
        ...recModificato,
        ...product.productInfo,
      };

      if (updated) {
        console.log('   DATI AGGIORNATI:', JSON.stringify(concatenatedProduct));
      }

      return { data, updated: this.isRecordAggiornato(updated) || this.isRecordAggiornato(risupdate) };
    } catch (error) {
      console.error('Errore in scrapeISBN:', error?.message);
      return { data: null, updated: false, error: 'Errore in scrapeISBN:' + error?.message };
    }
  }

  isRecordAggiornato(updatedDoc) {
    try {
      if (updatedDoc) {
        const wasUpserted = updatedDoc.lastErrorObject.upserted !== undefined;
        return updatedDoc.lastErrorObject.n === 1 && !wasUpserted;
      } else {
        return false;
      }
    } catch (e) {
      console.log('error isRecordAggiornato', e);
      return false;
    }
  }

  numeroValido(num) {
    return !isNaN(num) && num !== null && num !== '' && num > 0;
  }

  datiMancanti(product) {
    let datimancanti = false;

    if (product.arrvariazioni?.length > 0) {
      const arrvar = product.arrvariazioni[0];
      if (!this.numeroValido(arrvar.pagine)) {
        datimancanti = true;
      }
      if (!arrvar.misure) {
        datimancanti = true;
      }
      /*if (!arrvar.edizione) {
        datimancanti = true;
      }*/
    }

    if (product.productInfo) {
      if (!tools.isDateValid(product.productInfo.date_pub)) {
        datimancanti = true;
      }
    }

    return datimancanti;
  }

  getRemainingTimeToTheEnd(dataorainizio, index, numrecord) {
    // calcola il tempo stimato rimanente (ore e minuti), tenendo conto che sono arrivato a index, e devo raggiongere "numrecord", e sono partito alla data "dataorainizio"
    const differenza = ((new Date().getTime() - dataorainizio.getTime()) / (index + 1)) * (numrecord - index);

    // Se la differenza è negativa, restituisce 0
    if (differenza <= 0) {
      return 'COMPLETATO !';
    }

    // Calcola ore, minuti, secondi rimanenti
    const ore = Math.floor(differenza / (1000 * 60 * 60));
    const minuti = Math.floor((differenza % (1000 * 60 * 60)) / (1000 * 60));

    // Restituisci il tempo rimanente in formato ore:minuti:secondi
    return `Stimato: ${ore} ore e ${minuti} min`;
  }

  includiNelControlloIlRecProduct(product) {
    return product.productInfo && [1, 4, 34, 45, 46].includes(product.productInfo.idStatoProdotto);
  }

  async scrapeMultiple(products, options) {
    const results = [];
    let quanti = 0;
    let mylog = '';
    console.log(`scrapeMultiple INIZIATO...`);
    let dataorainizio = new Date();

    for (let i = 0; i < products.length; i++) {
      const product = products[i];
      let isbn = product.isbn;

      if (this.includiNelControlloIlRecProduct(product)) {
        if (this.datiMancanti(product) || options.caricatutti) {
          // console.log(`${quanti} / ${products.length} - Scraping: ${product.productInfo.name}`);
          const data = await this.scrapeISBN(product, isbn, options);

          if (data?.updated) {
            results.push({ isbn, ...data });
            quanti++;
          }

          if (i % 1 === 0) {
            const percentuale = ((i / products.length) * 100).toFixed(2);
            console.log(
              `Scraping: ${product.isbn} - ${product.productInfo.name} - ${quanti} su ${i + 1} / ${
                products.length
              } - [${percentuale}%] - ${this.getRemainingTimeToTheEnd(dataorainizio, i, products.length)}`
            );
          }

          // Per evitare blocchi, metti una pausa (es. 2 secondi)
          await new Promise((r) => setTimeout(r, 2000));
        }
      }
    }

    mylog += `RECORD AGGIORNATI: ${results.length - 1} su ${quanti}`;
    return results;
  }

  generateHtmlTableFromObject(obj) {
    if (!obj || typeof obj !== 'object') return '';

    let html = '<table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;">';
    html += '<thead><tr><th>Chiave</th><th>Valore</th></tr></thead><tbody>';

    for (const [key, value] of Object.entries(obj)) {
      // Se il valore è un oggetto o array, lo converto in JSON stringa
      const displayValue = value && typeof value === 'object' ? JSON.stringify(value) : String(value);

      html += `<tr><td>${key}</td><td>${displayValue}</td></tr>`;
    }

    html += '</tbody></table>';
    return html;
  }

  static async ScraperAzzeraFlagProducts(idapp, options) {
    // aggiorna tutti i record di Product (con idapp) scraped: false

    await Product.updateMany({ idapp, scraped: true }, { $set: { scraped: false } });
    await Product.updateMany({ idapp, scraped_updated: true }, { $set: { scraped_updated: false } });
  }

  static async ScraperAzzeraFlagErrori(idapp, options) {
    // aggiorna tutti i record di Product (con idapp) scraped: false

    await Product.updateMany({ idapp, scraped_error: true }, { $set: { scraped: false } });
  }

  static async removeDuplicateVariations(idapp, options) {
    let mylog = 'removeDuplicateVariations...\n';

    // Fase 1: Troviamo i documenti che hanno almeno due elementi in arrvariazioni,
    // uno con `versione` e uno senza.
    const result = await Product.aggregate([
      { $match: { idapp } }, // Seleziona il prodotto in base a idapp
      {
        $unwind: '$arrvariazioni', // Esplodi l'array `arrvariazioni` in documenti separati
      },
      {
        $group: {
          _id: '$_id', // Gruppo per _id del prodotto
          arrvariazioni: { $push: '$arrvariazioni' }, // Ricostruisci l'array arrvariazioni
          // Trova se c'è almeno un elemento con `versione` e uno senza
          hasVersione: {
            $sum: { $cond: [{ $ifNull: ['$arrvariazioni.versione', false] }, 1, 0] },
          },
        },
      },
      {
        $match: {
          hasVersione: { $gt: 0 }, // Se c'è almeno un record con `versione`
        },
      },
    ]);

    // Ora possiamo rimuovere i duplicati
    for (let doc of result) {
      // Filtra gli oggetti dentro `arrvariazioni` per mantenere quelli con versione
      const arrvariazioniWithVersione = doc.arrvariazioni.filter((item) => item.versione);

      // Rimuovi gli elementi che non hanno versione ma hanno gli stessi altri campi
      const cleanedArr = arrvariazioniWithVersione.filter(
        (item, index, self) =>
          index ===
          self.findIndex(
            (t) =>
              t.active === item.active &&
              t.status === item.status &&
              t.price === item.price &&
              t.sale_price === item.sale_price &&
              t.quantita === item.quantita &&
              t.pagine === item.pagine &&
              t.misure === item.misure &&
              t.edizione === item.edizione &&
              t.ristampa === item.ristampa &&
              t.formato === item.formato &&
              t.tipologia === item.tipologia &&
              t.idTipologia === item.idTipologia &&
              t.idTipoFormato === item.idTipoFormato &&
              t.preOrderDate === item.preOrderDate &&
              t.addtocart_link === item.addtocart_link &&
              t.eta === item.eta
          )
      );

      if (doc.arrvariazioni.length - cleanedArr.length > 0) {
        const logtemp = `Elaborato ${doc._id} con ${arrvariazioniWithVersione.length} elementi\n`;
        logtemp += `Rimossi ${doc.arrvariazioni.length - cleanedArr.length} duplicati\n`;
        console.log(logtemp);
        mylog += logtemp;

        // Aggiorna il documento eliminando i duplicati
        await Product.updateOne({ _id: doc._id }, { $set: { arrvariazioni: cleanedArr } });
      }
    }

    return { mylog };
  }

  static async queryArrVariazioni(idapp) {
    const result = await Product.aggregate([
      { $match: { idapp, 'arrvariazioni.0': { $exists: true }, 'arrvariazioni.1': { $exists: true } } },
      { $project: { arrvariazioni: 1, _id: 0 } },
    ]);
    console.log('result', result);
  }

  static async ScraperGeneraCSV(idapp, options, res) {
    // Dichiara le intestazioni del CSV
    const headers = ['isbn', 'titolo', 'pagine', 'misure', 'edizione', 'date_pub' /*'sottotitolo'*/];

    try {
      // Trova i prodotti
      const products = await Product.find({ idapp, scraped_updated: true })
        .lean();

      // Funzione per "appiattire" i dati
      const flattenData = (data) => {
        return data.map((item) => {
          const flattened = { ...item };

          // Se arrvariazioni esiste, prendi solo il primo elemento o elabora come richiesto
          if (item.arrvariazioni && item.arrvariazioni.length > 0) {
            const variation = item.arrvariazioni[0]; // Usa il primo elemento o modifica se necessario
            flattened.pagine = variation.pagine || ''; // Usa '' se pagine non esiste
            flattened.misure = variation.misure || '';
            flattened.edizione = variation.edizione || '';
            flattened.ristampa = variation.ristampa || '';
          }

          // Assicurati che 'productInfo' esista prima di usarlo
          flattened.date_pub = item.productInfo ? item.productInfo.date_pub : '';
          flattened.titolo = item.productInfo ? item.productInfo.name : '';
          // flattened.sottotitolo = item.productInfo ? item.productInfo.sottotitolo : '';

          return flattened;
        });
      };

      // Appiattisci i dati
      const records = flattenData(products);

      // Prepara le righe del CSV
      const rows = records.map((item) => {
        // Se 'date_pub' è valido, convertilo in formato data, altrimenti metti una stringa vuota
        const formattedDate = item.date_pub
          ? item.date_pub.toLocaleDateString('it-IT', { year: 'numeric', month: '2-digit', day: '2-digit' })
          : '';
        return [
          item.isbn || '', // Assicurati che ISBN sia sempre una stringa, anche vuota
          item.titolo || '',
          //item.sottotitolo || '',
          item.pagine || '', // Gestisci il caso in cui 'pagine' non esiste
          item.misure || '',
          item.edizione || '',
          formattedDate, // La data formattata
        ];
      });

      // Aggiungi l'intestazione al CSV
      rows.unshift(headers);

      // Unisci tutte le righe con il delimitatore "|"
      const csvData = rows.map((row) => row.join('|')).join('\n');

      // Scrivi il file CSV in modo asincrono
      // Ritorna la stringa CSV come oggetto per eventuali elaborazioni future
      return { data: csvData };
    } catch (e) {
      console.error('Error in ScraperGeneraCSV:', e);
      return { error: e.message };
    }
  }

  static async ScraperDataAmazon(idapp, options) {
    const scraper = new AmazonBookScraper();
    const isbn = options.isbn;

    try {
      const myproduct = await Product.getProductByIsbn(isbn);
      const ris = await scraper.scrapeISBN(myproduct, isbn, options);

      console.log(ris?.data);
      return res.status(200).send({ code: server_constants.RIS_CODE_OK, data: ris?.data, html });
    } catch (e) {
      console.error(e);
      return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: '' });
    }
  }

  static async ScraperMultipleDataAmazon(idapp, options) {
    const scraper = new AmazonBookScraper();

    try {
      // Prendi solo quelli che non sono ancora stati scraped !
      const query = [
        // Filtro di base sui campi idapp, isbn, scraped, e scraped_error
        {
          $match: {
            idapp,
            isbn: { $exists: true, $ne: '' },
            scraped: { $ne: true }, // Escludi direttamente i record con scraped = true
            $or: [{ scraped_error: { $exists: false } }, { scraped_error: { $exists: true, $eq: false } }],
            /*$or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
             */
          },
        },
        {
          $match: {
            'productInfo.idStatoProdotto': { $in: [1, 4, 34, 45, 46] }, // Condizione su idStatoProdotto
          },
        },
        // Proiettiamo solo i campi necessari
        {
          $project: {
            scraped: 1,
            scraped_updated: 1,
            isbn: 1,
            title: 1,
            sottotitolo: 1,
            arrvariazioni: 1,
            'productInfo.date_pub': 1,
            'productInfo.name': 1,
            'productInfo.sottotitolo': 1,
            'productInfo.idStatoProdotto': 1,
            'productInfo.link_macro': 1,
            'productInfo.imagefile': 1,
          },
        },
        // A questo punto, puoi aggiungere altre operazioni di aggregazione se necessario (e.g., ordinamento)
      ];

      const products = await Product.aggregate(query);

      // console.log(products);

      const books = await scraper.scrapeMultiple(products, options);
      console.log(books);
    } catch (e) {
      console.error(e);
      return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: '' });
    }
  }

  extractDataPubblicazione($) {
    try {
      // Seleziona il div con id specifico per la data di pubblicazione
      const publicationDate = $('#rpi-attribute-book_details-publication_date .rpi-attribute-value span').text().trim();

      // Se non trova la data, ritorna null
      return publicationDate || null;
    } catch (error) {
      console.error('Error extracting publication date:', error);
      return null;
    }
  }

  extractDescrizioneLunga($) {
    try {
      // Cerca l'elemento che contiene la descrizione lunga
      const descriptionElement = $('#bookDescription_feature_div .a-expander-content span');

      // Estrai il testo e rimuovi eventuali spazi extra o tag HTML
      const description = descriptionElement.text().trim();

      // Se la descrizione è vuota, restituisci null
      return description || null;
    } catch (error) {
      console.error('Error extracting long description:', error);
      return null;
    }
  }

  extractAutore($) {
    try {
      // Seleziona tutti gli elementi che contengono gli autori
      const authorsElements = $('#bylineInfo .author a');

      // Estrai il testo per ogni autore e rimuovi spazi extra
      const authors = authorsElements.map((i, el) => $(el).text().trim()).get();

      // Se ci sono autori, restituiscili come stringa separata da virgole
      return authors.length > 0 ? authors.join(', ') : null;
    } catch (error) {
      console.error('Error extracting authors:', error);
      return null;
    }
  }

  extractStelline($) {
    try {
      // Cerca l'elemento che contiene la descrizione lunga
      // Seleziona l'elemento che contiene il voto medio (5,0 su 5 stelle)
      const ratingText = $('#acrPopover').attr('title');

      if (ratingText) {
        // Estrai il numero di stelle dal testo "5,0 su 5 stelle"
        const stars = ratingText.split(' su ')[0].replace(',', '.'); // Cambia la virgola in punto
        return stars; // Restituisce il numero di stelle
      }
    } catch (error) {
      console.error('Error extracting stars:', error);
      return null;
    }
  }

  extractPrezzo($) {
    try {
      // Seleziona l'elemento che contiene il prezzo
      const priceElement = $(
        '.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-whole'
      );

      // Estrai il prezzo principale e la parte decimale
      const wholePrice = priceElement.text().trim();
      const decimalPrice = $(
        '.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-fraction'
      )
        .text()
        .trim();

      // Estrai il simbolo della valuta
      const currencySymbol = $(
        '.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-symbol'
      )
        .text()
        .trim();

      // Se il prezzo è stato trovato, formattalo come prezzo completo
      if (wholePrice && decimalPrice && currencySymbol) {
        const fullPrice = `${wholePrice}${decimalPrice} ${currencySymbol}`;
        return fullPrice;
      }

      // Se non trova il prezzo, restituisce null
      return null;
    } catch (error) {
      console.error('Error extracting price:', error);
      return null;
    }
  }

  extractNumeroDiPagine($) {
    try {
      // Seleziona il div con id specifico per pagine
      const pagesText = $('#rpi-attribute-book_details-fiona_pages .rpi-attribute-value span').text().trim();

      // pagesText dovrebbe essere tipo "184 pagine"
      if (!pagesText) return null;

      // Estrai solo il numero (facoltativo)
      const match = pagesText.match(/(\d+)/);
      return match ? match[1] : pagesText; // ritorna solo il numero o il testo intero
    } catch (error) {
      console.error('Error extracting number of pages:', error);
      return null;
    }
  }

  extractDimensions($) {
    try {
      // Seleziona il div con id specifico per dimensioni
      const dimText = $('#rpi-attribute-book_details-dimensions .rpi-attribute-value span').text().trim();

      // Se non trova niente ritorna null
      return dimText || null;
    } catch (error) {
      console.error('Error extracting dimensions:', error);
      return null;
    }
  }

  convertDimensionsToMisureMacro(dimString) {
    if (!dimString) return null;

    // Estrai tutti i numeri (compresi decimali)
    const numbers = dimString.match(/[\d.]+/g);
    if (!numbers || numbers.length < 2) return null;

    // Converti in numeri float e ordina decrescente
    const sortedNums = numbers
      .map((num) => parseFloat(num))
      .filter((n) => !isNaN(n))
      .sort((a, b) => b - a);

    if (sortedNums.length < 2) return null;

    // Prendi i due più grandi
    const [first, second] = sortedNums;

    return `cm ${first}x${second}`;
  }

  parseItalianDate(dateStr) {
    if (!dateStr) return null;

    // Mappa mesi in italiano a numeri (0-based per Date)
    const months = {
      gennaio: 0,
      febbraio: 1,
      marzo: 2,
      aprile: 3,
      maggio: 4,
      giugno: 5,
      luglio: 6,
      agosto: 7,
      settembre: 8,
      ottobre: 9,
      novembre: 10,
      dicembre: 11,
    };

    // Divido la stringa (es. "14 maggio 2025")
    const parts = dateStr.toLowerCase().split(' ');
    if (parts.length !== 3) return null;

    const day = parseInt(parts[0], 10);
    const month = months[parts[1]];
    const year = parseInt(parts[2], 10);

    if (isNaN(day) || month === undefined || isNaN(year)) return null;

    return new Date(year, month, day);
  }
  extractSubtitle(fullTitle, baseTitle) {
    if (!fullTitle || !baseTitle) return null;

    let mybaseTitle = '';
    let numCharRemoved = 0;
    let titoloOriginale = '';

    // Se il fullTitle non contiene il baseTitle, ritorna null
    const coniniziali = fullTitle.trim().toLowerCase().startsWith(baseTitle.trim().toLowerCase());
    if (coniniziali) {
      mybaseTitle = baseTitle;
    }

    let senzainiziali = false;

    if (!coniniziali) {
      // torna la posizione in cui l'ho trovato
      const posizione = fullTitle.toLowerCase().indexOf(baseTitle.trim().toLowerCase());
      if (posizione < 0 || posizione > 3) {
        return null;
      }
      // torna il nome del titolo, compreso degli articoli, partendo da zero, fino a posizione + baseTitle
      titoloOriginale = fullTitle.substring(0, posizione + baseTitle.length);

      numCharRemoved = posizione;
      senzainiziali = true;
    }

    if (!coniniziali && !senzainiziali) {
      return null;
    }

    // Rimuovi il baseTitle dall'inizio
    let remainder = fullTitle.slice(baseTitle.length + numCharRemoved).trim();

    // Se la rimanenza inizia con ":" o "-" o ".", rimuovila
    if (remainder.startsWith(':') || remainder.startsWith('-') || remainder.startsWith('.')) {
      remainder = remainder.slice(1).trim();
    }

    // Se resta una stringa non vuota, è il sottotitolo
    return { subtitle: remainder.length > 0 ? remainder : null, titoloOriginale };
  }

  extractMonthYear(dateStr) {
    if (!dateStr) return null;

    // Divide la stringa in parole
    const parts = dateStr.trim().split(' ');

    // Se ha almeno 3 parti (giorno, mese, anno)
    if (parts.length >= 3) {
      // Restituisce mese + anno
      return parts
        .slice(1)
        .map((part, idx) => (idx === 0 ? part[0].toUpperCase() + part.slice(1) : part))
        .join(' ');
    }

    return null;
  }

  extractEditore($) {
    // Seleziona il testo dentro il div id rpi-attribute-book_details-publisher
    const publisher = $('#rpi-attribute-book_details-publisher .rpi-attribute-value span').text().trim();

    return publisher || null;
  }
}

module.exports = AmazonBookScraper;
