const os = require('os');
const fs = require('fs-extra'); // 👈 Usa il modulo promises
const xml2js = require('xml2js');
const path = require('path');
const WebSocket = require('ws');

require('../config/config');
require('../models/subscribers');

const ImageDownloader = require('../modules/ImageDownloader');

const printf = require('util').format;

const { JSDOM } = require('jsdom');

const axios = require('axios');

const CryptoJS = require('crypto-js');
const Url = require('url-parse');
const crypto = require('crypto');

const { ObjectId } = require('mongodb');

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

const mongoose = require('mongoose').set('debug', false);

const server_constants = require('./server_constants');

// SETTINGS WebPush Configuration
const webpush = require('web-push');

const i18n = require('i18n');

const FILELOG = 'filelog.txt';
const FILEEVENTS = 'logevents.txt';
const FILEMANAGERS = 'logmanagers.txt';
const FILETRANSACTION = 'logtrans.txt';
const FILEERRLOGIN = 'errlogin.txt';
const FILESOSTITUZIONI = 'log_sostituzioni.txt';
const FILEIP_TO_BAN = 'log_iptoban.txt';
const FILENAVE = 'logNave.txt';
const subject = process.env.VAPI_KEY_SUBJECT;
const publicVapidKey = process.env.PUBLIC_VAPI_KEY;
const privateVapidKey = process.env.PRIVATE_VAPI_KEY;

const FIELDS_REGEX = ['username', 'name', 'surname'];

const sanitizeHtml = require('sanitize-html');

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

const util = require('util');
const execPromise = util.promisify(exec);

const { spawn } = require('child_process');
const readline = require('readline');

// Code goes here
const keySize = 256;
// const ivSize = 128;
const iterations = 1000;

if (!!process.env.GCM_API_KEY && process.env.GCM_API_KEY !== '') {
  webpush.setGCMAPIKey(process.env.GCM_API_KEY);

  webpush.setVapidDetails(subject, publicVapidKey, privateVapidKey);
}
// console.log('setVapidDetails... config...');

// To Translate!
const textlang = {
  it: {
    "L'Email è già stata Verificata": "L'Email è già stata Verificata",
    'Nuova Registrazione': 'Nuova Registrazione',
    'Effettuata una Nuova Registrazione': 'Effettuata una Nuova Registrazione',
    partecipanti: "partecipanti all'Evento",
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'partecipanti a Cena Condivisa': 'partecipanti a Cena Condivisa',
    TESTO_ASSISTENZA:
      '<strong><a href="%s">👉 Per entrare nel Sito</a></strong>\n\n' +
      '👉 <strong><a href="https://freeplanet.app/requestresetpwd">Hai dimenticato la password?</a></strong>\n\n',
    BENVENUTO: 'Benvenut@',
    TUE_NAVI: 'Ecco le tue Navi programmate',
    HAI_I_7_REQUISITI: 'PRIMI PASSI OK!\nHai i Primi Requisiti per Entrare nella Lista !',
    NON_HAI_I_7_REQUISITI: 'Attenzione!\nAncora non hai i 7 Requisiti per Entrare nella Lista !',
    INFO_LA_MIA_LAVAGNA: '✨ Lista dei Passi: ✨ \n',
    INFO_LAVAGNA_SITO_COMPLETARE:
      'Per completare tutti i requisiti vai sul sito:\n%s\n👉🏻 <a href="%s">Entra nella tua Lavagna</a>.\n',
    INFO_LAVAGNA_SITO:
      'Per vedere in dettaglio lo <strong>STATO</strong>, sul sito \n 👉🏻 <a href="%s">Entra nella tua Lavagna</a>.\n',
    INFO_LINK_DA_CONDIVIDERE: 'Link da condividere ai tuoi invitati per farli registrare al sito:\n\n%s',
    INFO_LINK_ZOOM: 'Link da condividere per partecipare allo Zoom (Conferenza OnLine):\n%s',
    ZOOM_CONFERENCE: 'Ecco il programma delle Conferenze (Zoom) aperti a TUTTI:',
    NON_VERIF: 'Non Verificata',
    VERIF: 'Verificata',
    EMAIL: 'Email',
    MEMBRO_AMMESSO: 'Ottimo! Hai ammesso %s!',
    MEMBRO_GIA_AMMESSO: 'Il membro %s è stato già Ammesso!',
    BOT: 'BOT',
    EMAIL_NON_VERIF:
      'Email Non Verificata\nleggi la tua casella email e trova **"Confermare la Registrazione"**\ne clicca sul bottone **"Verifica Registrazione"**',
    TELEGRAM_NOT_CONNECTED: 'Telegram non associato al tuo account!',
    NESSUN_INVITATO: 'Non hai invitato nessuno',
    ZOOM_PARTECIPATO: 'Partecipazione ad almeno 1 Conferenza (Zoom)',
    LINEE_GUIDA: 'Accettato le Linee Guida',
    VIDEO_INTRO: 'Visto il Video',
    SCRITTO_SOGNO: 'Hai scritto il tuo Sogno',
    PAYMENTS: 'Modalità di Pagamento',
    INVITATI: 'persone registrate che hai invitato',
    INVITATI_ATTIVI: 'Invitati con i 7 Requisiti',
    NONREG: 'Invitati non Registrati',
    CLICCA_PER_ZOOM: 'AL GIORNO E ORA INDICATA, PER ENTRARE NELLA VIDEO-CONFERENZA, CLICCA SUL LINK INDICATO',
    CLICCA_ENTRA: 'CLICCA QUI PER ENTRARE !',
    ZOOM_INIZIATO: "QUESTA CONFERENZA E' INIZIATA!",
    SCEGLI_VOCE: 'scegli una voce:',
    INVITATI_LISTA: 'I Tuoi Invitati (in verde con almeno i primi 7 Requisiti)',
    CIAO: 'Ciao',
    ADDED_TOLISTAINGRESSO:
      "<strong>Complimenti! 🎊</strong>\nSei stato aggiunto alla <strong>Lista D'Imbarco</strong> !\nNei prossimi giorni riceverai un messaggio qui quando la tua Nave sarà pronta a partire.\nRicorda che per salire di graduatoria, puoi condividere il Movimento con i tuoi amici ❤️.",
    REGALATO_INVITANTE: 'da fare.... 😍🎊 Hai ricevuto in Regalo un Invitato {invitato} da parte di {mittente} !',
    NO_PROG: "Attualmente non sei ancora dentro alla Lista d'Imbarco!",
    SEND_LINK_CHAT_DONATORI:
      'Ciao %s!\nLa tua NAVE sta finalmente Salpando!\nEntra nella Gift Chat della nave {%s} cliccando qui: %s',
    SEND_LINK_CHAT_SOGNATORE:
      'Ciao %s!\nOggi Diventi Sognatore !!! 🎊🎊🎊 Entra nella Gift Chat della nave {%s} cliccando qui: %s',
    ENTRA_GIFT_CHAT: 'Entra nella Gift Chat',
    DATA_PART_NAVE: 'data di Partenza della Nave:',
    SOGNATORE: 'SOGNATORE',
    INTERMEDIO: 'Intermedio',
    MEDIATORE: 'MEDIATORE',
    DONATORI: 'DONATORI',
    RITESSITURA: 'RITESSITURA',
    'NAVE COMPLETATA': 'NAVE COMPLETATA',
    'Doni Effettuati': 'Doni Effettuati',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore che affianchèra il Mediatore',
    APERTURA_CHIUSURA_GIFT_CHAT:
      "%s: Apertura GIFT CHAT\n%s Chiusura GIFT CHAT\nNota Bene: Hai tempo 3 giorni dall'apertura per entrare in Chat ed effettuare il tuo Dono, dopodichè effettueremo la sostituzione.",
    'Giorno di Apertura GIFT CHAT': 'Giorno di Apertura GIFT CHAT',
    'Giorno in cui Inviare il DONO': 'Hai 3 giorni di tempo per inviare il tuo dono.\nGiorno di Chiusura',
    SPOSTATO: 'Sei stato Spostato in una Nuova Nave !',
    Note: 'Note',
    TEMPORANEA: 'TEMPORANEA',
    NAVE: 'NAVE',
    MSG_SEND_FROM: 'Msg Inviato da',
    ZOOM_CONFERMATO: 'Sei stato confermato ad aver visto la Video Conferenza di Benvenuto!',
    RICHIESTA_AMICIZIA: "🌈 Richiesta d'Amicizia da parte di %s",
    RICHIESTA_HANDSHAKE: '🌈 %s ha comunicato che ti conosce personalmente e ha fiducia in te.',
    ACCETTATO_SUL_GRUPPO: '✅ Sei stato accettato da %s a far parte del Gruppo %s',
    CREATO_NUOVO_GRUPPO: '✅ Hai appena creato un nuovo Gruppo chiamato %s',
    ACCETTATO_NOTIFICA_ADMINS: "✅ l'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)",
    GROUP_REQUEST: 'Richiesta di entrare nel Gruppo %s da parte di %s',
    CLICCA_QUI: 'CLICCA QUI',
    TELEFONO: 'Tel',
  },
  si: {},
  es: {
    "L'Email è già stata Verificata": 'El correo electrónico ya ha sido verificado',
    'Nuova Registrazione': 'Nuevo Registro',
    'Effettuata una Nuova Registrazione': 'Se ha realizado un nuevo registro',
    partecipanti: 'participantes',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    TESTO_ASSISTENZA:
      'Para entrar en el sitio:\n%s\n\n' +
      '¿Olvidó su contraseña para acceder al sitio?\nhttps://freeplanet.app/requestresetpwd\n\n',
    BENVENUTO: 'Bienvenido',
    TUE_NAVI: 'Aquí están sus naves programadas',
    HAI_I_7_REQUISITI:
      '¡LOS PRIMEROS PASOS ESTÁN BIEN!\nTiene los primeros 7 requisitos para entrar en la lista de embarque!',
    NON_HAI_I_7_REQUISITI: '¡Atención!\nTodavía no tienes los 7 requisitos para entrar en la lista de embarque!',
    HAI_I_9_REQUISITI: '¡FELICITACIONES!\n¡Has completado los 9 pasos de la Guía! Gracias por ayudar a expandirse!',
    NON_HAI_I_9_REQUISITI:
      'Recuerda que puedes ayudar a que el Movimiento crezca y se expanda compartiendo nuestro viaje con todos.!',
    INFO_LA_MIA_LAVAGNA: '✨ Lista de pasos: ✨ \n',
    INFO_LAVAGNA_SITO_COMPLETARE:
      'Para completar todos los requisitos vaya al sitio:\n%s\nPara ver el estado de su nave y sus invitados,👉🏻 <a href="%s">Pizarra</a>.\n',
    INFO_LAVAGNA_SITO:
      'Para ver en detalle el estado de su nave, en el sitio web, haga clic en las 3 líneas de la parte superior izquierda y vaya a "Pizarra"..\n',
    INFO_LINK_DA_CONDIVIDERE: 'Enlaces para compartir con sus invitados para que se registren en el sitio web:\n\n%s',
    INFO_LINK_ZOOM: 'Enlaces para compartir para participar en el Zoom (Conferencia en línea):\n%s',
    ZOOM_CONFERENCE: 'Aquí puedes encontrar las fechas de programación en el Zoom:',
    NON_VERIF: 'No verificado',
    VERIF: 'Verificado',
    EMAIL: 'Email',
    BOT: 'BOT',
    EMAIL_NON_VERIF:
      'Correo electrónico no verificado\nlea su buzón y encuentre **"Confirmar Registro "** <marca>y haga clic en el botón **"Verificar Registro "**.',
    TELEGRAM_NOT_CONNECTED: 'Telegram no asociado a su cuenta!',
    NESSUN_INVITATO: 'No invitaste a nadie',
    ZOOM_PARTECIPATO: 'Participación en al menos 1 Conferencia (Zoom)',
    LINEE_GUIDA: 'Directrices aceptadas',
    VIDEO_INTRO: 'Ver el video',
    SCRITTO_SOGNO: 'Escribiste tu sueño',
    PAYMENTS: 'Métodos de pago',
    INVITATI: 'las personas registradas que usted invitó',
    INVITATI_ATTIVI: 'Invitado con los 7 requisitos',
    NONREG: 'Invitados no registrados',
    CLICCA_PER_ZOOM: 'AL DÍA Y HORA INDICADOS, PARA ENTRAR EN LA VIDEOCONFERENCIA, PULSE AQUÍ',
    CLICCA_ENTRA: 'HAGA CLIC AQUÍ PARA ENTRAR !',
    ZOOM_INIZIATO: 'ESTA CONFERENCIA HA COMENZADO!',
    SCEGLI_VOCE: 'elegir una voz:',
    INVITATI_LISTA: 'Sus invitados (en verde con al menos los 7 primeros requisitos)',
    CIAO: 'Hola',
    ADDED_TOLISTAINGRESSO: 'Has sido añadido a la lista de personas que entrarán en Lista de embarque!',
    NO_PROG: 'Actualmente no está en la lista de embarque.!',
    SEND_LINK_CHAT_DONATORI:
      'Hola %s!\n¡Tu barco por fin está navegando!\nEntra en el Gift Chat haciendo clic aquí: %s',
    SEND_LINK_CHAT_SOGNATORE:
      '¡Hola!\n¡Hoy te conviertes en un soñador! 🎊🎊🎊 Entre en el Chat de Regalos de la nave {%s} haciendo clic aquí: %s',
    ENTRA_GIFT_CHAT: 'Entre en el Chat de Regalos',
    DATA_PART_NAVE: 'fecha de salida de la nave:',
    SOGNATORE: 'SOÑADOR',
    INTERMEDIO: 'Intermedio',
    MEDIATORE: 'MEDIATOR',
    DONATORI: 'DONANTES',
    RITESSITURA: 'RETEJIDO',
    'NAVE COMPLETATA': 'NAVE COMPLETADA',
    'Doni Effettuati': 'Regalos Realizados',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore de apoyo al Donante',
    APERTURA_CHIUSURA_GIFT_CHAT:
      '%s: Apertura del CHAT DE REGALOS\n%s Cierre del CHAT DE REGALOS\nNota: Tienes 3 días desde la apertura para entrar en el Chat y hacer tu Regalo, después de lo cual haremos el reemplazo',
    SPOSTATO: 'Has sido trasladado a una nueva nave !',
    Note: 'Notas',
    TEMPORANEA: 'TEMPORAL',
    NAVE: 'NAVE',
    MSG_SEND_FROM: 'Mensaje enviado por',
    ZOOM_CONFERMATO: '¡Se ha confirmado que ha visto la Video Conferencia de Bienvenida!',
    RICHIESTA_AMICIZIA: "Richiesta d'Amicizia da parte di %s",
    ACCETTATO_SUL_GRUPPO: 'Sei stato accettato da %s a far parte del Gruppo %s',
    ACCETTATO_NOTIFICA_ADMINS: "l'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)",
    RICHIESTA_BLOCCO_GRUPPO: 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  enUs: {
    partecipanti: 'participants',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    TESTO_ASSISTENZA:
      'To enter the Site:\n%s\n\nForgot your password to access the site?\n' +
      'https://freeplanet.app/requestresetpwd\nChat AYNI BOT (this one):\nhttps://t.me/notevoleaynibot\n\n',
    BENVENUTO: 'Welcome',
    TUE_NAVI: 'Here are your programmed ships',
    HAI_I_7_REQUISITI: 'FIRST STEPS OK!\nYou have the First 7 Requirements to Enter the Boarding List!',
    NON_HAI_I_7_REQUISITI: 'Attention!\nYou still do not have the 7 requirements to enter the boarding list!',
    INFO_LA_MIA_LAVAGNA: '✨ Step List: ✨ \n',
    INFO_LAVAGNA_SITO_COMPLETARE:
      'To complete all the requirements go to the site:%s\nTo see the status of your Ship and your guests\n👉🏻 <a href="%s">Dashboard</a>\n',
    INFO_LAVAGNA_SITO:
      'To see in detail the status of your ship, on the website, click on the 3 lines at the top left and go to "Blackboard".\n',
    INFO_LINK_DA_CONDIVIDERE: 'Links to share with your guests to have them register on website:\n\n%s',
    INFO_LINK_ZOOM: 'Links to share to participate in Zoom (Online Conference):\n%s',
    ZOOM_CONFERENCE: 'Here you can find the Zoom Conference Planning:',
    NON_VERIF: 'Not Verified',
    VERIF: 'Verified',
    EMAIL: 'Email',
    BOT: 'BOT',
    EMAIL_NON_VERIF:
      'Email Not Verified\nread your mailbox and find **"Confirm Registration "**<br>and click on the button **"Verify Registration "**.',
    TELEGRAM_NOT_CONNECTED: 'Telegram not associated with your account!',
    NESSUN_INVITATO: "You didn't invite anyone",
    ZOOM_PARTECIPATO: 'Participation in at least 1 Conference (Zoom)',
    LINEE_GUIDA: 'Guidelines Accepted',
    VIDEO_INTRO: 'Seen the Video',
    SCRITTO_SOGNO: 'You wrote your Dream',
    PAYMENTS: 'Methods of Payment',
    INVITATI: 'registered people you invited',
    INVITATI_ATTIVI: 'Guests with the 7 Requirements',
    NONREG: 'Non-registered Guests',
    CLICCA_PER_ZOOM: 'TO THE DAY AND TIME INDICATED, TO ENTER THE VIDEO-CONFERENCE, CLICK HERE',
    CLICCA_ENTRA: 'CLICK HERE TO ENTER !',
    ZOOM_INIZIATO: 'THIS CONFERENCE HAS BEGUN!',
    SCEGLI_VOCE: 'choose a menu item:',
    INVITATI_LISTA: 'Your Guests (in green with at least the first 7 requirements)',
    CIAO: 'Hi',
    ADDED_TOLISTAINGRESSO: 'You have been added to the List of people who will enter the Boarding List!',
    NO_PROG: 'You are not currently on the boarding list.!',
    SEND_LINK_CHAT_DONATORI: 'Hi %s!\nYour ship is finally sailing!\nEnter the Gift Chat by clicking here: %s',
    SEND_LINK_CHAT_SOGNATORE:
      "Hello %s!\nToday you become a Dreamer! 🎊🎊🎊 Enter the ship's Gift Chat {%s} by clicking here: %s",
    ENTRA_GIFT_CHAT: "Enter the ship's Gift Chat",
    DATA_PART_NAVE: 'date of departure of the ship:',
    SOGNATORE: 'DREAMER',
    INTERMEDIO: 'Intermediate',
    MEDIATORE: 'MEDIATOR',
    DONATORI: 'DONOR',
    RITESSITURA: 'RE-ENTER',
    'NAVE COMPLETATA': 'SHIP COMPLETED',
    'Doni Effettuati': 'Gifts Made',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore supporting the Mediator',
    APERTURA_CHIUSURA_GIFT_CHAT:
      '%s: Opening GIFT CHAT\n%s Closing GIFT CHAT\nNote: You have 3 days from the opening to enter the Chat and make your Gift, after which we will make the replacement.',
    SPOSTATO: "You've been moved to a New Ship !",
    Note: 'Note',
    TEMPORANEA: 'TEMPORARY',
    NAVE: 'SHIP',
    MSG_SEND_FROM: 'Msg sent by',
    ZOOM_CONFERMATO: 'You have been confirmed to have seen the Welcome Video Conference!',
    RICHIESTA_AMICIZIA: "Richiesta d'Amicizia da parte di %s",
    ACCETTATO_SUL_GRUPPO: 'Sei stato accettato da %s a far parte del Gruppo %s',
    ACCETTATO_NOTIFICA_ADMINS: "l'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)",
    RICHIESTA_BLOCCO_GRUPPO: 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  fr: {
    "L'Email è già stata Verificata": 'Le courrier électronique a déjà été vérifié',
    'Nuova Registrazione': 'Nouvelle inscription',
    'Effettuata una Nuova Registrazione': 'Un nouvel enregistrement a été effectué',
    partecipanti: 'participants',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    TESTO_ASSISTENZA:
      'Pour entrer sur le site:\n%s\n\n' +
      'Vous avez oublié votre mot de passe pour accéder au site ?\nhttps://ayni.gifteconomy.app/requestresetpwd\n\n',
    BENVENUTO: 'Bienvenue',
    TUE_NAVI: 'Voici vos navires programmés',
    HAI_I_7_REQUISITI:
      "PREMIÈRES ÉTAPES OK!\nvous avez les 7 premiers Requis pour Entrer dans la liste d'embarquement!",
    NON_HAI_I_7_REQUISITI:
      "'Attention!\nVous ne remplissez pas encore les 7 conditions pour figurer sur la liste d'embarquement!",
    HAI_I_9_REQUISITI:
      "FÉLICITATIONS!\n!Vous avez franchi les 9 étapes de la conduite ! Merci d'avoir aidé à se développer!",
    NON_HAI_I_9_REQUISITI:
      "N'oubliez pas que vous pouvez aider le Mouvement à grandir et à se développer en partageant notre voyage avec tout le monde.!",
    INFO_LA_MIA_LAVAGNA: '✨ Liste des étapes: ✨ \n',
    INFO_LAVAGNA_SITO_COMPLETARE:
      'Pour remplir toutes les conditions, rendez-vous sur le site:\n%s\nPour voir le statut de votre navire et de vos invités\n 👉🏻 <a href="%s">Tableau</a>.\n',
    INFO_LAVAGNA_SITO:
      'Pour voir en détail le statut de votre navire, sur le site, cliquez sur les 3 lignes en haut à gauche et allez sur "Tableau".\n',
    INFO_LINK_DA_CONDIVIDERE: "Liens à partager avec vos invités pour qu'ils s'inscrivent sur le site web:\n\n%s",
    INFO_LINK_ZOOM: 'Liens à partager pour participer à Zoom (Conférence en ligne):\n%s',
    ZOOM_CONFERENCE: 'Vous trouverez ici les dates de programmation sur Zoom:',
    NON_VERIF: 'Non vérifié',
    VERIF: 'Vérifié',
    EMAIL: 'Courriel',
    BOT: 'BOT',
    EMAIL_NON_VERIF:
      'Courriel non vérifié\nlisez votre boîte aux lettres et trouvez **"Confirmer l\'inscription "**<br>et cliquez sur le bouton **"Vérifier l\'inscription "**.',
    TELEGRAM_NOT_CONNECTED: 'Télégramme non associé à votre compte!',
    NESSUN_INVITATO: "Vous n'avez invité personne",
    ZOOM_PARTECIPATO: 'Participation à au moins 1 conférence (Zoom)',
    LINEE_GUIDA: 'Lignes directrices acceptées',
    VIDEO_INTRO: 'Voir la vidéo',
    SCRITTO_SOGNO: 'Vous avez écrit votre rêve',
    PAYMENTS: 'Modes de paiement',
    INVITATI: 'personnes inscrites que vous avez invitées',
    INVITATI_ATTIVI: 'Invité avec les 7 exigences',
    NONREG: 'Invités non enregistrés',
    CLICCA_PER_ZOOM: "AU JOUR ET À L'HEURE INDIQUÉS, POUR ENTRER DANS LA VIDÉOCONFÉRENCE, CLIQUEZ ICI",
    CLICCA_ENTRA: 'CLIQUEZ ICI POUR ENTRER !',
    ZOOM_INIZIATO: 'CETTE CONFÉRENCE A COMMENCÉ!',
    SCEGLI_VOCE: 'choisir une voix:',
    INVITATI_LISTA: 'Vos invités (en vert avec au moins les 7 premières exigences)',
    CIAO: 'Salut',
    ADDED_TOLISTAINGRESSO: "Vous avez été ajouté à la liste d'embarquement !",
    NO_PROG: "Vous n'êtes pas encore sur la liste d'embarquement.!",
    SEND_LINK_CHAT_DONATORI:
      'Salut %s!\nVotre SHIP prend enfin la mer.!\nEntrez dans le Chat Cadeau en cliquant ici: %s',
    SEND_LINK_CHAT_SOGNATORE:
      "Bonjour %s !\nAujourd'hui, vous devenez un Rêveur ! 🎊🎊🎊 Entrez dans le chat des cadeaux du navire {%s} en cliquant ici : %s",
    ENTRA_GIFT_CHAT: 'Entrez dans le chat des cadeaux',
    DATA_PART_NAVE: 'la date de départ du navire:',
    SOGNATORE: 'Rêveur',
    INTERMEDIO: 'Intermediaire',
    MEDIATORE: 'Médiateur',
    DONATORI: 'DONATEUR',
    RITESSITURA: 'RETESSITURA',
    'NAVE COMPLETATA': 'NAVIRE COMPLÉTÉ',
    'Doni Effettuati': 'Don effectués',
    'Facilitatore che affianchèra il Mediatore': 'Le tuteur qui soutient le Mediateur',
    APERTURA_CHIUSURA_GIFT_CHAT:
      "%s : Chat d'ouverture\n%s Clôture du GIFT CHAT\nNote: Vous avez 3 jours à partir de l'ouverture pour entrer dans le Chat et faire votre Cadeau, après quoi nous ferons le remplacement",
    SPOSTATO: 'Vous avez été transféré sur un nouveau navire !',
    Note: 'Notes',
    TEMPORANEA: 'TEMPORAIRE',
    NAVE: 'NAVIRE',
    MSG_SEND_FROM: 'Message envoyé par',
    ZOOM_CONFERMATO: 'Vous avez été confirmé comme ayant vu la vidéoconférence de bienvenue !',
    RICHIESTA_AMICIZIA: "Richiesta d'Amicizia da parte di %s",
    ACCETTATO_SUL_GRUPPO: 'Sei stato accettato da %s a far parte del Gruppo %s',
    ACCETTATO_NOTIFICA_ADMINS: "l'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)",
    RICHIESTA_BLOCCO_GRUPPO: 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  pt: {
    "L'Email è già stata Verificata": '',
    'Nuova Registrazione': 'Novo Registo',
    'Effettuata una Nuova Registrazione': 'Foi efectuado um novo registo',
    partecipanti: 'participantes',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    TESTO_ASSISTENZA:
      'Para entrar no site:\n%s\n\n' +
      'Esqueceu sua senha para acessar o site?\nhttps://freeplanet.app/requestresetpwd\n\n',
    BENVENUTO: 'Bem-vindo',
    TUE_NAVI: 'Aqui estão os seus navios programados',
    HAI_I_7_REQUISITI: 'PRIMEIROS PASSOS OK!\nVocê tem os 7 primeiros requisitos para entrar na lista de embarque!',
    NON_HAI_I_7_REQUISITI: 'Atenção!\nVocê ainda não tem os 7 requisitos para entrar na lista de embarque!',
    INFO_LA_MIA_LAVAGNA: '✨ Lista de etapas: ✨ \n',
    INFO_LAVAGNA_SITO_COMPLETARE:
      'Para completar todos os requisitos ir para o sitio:\n%s\nPara ver o estado do seu navio e dos seus convidados:\n 👉🏻 <a href="%s">Entre no seu Tablero</a>.\n',
    INFO_LAVAGNA_SITO:
      'Para ver em detalhe o estado do seu navio, no site, clique nas 3 linhas no canto superior esquerdo e vá para "DashBoard"..\n',
    INFO_LINK_DA_CONDIVIDERE: 'Links para partilhar com os seus convidados para que se registem no website:\n\n%s',
    INFO_LINK_ZOOM: 'Links para partilhar para participar na Zoom (Conferência Online):\n%s',
    ZOOM_CONFERENCE: 'Aqui está o programa das Conferências (Zoom) aberto a TODOS:',
    NON_VERIF: 'Não verificado',
    VERIF: 'Verificado',
    EMAIL: 'Email',
    BOT: 'BOT',
    EMAIL_NON_VERIF:
      'Email Não verificado\nleia a sua caixa de correio e encontre **"Confirmar o registo"**<br>e clique no botão **"Verificação do registo"**',
    TELEGRAM_NOT_CONNECTED: 'Telegrama não associado à sua conta!',
    NESSUN_INVITATO: 'Você não convidou ninguém',
    ZOOM_PARTECIPATO: 'Participação em pelo menos 1 Conferência (Zoom)',
    LINEE_GUIDA: 'Directrizes Aceites',
    VIDEO_INTRO: 'Ver o vídeo',
    SCRITTO_SOGNO: 'Você escreveu o seu sonho',
    PAYMENTS: 'Formas de pagamento',
    INVITATI: 'pessoas registadas que convidou',
    INVITATI_ATTIVI: 'Convidado com os 7 Requisitos',
    NONREG: 'Convidados não registados',
    CLICCA_PER_ZOOM: 'PARA O DIA E HORA INDICADOS, PARA ENTRAR NA VÍDEO-CONFERÊNCIA, CLIQUE AQUI',
    CLICCA_ENTRA: 'CLIQUE AQUI PARA ENTRAR !',
    ZOOM_INIZIATO: 'ESTA CONFERÊNCIA JÁ COMEÇOU!',
    SCEGLI_VOCE: 'escolha uma voz:',
    INVITATI_LISTA: 'Os seus convidados (em verde com, pelo menos, os 7 primeiros requisitos)',
    CIAO: 'Olá',
    ADDED_TOLISTAINGRESSO: 'Foi acrescentado à Lista de Embarque !',
    NO_PROG: 'Actualmente, ainda não consta da lista de embarque.!',
    SEND_LINK_CHAT_DONATORI: 'Olà %s!\nO seu SHIP está finalmente a zarpar.!\nEntre no Gift Chat, clicando aqui: %s',
    SEND_LINK_CHAT_SOGNATORE:
      'Olá %s!\nHoje você se torna um sonhador! 🎊🎊🎊 Entre no Gift Chat do navio {%s} clicando aqui: %s',
    ENTRA_GIFT_CHAT: 'Entre no Gift Chat do navio',
    DATA_PART_NAVE: 'data de partida do navio:',
    SOGNATORE: 'SONHEIROS',
    INTERMEDIO: 'Intermediar',
    MEDIATORE: 'MEDIATOR',
    DONATORI: 'DONATORES',
    RITESSITURA: 'ENTRE EM',
    'NAVE COMPLETATA': 'NAVIO COMPLETADO',
    'Doni Effettuati': 'Regalo Feitos',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore que apoia o Mediator',
    APERTURA_CHIUSURA_GIFT_CHAT:
      '%s: Abertura do GIFT CHAT\n%s Fechamento GIFT CHAT\nNota: Você tem 3 dias desde a abertura para entrar no Chat e fazer o seu Presente, depois do qual faremos o substituição.',
    SPOSTATO: 'Você foi transferido para um novo navio !',
    Note: 'Notas',
    TEMPORANEA: 'TEMPORÁRIO',
    NAVE: 'NAVE',
    MSG_SEND_FROM: 'Mensagem enviada por',
    ZOOM_CONFERMATO: 'Foi-lhe confirmado que assistiu à Conferência de Vídeo de Boas-vindas!',
    RICHIESTA_AMICIZIA: "Richiesta d'Amicizia da parte di %s",
    ACCETTATO_SUL_GRUPPO: 'Sei stato accettato da %s a far parte del Gruppo %s',
    ACCETTATO_NOTIFICA_ADMINS: "l'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)",

    RICHIESTA_BLOCCO_GRUPPO: 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
};

module.exports = {
  MYAPPS: [],
  INITDB_FIRSTIME: true,
  ACAPO: '\n',

  ENABLE_CRONTAB: 'CRONTAB',
  UPDATE_GRADUATORIA: 'UPDATE_GRAD',

  LANGADMIN: 'it',

  FREEPLANET: '1',
  AYNI: '7',
  CNM: '10',
  RISO: '13',
  RISO_STR_PATH: 'RISO',
  FIOREDELLAVITA: '15',
  PIUCHEBUONO: '17',
  MACRO: '18',

  IDAPP_BOTONGROUP: '1000',

  HELP_CHAT: '',
  TYPECONF_ZOOM: 'zoom',
  TYPECONF_JITSI: 'jitsi',

  APORTADOR_NONE: '------',

  SEP: '|',

  TYPE_PROJECT: 1,
  TYPE_TODO: 2,

  TipoMsg: {
    SEND_LINK_CHAT_DONATORI: 1,
    SEND_MSG: 2,
    SEND_MSG_SINGOLO: 3,
    SEND_TO_ALL: 10,
    SEND_MSG_EFFETTUA_IL_DONO: 1000,
    SEND_MSG_SOLLECITO_DONATORI_NO_DONO: 1005,
    SEND_MSG_A_MEDIATORI: 1010,
    SEND_MSG_A_SOGNATORE: 1020,
    SEND_MSG_A_UTENTE_SOSTITUITO: 1030,
    SEND_MSG_DONO_RICEVUTO_CORRETTAMENTE: 1040,
    SEND_MSG_DONO_NON_RICEVUTO: 1050,
  },

  Perm: {
    PERM_NONE: 0,
    PERM_FRIEND: 1,
    PERM_ALL: 10,
  },

  Placca: {
    DONATORI: 1,
    TUTTI: 2,

    STR_SOGNATORE: '<strong>Sognatore</strong>',
    STR_MEDIATORE: '<strong>Mediatore</strong>',
    STR_MEDIATORI: '<strong>Mediatori</strong>',
    STR_DONATORI: '<strong>Donatori</strong>',
    SOGNATORE: 'E1 🌈 ',
    MEDIATORE: 'A1 💦 ',
    SONOFUOCO: 0,
    SONOACQUA: 3,
    SONOTERRA: 2,
    SONOARIA: 1,
  },

  FieldType: {
    boolean: 1,
    date: 2,
    string: 4,
    binary: 8,
    html: 16,
    select: 32,
    number: 64,
    typeinrec: 128,
    multiselect: 256,
    password: 512,
    listimages: 1024,
    exact: 2048,
    image: 3000,
    image_and_filename: 3100,
    nationality: 4096,
    intcode: 5000,
    multioption: 6000,
    onlydate: 7000,
    hours: 8000,
    crypted: 9000,
  },

  MAX_PHASES: 5,
  FIRST_PROJ: '__PROJECTS',
  EXECUTE_CALCPROJ: true,

  isDirectoryAsync: async function (dir) {
    try {
      const stats = await fs.promises.stat(dir);
      return stats.isDirectory();
    } catch (e) {
      return false;
    }
  },

  isFileExistsAsync: async function (filename) {
    try {
      let fileExists = await fs.promises
        .stat(filename)
        .then(() => true)
        .catch(() => false);
      // console.log(filename, 'esiste', fileExists)
      return fileExists;
    } catch (e) {
      // console.log(filename, 'esiste', 'FALSE')
      return false;
    }
  },

  gettranslate: function (text, lang) {
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getlangbyres: function (res) {
    // ++Todo: res estrarre la lingua
    let lang = '';
    try {
      lang = res.lang;
    } catch (e) {}
    if (!lang) {
      lang = 'it';
    }
    return lang;
  },

  get__: function (text, lang) {
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getres__: function (text, res) {
    let lang = this.getlangbyres(res);
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getHostname: function () {
    return os.hostname();
  },
  testing: function () {
    return process.env.TESTING_ON === '1';
  },

  mylog: function (...args) {
    if (!this.testing()) console.log(args);
  },

  mylogoff: function (...args) {
    // doing nothing
  },

  mylogshow: function (...args) {
    console.log(args);
  },

  mylogserr: function (...args) {
    console.error(args);
  },

  allfieldSendMsg: function () {
    return [
      'userId',
      'source',
      'dest',
      'message',
      'datemsg',
      'read',
      'deleted',
      'origin',
      'idapp',
      'status',
      'options',
    ];
  },

  allfieldSendNotif: function () {
    return [
      'type',
      'userId',
      'sender',
      'typedir',
      'typeid',
      'dest',
      'descr',
      'title',
      'openUrl',
      'datenotif',
      'date_updated',
      'read',
      'deleted',
      'idapp',
      'tablerec',
      'extrafield',
      'tag',
      'options',
      'idrec',
      'status',
    ];
  },

  allfieldTodo: function () {
    return [
      'userId',
      'pos',
      'category',
      'descr',
      'note',
      'priority',
      'statustodo',
      'assignedToUsers',
      'created_at',
      'groupId',
      'modify_at',
      'completed_at',
      'expiring_at',
      'enableExpiring',
      'progress',
      'modified',
      'phase',
      'assigned_to_userId',
      'assignedToUsers',
      'hoursplanned',
      'hoursworked',
      'start_date',
      'themecolor',
      'themebgcolor',
    ];
  },

  allfieldMyEvent: function () {
    return ['userId'];
  },

  allfieldTodoWithId: function () {
    return ['_id', ...this.allfieldTodo()];
  },

  // #TODO Projects++ Add fields ...
  allfieldProject: function () {
    return [
      'idapp',
      'userId',
      'respUsername',
      'viceRespUsername',
      'pos',
      'typeproj',
      'id_main_project',
      'id_parent',
      'descr',
      'longdescr',
      'groupId',
      'hoursplanned',
      'hoursleft',
      'themecolor',
      'themebgcolor',
      'hoursworked',
      'priority',
      'statusproj',
      'created_at',
      'modify_at',
      'completed_at',
      'expiring_at',
      'enableExpiring',
      'progressCalc',
      'modified',
      'live_url',
      'test_url',
      'begin_development',
      'begin_test',
      'totalphases',
      'actualphase',
      'hoursweeky_plannedtowork',
      'endwork_estimate',
      'privacyread',
      'privacywrite',
      'tipovisu',
      'view',
    ];
  },

  allfieldBooking: function () {
    return [
      'idapp',
      'userId',
      'username',
      'id_bookedevent',
      'tableType',
      'numpeople',
      'numpeopleLunch',
      'numpeopleDinner',
      'numpeopleDinnerShared',
      'msgbooking',
      'modified',
      'infoevent',
      'datebooked',
      'booked',
    ];
  },

  allfieldBookingChange: function () {
    return [
      'numpeople',
      'numpeopleLunch',
      'numpeopleDinner',
      'numpeopleDinnerShared',
      'msgbooking',
      'modified',
      'infoevent',
      'datebooked',
      'booked',
    ];
  },

  allfieldProjectWithId: function () {
    return ['_id', ...this.allfieldProject()];
  },

  jsonCopy(src) {
    return JSON.parse(JSON.stringify(src));
  },

  CloneRecordToNew(src, modelTable) {
    const myrec = Object.assign({}, src);
    delete myrec._doc['_id'];
    myrec._id = new ObjectId();

    if (modelTable === 'Circuit') {
      myrec._doc.name = myrec._doc.name + ' copia';
      myrec._doc.path = myrec._doc.path + '_copia';
    }

    return myrec._doc;
  },

  sendBackNotif: async function (subscription, payload) {
    const Subscription = require('../models/subscribers');

    // console.log('sendBackNotif:', subscription, payload);

    const pushOptions = {
      vapidDetails: {
        subject: process.env.VAPI_KEY_SUBJECT,
        privateKey: process.env.PRIVATE_VAPI_KEY,
        publicKey: process.env.PUBLIC_VAPI_KEY,
      },
      TTL: payload.ttl,
      headers: {},
    };

    // Pass object into sendNotification
    return (
      webpush
        .sendNotification(subscription._doc, JSON.stringify(payload), pushOptions)
        .catch((err) => {
          if (err.statusCode === 410) {
            // Gone: is not valid anymore (Expired probably!), so I have to delete from my db
            return Subscription.findOneAndDelete({ _id: subscription._id });
          } else {
            console.log('Subscription is no longer valid: ', err);
          }
        })
        .then((ris) => {})
        // console.log('sendNotification', ris);
        .catch((err) => {
          console.error(err);
        })
    );
  },

  sendNotificationToUser: async function (userId, title, content, openUrl, openUrl2, tag, actions, id) {
    const Subscription = require('../models/subscribers');

    content = this.convertHTMLtoText(content);
    content = this.ConvertHTMLToPlainText(content);

    let payload = {
      actions,
      title: title,
      content,
      url: openUrl,
      url2: openUrl2,
      tag,
      id,
    };

    try {
      let subscriptions = [];
      try {
        subscriptions = await Subscription.find({ userId });
      } catch (err) {
        console.error(`Error occurred while getting subscriptions:`, err);
        return false;
      }

      if (!subscriptions || subscriptions.length === 0) {
        // console.log('No subscriptions found for user:', userId);
        return false;
      }

      const pushPayload = JSON.stringify(payload);

      // Opzioni VAPID base (senza audience specifico)
      const vapidDetails = {
        subject: process.env.VAPI_KEY_SUBJECT,
        privateKey: process.env.PRIVATE_VAPI_KEY,
        publicKey: process.env.PUBLIC_VAPI_KEY,
      };

      let parallelSubscriptionCalls = subscriptions.map((subscription, index) => {
        return new Promise(async (resolve, reject) => {
          try {
            const pushSubscription = {
              endpoint: subscription.endpoint,
              keys: {
                p256dh: subscription.keys.p256dh,
                auth: subscription.keys.auth,
              },
            };

            // Opzioni per la notifica
            const pushOptions = {
              vapidDetails: vapidDetails,
              TTL: payload.ttl || 86400, // Default 24 ore se non specificato
            };

            /*console.log(
              `Sending push notification ${index + 1}/${subscriptions.length} to ${
                subscription.browser || 'unknown browser'
              }`
            );*/

            const result = await webpush.sendNotification(pushSubscription, pushPayload, pushOptions);

            resolve({
              status: true,
              endpoint: subscription.endpoint,
              data: result,
            });
          } catch (err) {
            console.error('Push notification error:', {
              endpoint: subscription.endpoint,
              browser: subscription.browser,
              statusCode: err.statusCode,
              error: err.body || err.message,
            });

            // Cancella la subscription se l'errore indica che non è più valida
            if (err.statusCode === 401 || err.statusCode === 404 || err.statusCode === 410) {
              try {
                await Subscription.deleteOne({ _id: subscription._id });
                console.log('Deleted invalid subscription:', subscription.endpoint);
              } catch (deleteErr) {
                console.error('Error deleting subscription:', deleteErr);
              }
            }

            resolve({
              status: false,
              endpoint: subscription.endpoint,
              error: err.message || 'Unknown error',
              statusCode: err.statusCode,
            });
          }
        });
      });

      const results = await Promise.allSettled(parallelSubscriptionCalls);

      // Conta i successi
      const successful = results.filter((r) => r.status === 'fulfilled' && r.value.status === true).length;
      const failed = results.length - successful;

      console.log(`Push notifications sent: ${successful} successful, ${failed} failed out of ${results.length} total`);

      return successful > 0; // Ritorna true se almeno una notifica è stata inviata con successo
    } catch (e) {
      console.error('sendNotificationToUser error:', e.message);
      return false;
    }
  },

  getReqByPar(idapp, usernameOrig, groupOrig) {
    return {
      body: {
        idapp: idapp,
      },
      user: {
        username: usernameOrig,
        groupOrig,
      },
    };
  },

  sendNotificationByUsername: async function (idapp, username, cmd, telegram, usernameOrig, recObjCreator) {
    var { User } = require('../models/user');
    const { SendNotif } = require('../models/sendnotif');

    const telegrambot = require('../telegram/telegrambot');

    const user = await User.findOne({ idapp, username }, { _id: 1, username: 1, lang: 1 }).lean();
    if (!user) return;

    let userId = user._id;
    let lang = user.lang;

    let sendmynotif = true;

    let title = this.getNomeAppByIdApp(idapp);
    let descr = '';
    let openUrl = '/';
    let tag = '';
    let actions = [];
    let domanda = '';
    let keyb = null;
    if (cmd) {
      if (cmd === shared_consts.FRIENDSCMD.REQFRIEND) {
        descr = printf(this.get__('RICHIESTA_AMICIZIA', lang), usernameOrig);
        openUrl = '/my/' + usernameOrig;
        tag = 'reqfriends';

        const userrecDest = await User.getUserShortDataByUsername(idapp, usernameOrig);

        if (userrecDest) {
          sendmynotif = false; // non lo rimandare 2 volte !
          // SEND TELEGRAM NOTIFICATION
          telegrambot.askConfirmationUserFriend(
            idapp,
            shared_consts.CallFunz.RICHIESTA_AMICIZIA,
            userrecDest,
            username,
            usernameOrig
          );
        }
      } else if (cmd === shared_consts.FRIENDSCMD.SETHANDSHAKE) {
        const userrecDest = await User.getUserShortDataByUsername(idapp, usernameOrig);

        const foundIfAlready = await User.isMyHandShake(idapp, username, usernameOrig);
        if (!foundIfAlready) {
          telegrambot.askConfirmationUserFriend(
            idapp,
            shared_consts.CallFunz.RICHIESTA_HANDSHAKE,
            userrecDest,
            username,
            usernameOrig
          );
        }
      }
    }

    if (userId) {
      // SEND PUSH NOTIFICATION
      await this.sendNotificationToUser(userId, title, descr, openUrl, '', tag, actions);
    }

    if (telegram && sendmynotif) {
      const idtelegram = await User.TelegIdByUsername(idapp, username);

      await telegrambot.sendMsgTelegramByIdTelegram(
        idapp,
        idtelegram,
        descr + domanda,
        undefined,
        undefined,
        true,
        keyb
      );
    }
  },

  sendMsgTelegramToAdmin: async function (idapp, msg) {
    const telegrambot = require('../telegram/telegrambot');

    const idtelegram = telegrambot.ADMIN_IDTELEGRAM_SERVER;

    await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, msg);
  },

  sendNotifAndMsgTelegram: async function (idapp, userId, objmsg, telegram, msgextrateleg) {
    if (userId) {
      await this.sendNotificationToUser(
        userId,
        objmsg.title,
        objmsg.descr,
        objmsg.openUrl,
        '',
        objmsg.tag,
        objmsg.actions
      );
    }

    if (telegram) {
      var { User } = require('../models/user');
      const telegrambot = require('../telegram/telegrambot');

      const idtelegram = await User.TelegIdById(idapp, userId);

      const msgteleg = objmsg.descr + (msgextrateleg ? '\n' + msgextrateleg : '');
      await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, msgteleg);
    }
  },

  sendNotifGroupByUsername: async function (
    cmd,
    idapp,
    usernameOrig,
    usernameDest,
    username_action,
    groupname,
    myrecgroup,
    isAdmin,
    username_worked
  ) {
    const { SendNotif } = require('../models/sendnotif');
    const { User } = require('../models/user');
    const telegrambot = require('../telegram/telegrambot');

    const req = this.getReqByPar(idapp, usernameOrig);

    const user = await User.findOne({ idapp, username: usernameDest }, { _id: 1, lang: 1 });
    if (user) {
      let lang = user.lang;
      let paramsObj = {
        usernameDest,
        groupnameDest: groupname,
        username_action: username_action,
        singleadmin_username: usernameDest,
        options: 0,
        lang,
        isAdmin,
        username_worked,
      };

      let sendnotif = true;
      let typedir = shared_consts.TypeNotifs.TYPEDIR_GROUPS;
      let typeid = 0;
      let onlysave = false;
      let numuseringroup = await User.countUsersInGroup(idapp, groupname);

      if (cmd) {
        if (cmd === shared_consts.GROUPSCMD.SETGROUP) {
          if (myrecgroup && myrecgroup.createdBy === usernameDest && numuseringroup <= 1) {
            typeid = shared_consts.TypeNotifs.ID_GROUP_NEW_REC;
          } else {
            typeid = shared_consts.TypeNotifs.ID_GROUP_ACCEPTED;
          }
        } else if (cmd === shared_consts.GROUPSCMD.REFUSE_REQ_GROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REFUSED;
        } else if (cmd === shared_consts.GROUPSCMD.REMOVE_FROM_MYGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REMOVED;
        } else if (cmd === shared_consts.GROUPSCMD.REQGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REQUEST_TO_ENTER;
          // paramsObj.options = MessageOptions.Notify_OnlyToNotifinApp + MessageOptions.Notify_ByBotTelegram;
          const myuserdata = await User.getUserShortDataByUsername(idapp, username_action);
          telegrambot.askConfirmationUser(
            idapp,
            shared_consts.CallFunz.RICHIESTA_GRUPPO,
            myuserdata,
            usernameDest,
            groupname,
            myrecgroup._id,
            '',
            ''
          );
          onlysave = false;
        } else if (cmd === shared_consts.GROUPSCMD.BLOCK_USER) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_BLOCK_USER;
        } else if (cmd === shared_consts.GROUPSCMD.DELETE_GROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_DELETE_USER;
        } else if (cmd === shared_consts.GROUPSCMD.ADDADMIN_OFMYGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_ADDED_ADMIN_OFMYGROUP;
        } else if (cmd === shared_consts.GROUPSCMD.REMOVEADMIN_OFMYGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REMOVED_ADMIN_OFMYGROUP;
        }
      }

      if (sendnotif && typeid > 0) {
        // CREATE NOTIFICATION IN TABLE SENDNOTIF
        await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
      }
    }
  },

  sendNotifCircuitByUsername: async function (
    cmd,
    idapp,
    usernameOrig,
    groupOrig,
    usernameDest,
    groupDest,
    username_action,
    circuitname,
    path,
    myreccircuit,
    isAdmin,
    username_worked,
    extrarec
  ) {
    const { SendNotif } = require('../models/sendnotif');
    const { User } = require('../models/user');
    const telegrambot = require('../telegram/telegrambot');
    const { MyGroup } = require('../models/mygroup');
    const { Circuit } = require('../models/circuit');

    const req = this.getReqByPar(idapp, usernameOrig, groupOrig);

    let arrris = [];

    const userOrig = await User.findOne(
      { idapp, username: usernameOrig },
      { _id: 1, lang: 1, aportador_solidario: 1, name: 1, surname: 1, username: 1 }
    );

    const user = await User.findOne({ idapp, username: usernameDest }, { _id: 1, lang: 1 });

    let lang = 'it';
    if (user) {
      lang = user.lang;
    }
    let ris = null;

    try {
      let paramsObj = {
        usernameDest,
        circuitnameDest: circuitname,
        circuitId: myreccircuit ? myreccircuit._id : '',
        path,
        username_action: username_action,
        singleadmin_username: usernameDest,
        extrarec,
        options: 0,
        lang,
        isAdmin,
        username_worked,
      };

      let sendnotif = true;
      let typedir = shared_consts.TypeNotifs.TYPEDIR_CIRCUITS;
      let typeid = 0;
      let onlysave = false;
      let numuserincircuit = await User.countUsersInCircuit(idapp, circuitname);
      const mycircuit = await Circuit.findOne({ name: circuitname }).lean();
      if (mycircuit && extrarec) {
        extrarec.fido_scoperto_default = mycircuit.fido_scoperto_default;
        extrarec.fido_scoperto_default_grp = mycircuit.fido_scoperto_default_grp;
        extrarec.link_group = mycircuit.link_group;
      }

      if (cmd) {
        if (cmd === shared_consts.CIRCUITCMD.SET) {
          if (myreccircuit && myreccircuit.createdBy === usernameDest && numuserincircuit <= 1) {
            typeid = shared_consts.TypeNotifs.ID_CIRCUIT_NEW_REC;
          } else {
            typeid = shared_consts.TypeNotifs.ID_CIRCUIT_ACCEPTED;
          }
        } else if (cmd === shared_consts.CIRCUITCMD.REFUSE_REQ) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REFUSED;
        } else if (cmd === shared_consts.CIRCUITCMD.REMOVE_FROM_MYLIST) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REMOVED;
        } else if (cmd === shared_consts.CIRCUITCMD.REQ) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REQUEST_TO_ENTER;
          // paramsObj.options = MessageOptions.Notify_OnlyToNotifinApp + MessageOptions.Notify_ByBotTelegram;
          const myuserdata = await User.getUserShortDataByUsername(idapp, username_action);
          let cmdrichiesta = shared_consts.CallFunz.RICHIESTA_FIDO;
          if (mycircuit && mycircuit.askManagerToEnter) {
            cmdrichiesta = shared_consts.CallFunz.RICHIESTA_CIRCUIT;
          }

          telegrambot.askConfirmationUser(
            idapp,
            cmdrichiesta,
            myuserdata,
            usernameDest,
            circuitname,
            '',
            myreccircuit._id,
            '',
            extrarec.groupname
          );

          if (mycircuit.sendEmailAfterAskingToEnter) {
            const { Circuit } = require('../models/circuit');

            const path = await Circuit.getPathByCircuitName(idapp, circuitname);
            let myapp = this.getHostByIdApp(idapp);
            let linkcirc = myapp + path;
            let messaggio = '';
            let mytitle = i18n.__('CIRCUIT_REQUEST_TO_ENTER_ASKMANAGER_TITLE', usernameOrig);
            messaggio = i18n.__(
              'CIRCUIT_REQUEST_TO_ENTER_ASKMANAGER_SHORT',
              usernameOrig,
              '<strong>' + circuitname + '</strong>',
              numuserincircuit,
              userOrig.aportador_solidario
            );
            messaggio += '\n\n';
            messaggio += '<a href="' + linkcirc + '" target="_blank">Clicca qui per entrare sul Circuito</a>\n';

            const sendemail = require('../sendemail');

            const email = myuserdata.email;

            sendemail.sendEmail_ByText(lang, email, myuserdata, idapp, {
              emailbody: messaggio,
              emailtitle: mytitle,
            });
          }
          onlysave = false;
        } else if (cmd === shared_consts.CIRCUITCMD.ADDADMIN) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_ADDED_ADMIN;
        } else if (cmd === shared_consts.CIRCUITCMD.SETFIDO) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_SETFIDO;
        } else if (cmd === shared_consts.CIRCUITCMD.REMOVEADMIN) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REMOVED_ADMIN;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_SENDCOINSREQ;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ_SENT) {
          // Crea l'ID di Transazione
          paramsObj.idTransaction = new ObjectId();
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_SENDCOINSREQ_SENT;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_ACCEPTED;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT_SENT) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_ACCEPTED_SENT;
          // } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
          //  typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_REFUSED;
          //} else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE_SENT) {
          //  typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_REFUSED_SENT;
        }
      }

      if (sendnotif && typeid > 0) {
        const groupDestoContoCom = groupDest
          ? groupDest
          : extrarec && extrarec.contoComDest
          ? extrarec.contoComDest
          : '';
        // Check if is group:
        if (
          groupDestoContoCom &&
          cmd !== shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT_SENT &&
          cmd !== shared_consts.CIRCUITCMD.SENDCOINS_REFUSE_SENT
        ) {
          let arrusers = await MyGroup.getListAdminsByGroupName(idapp, groupDestoContoCom);
          if (arrusers.length <= 0) arrusers = await Circuit.getListAdminsByCircuitPath(idapp, groupDestoContoCom);

          let notiftoUpdate = '';
          let findrecnotif = await SendNotif.findOne({ _id: paramsObj.extrarec.notifId }).lean();
          notiftoUpdate = findrecnotif ? findrecnotif.extrarec.notifIdToUpdate : '';
          if (findrecnotif) {
            let arrrec = [];

            if (notiftoUpdate) arrrec = await SendNotif.find({ 'extrarec.notifIdToUpdate': notiftoUpdate });

            for (let rec of arrrec) {
              paramsObj.usernameDest = rec.dest;
              paramsObj.sender = rec.sender;
              if (
                cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT ||
                cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE
              ) {
                try {
                  paramsObj.extrarec.notifId = rec._id;
                } catch (e) {}
              }

              ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
              if (!ris) {
                console.error(
                  'Errore createNewNotifToSingleUser! ',
                  paramsObj.usernameDest,
                  'dest ->',
                  groupDestoContoCom
                );
              } else {
                arrris.push(ris._id);
              }
            }
          } else {
            for (let i = 0; i < arrusers.length; i++) {
              paramsObj.usernameDest = arrusers[i].username;

              ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
              if (!ris) {
                console.error(
                  'Errore createNewNotifToSingleUser! ',
                  paramsObj.usernameDest,
                  'dest ->',
                  groupDestoContoCom
                );
              } else {
                arrris.push(ris._id);
              }
            }
          }
        } else {
          // CREATE NOTIFICATION IN TABLE SENDNOTIF
          ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);

          if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT || cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
            if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
              typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_ACCEPTED_SENT;
            } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
              typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_REFUSED_SENT;
            }

            let notiftoUpdate = '';
            let findrecnotif = await SendNotif.findOne({ _id: paramsObj.extrarec.notifId }).lean();
            notiftoUpdate = findrecnotif ? findrecnotif.extrarec.notifIdToUpdate : '';
            arrnotiftoUpdate = findrecnotif ? findrecnotif.extrarec.arrnotifIdToUpdate : '';
            if (findrecnotif && notiftoUpdate && arrnotiftoUpdate) {
              for (let myid of arrnotiftoUpdate) {
                let rec = await SendNotif.findOne({ _id: myid }).lean();
                if (rec && rec.status === 0) {
                  paramsObj.usernameDest = rec.dest;
                  paramsObj.extrarec.idStatusToSent = rec._id;

                  ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
                  if (!ris) {
                    console.error(
                      'Errore createNewNotifToSingleUser! ',
                      paramsObj.usernameDest,
                      'dest ->',
                      groupDestoContoCom
                    );
                  }
                }
              }
            }
          }
        }
      }

      return { recnotif: ris, arrris };
    } catch (e) {
      console.log(e.message);
    }
  },

  sendNotificationByGroupname: async function (idapp, usernameOrig, groupname, cmd, value, telegram, username_action) {
    const { MyGroup } = require('../models/mygroup');
    const { User } = require('../models/user');

    const group = await MyGroup.findOne({ idapp, groupname }, { _id: 1, admins: 1, createdBy: 1 }).lean();
    if (!group) return;

    let giainviato = false;

    try {
      // SEND TO THE ADMINS THE NOTIFICATIONS
      for (const singleadmin of group.admins) {
        try {
          if (singleadmin.username) {
            if (usernameOrig === singleadmin.username) giainviato = true;

            await this.sendNotifGroupByUsername(
              cmd,
              idapp,
              usernameOrig,
              singleadmin.username,
              username_action,
              groupname,
              group,
              true
            );
          }
        } catch (e) {
          console.error('sendNotificationByGroupname', e);
        }
      }

      if (!giainviato && cmd !== shared_consts.GROUPSCMD.REQGROUP) {
        // SEND TO THE USER THE NOTIFICATION
        await this.sendNotifGroupByUsername(
          cmd,
          idapp,
          usernameOrig,
          usernameOrig,
          username_action,
          groupname,
          null,
          false
        );
      }
    } catch (e) {
      console.error('e', e);
    }
  },

  // VERSIONE CORRETTA - USER_ADMIN_CIRCUITS è un array
  getAdminsByCircuit(circuit) {
    const adminsMap = new Map();

    // Aggiungi gli admin esistenti
    circuit.admins.forEach((admin) => {
      adminsMap.set(admin.username, admin);
    });

    // Aggiungi gli admin dei circuiti dall'array (sovrascrive se esistono già)
    shared_consts.USER_ADMIN_CIRCUITS.forEach((username) => {
      adminsMap.set(username, { username });
    });

    return Array.from(adminsMap.values());
  },

  sendNotificationByCircuit: async function (
    idapp,
    usernameOrig,
    circuitname,
    cmd,
    value,
    telegram,
    username_action,
    extrarec
  ) {
    const { Circuit } = require('../models/circuit');
    const { SendNotif } = require('../models/sendnotif');
    var { User } = require('../models/user');

    const circuit = await Circuit.findOne(
      { idapp, name: circuitname },
      { _id: 1, admins: 1, createdBy: 1, path: 1 }
    ).lean();
    if (!circuit) return;

    let giainviato = false;

    try {
      // SEND TO THE ADMINS THE NOTIFICATIONS

      let ris = false;
      let inviato = false;
      let arrris = [];

      if (
        cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ ||
        cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT ||
        cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE
      ) {
        groupOrig = extrarec.grouporig;

        let recnotif = null;

        if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ) {
          // msg al Mittente (o mittenti del 'groupOrig') "Stai inviando X RIS"
          let res = await this.sendNotifCircuitByUsername(
            shared_consts.CIRCUITCMD.SENDCOINS_REQ_SENT,
            idapp,
            extrarec.dest,
            extrarec.groupdest,
            usernameOrig,
            groupOrig,
            username_action,
            circuitname,
            circuit.path,
            null,
            false,
            '',
            extrarec
          );

          if (res) {
            recnotif = res.recnotif;
            arrris = res.arrris;
          }
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
          // msg ai MITTENTI: "X RIS sono stati accettati da Destinatario"
          await this.sendNotifCircuitByUsername(
            shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT_SENT,
            idapp,
            extrarec.dest,
            extrarec.groupdest,
            usernameOrig,
            groupOrig,
            username_action,
            circuitname,
            circuit.path,
            null,
            false,
            '',
            extrarec
          );
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
          await this.sendNotifCircuitByUsername(
            shared_consts.CIRCUITCMD.SENDCOINS_REFUSE_SENT,
            idapp,
            extrarec.dest,
            extrarec.groupdest,
            usernameOrig,
            groupOrig,
            username_action,
            circuitname,
            circuit.path,
            null,
            false,
            '',
            extrarec
          );
        }

        if (recnotif) extrarec.notifIdToUpdate = recnotif._id;
        if (arrris) extrarec.arrnotifIdToUpdate = arrris;

        let usernametoOrigUse = usernameOrig;
        if (extrarec.groupname) {
          usernametoOrigUse = '';
        }
        ris = await this.sendNotifCircuitByUsername(
          cmd,
          idapp,
          usernametoOrigUse,
          groupOrig,
          extrarec.dest,
          extrarec.groupdest,
          username_action,
          circuitname,
          circuit.path,
          null,
          false,
          '',
          extrarec
        );

        if (arrris && ris && ris.recnotif) {
          for (const rec of arrris) {
            let obj = {
              'extrarec.notifIdToUpdate': ris.recnotif._id,
            };

            await SendNotif.findOneAndUpdate({ _id: rec._id }, { $set: obj }, { new: false });
          }
        }

        extrarec.notifIdToUpdate = '';

        if (ris.recnotif) inviato = true;
        // ++Todo: Inviare anche agli Admin ?!
      } else {
        const groupOrig = ''; //++Todo: extrarec.groupOrig

        let arradmins = this.getAdminsByCircuit(circuit);
        // console.log('arradmins', arradmins);

        const mapByUsername = arradmins.reduce((map, item) => {
          map[item.username] = item;
          return map;
        }, {});

        arradmins = Object.values(mapByUsername);

        for (const singleadmin of arradmins) {
          try {
            if (singleadmin.username) {
              if (usernameOrig === singleadmin.username) giainviato = true;

              extrarec.send_email = !!singleadmin.enable_to_receive_email ? singleadmin.enable_to_receive_email : true;

              await this.sendNotifCircuitByUsername(
                cmd,
                idapp,
                usernameOrig,
                groupOrig,
                singleadmin.username,
                '',
                username_action,
                circuitname,
                circuit.path,
                circuit,
                true,
                '',
                extrarec
              );
              inviato = true;
            }
          } catch (e) {
            console.error('sendNotificationByCircuit', e);
          }
        }

        if (!giainviato && cmd !== shared_consts.CIRCUITCMD.REQ) {
          extrarec.send_email = await User.isEnableToReceiveEmailByUsernameECmd(idapp, usernameOrig, cmd);

          // SEND TO THE USER DEST THE NOTIFICATION
          ris = await this.sendNotifCircuitByUsername(
            cmd,
            idapp,
            usernameOrig,
            groupOrig,
            usernameOrig,
            '',
            username_action,
            circuitname,
            circuit.path,
            null,
            false,
            '',
            extrarec
          );

          if (ris && ris.recnotif) inviato = true;
        }
      }

      if (inviato) {
        if (arrris && arrris.length > 0) {
          for (const notif of arrris) {
            await SendNotif.updatePendingTransactions(notif);
          }
        } else {
          if (ris) {
            await SendNotif.updatePendingTransactions(ris.recnotif);
          }
        }
      }

      return { ris: ris.recnotif, inviato };
    } catch (e) {
      console.error('sendNotificationByCircuit: ', e);
      return { ris: null, inviato: false };
    }
  },

  // **********************
  // SORT WITH PREV_ID
  // **********************
  /* mapSort: function (linkedList) {
    let sortedList = [];
    let remainingList = [];
    var map = new Map();
    var currentId = null;

    // console.log('linkedList', linkedList);

    // index the linked list by previous_item_id
    for (var i = 0; i < linkedList.length; i++) {
      var item = linkedList[i];
      if (item.id_prev === server_constants.LIST_START) {
        // first item
        currentId = item._id.toString();
        // console.log('currentId', currentId);
        sortedList.push(item);
      } else {
        map.set(item.id_prev.toString(), i);
      }
    }

    let conta = 0;
    while (conta < linkedList.length) {
      // get the item with a previous item ID referencing the current item
      var nextItem = linkedList[map.get(currentId)];
      if (nextItem === undefined) {

      } else {
        sortedList.push(nextItem);
        currentId = nextItem._id.toString();
      }
      conta++;
    }

    if (linkedList.length > sortedList.length) {
      // If are not in the list, I'll put at the bottom of the list
      // console.log('ATTENZIONE !!! ', sortedList.length, linkedList.length);
      for (const itemlinked of linkedList) {
        const elemtrov = sortedList.find((item) => item._id === itemlinked._id);
        if (elemtrov === undefined) {
          sortedList.push(itemlinked);
        }
      }
    }

    // console.log('DOPO sortedList', sortedList);

    return sortedList;
  },
  */

  async sendNotifToCircuitsAdmin(idapp, circuit, alsotelegram, title, msg, tag = '') {
    const { User } = require('../models/user');

    const telegrambot = require('../telegram/telegrambot');

    if (!circuit || !this.getAdminsByCircuit(circuit)) {
      console.error('sendNotifToCircuitsAdmin: missing circuit or circuit.admins', circuit);
      return;
    }

    for (const singleadmin of this.getAdminsByCircuit(circuit)) {
      try {
        if (singleadmin && singleadmin.username) {
          const user = await User.getUserShortDataByUsername(idapp, singleadmin.username);

          if (user) {
            if (alsotelegram) {
              if (!user.profile || !user.profile.teleg_id) {
                console.error('sendNotifToCircuitsAdmin: missing user.profile.teleg_id', user);
              } else {
                telegrambot.local_sendMsgTelegramByIdTelegram(idapp, user.profile.teleg_id, title + ' ' + msg);
              }
            }

            if (!user._id) {
              console.error('sendNotifToCircuitsAdmin: missing user._id', user);
            } else {
              await this.sendNotificationToUser(user._id, title, msg, '/', '', tag, []);
            }
          }
        }
      } catch (e) {
        console.error('sendNotifToCircuitsAdmin', e);
      }
    }
  },

  sendNotifToAdmin(idapp, alsotelegram, title, msg, tag = '') {
    try {
      const { User } = require('../models/user');

      let queryadmins = { username: 'paoloar77', idapp };

      if (idapp === 0) idapp = '13';

      if (idapp === '13') {
        // this.RISO
        queryadmins = { username: 'surya1977', idapp };
      }

      User.find(queryadmins).then(async (arrusers) => {
        if (arrusers !== null) {
          for (const user of arrusers) {
            if (alsotelegram) {
              this.sendMsgTelegramToAdmin(idapp, title + ' ' + msg);
            }

            await this.sendNotificationToUser(user._id, title, msg, '/', '', tag, []).then((ris) => {
              if (ris) {
              } else {
                // already sent the error on calling sendNotificationToUser
              }
            });
          }
        }
      });
    } catch (e) {
      console.error(e);
    }
  },

  checkUserOk(userpassed, userauth, res) {
    // this.mylog('checkUserOk', userpassed, userauth);
    if (String(userpassed) !== String(userauth)) {
      // I'm trying to write something not mine!
      this.mylog("I'm trying to write something not mine!: userId = ", userpassed, 'req.user._id', userauth);
      if (!res) {
        return {
          exit: true,
          ret: false,
        };
      } else {
        return {
          exit: true,
          ret: false,
          // ret: res.status(404).
          //   send({ code: server_constants.RIS_CODE_TODO_CREATING_NOTMYUSER }),
        };
      }
    } else {
      return { exit: false, ret: false };
    }
  },

  ConvertHTMLToPlainText(strHTML) {
    if (strHTML) return strHTML.replace(/<[^>]+>/g, '');
    return '';
  },

  convertSpaces_ToUScore(mystr) {
    if (mystr) return mystr.replace(/\s+/g, '_');
    return '';
  },

  convertHTMLtoText(myhtml) {
    let msg = myhtml;
    if (msg) {
      msg = msg.replace('&quot;', '"');
      msg = msg.replace(/<strong>/g, '');
      msg = msg.replace(/<\/strong>/g, '');
      msg = msg.replace(/<em>/g, '');
      msg = msg.replace(/<\/em>/g, '');
      msg = msg.replace('&gt;', '>');
      msg = msg.replace('&lt;', '<');
      msg = msg.replace('&amp;', '&');
      msg = msg.replace('<br>', '\n');
    }

    return msg;
  },

  htmlToTelegramText(html) {
    try {
      const withMarkdown = html
        .replace(/<strong>(.*?)<\/strong>/g, '*$1*')
        .replace(/<b>(.*?)<\/b>/g, '*$1*')
        .replace(/<em>(.*?)<\/em>/g, '_$1_')
        .replace(/<u>(.*?)<\/u>/g, '__$1__');

      // Remove other HTML tags
      const plainText = withMarkdown.replace(/<[^>]*>/g, '');

      // Replace HTML entities with their equivalent Markdown or plain text representations
      const replacements = [
        { pattern: /&amp;/g, replacement: '&' },
        { pattern: /&lt;/g, replacement: '<' },
        { pattern: /&gt;/g, replacement: '>' },
        { pattern: /&quot;/g, replacement: '"' },
        { pattern: /&#39;/g, replacement: "'" },
      ];

      let telegramText = plainText;
      replacements.forEach((replacement) => {
        telegramText = telegramText.replace(replacement.pattern, replacement.replacement);
      });

      return telegramText;
    } catch (e) {
      console.error(e);
    }
  },

  htmlToTelegramText(html) {
    try {
      const withMarkdown = html
        .replace(/<strong>(.*?)<\/strong>/g, '*$1*')
        .replace(/<b>(.*?)<\/b>/g, '*$1*')
        .replace(/<em>(.*?)<\/em>/g, '_$1_')
        .replace(/<u>(.*?)<\/u>/g, '__$1__');

      // Remove other HTML tags
      const plainText = withMarkdown.replace(/<[^>]*>/g, '');

      // Replace HTML entities with their equivalent Markdown or plain text representations
      const replacements = [
        { pattern: /&amp;/g, replacement: '&' },
        { pattern: /&lt;/g, replacement: '<' },
        { pattern: /&gt;/g, replacement: '>' },
        { pattern: /&quot;/g, replacement: '"' },
        { pattern: /&#39;/g, replacement: "'" },
      ];

      let telegramText = plainText;
      replacements.forEach((replacement) => {
        telegramText = telegramText.replace(replacement.pattern, replacement.replacement);
      });

      return telegramText;
    } catch (e) {
      console.error(e);
    }
  },

  convertTexttoHtml(myhtml) {
    // let msg = myhtml;
    // msg = msg.replace('\n', '<br>');

    // return msg

    return myhtml;
  },

  removeSpecialCharForEmail(myhtml) {
    let msg = myhtml;
    try {
      if (msg) msg = msg.replace(/"/g, "'");
    } catch (e) {
      return msg;
    }

    return msg;
  },

  getNomeAppByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) return (process.env.NODE_ENV === 'test' ? 'Test: ' : '') + myapp.name;
    else return '';
  },

  getTimeExpReg: async function (idapp, username) {
    var { User } = require('../models/user');

    return await User.createNewReqRegistrationGetLink(idapp, username);
  },

  getLinkRegByIdAppAndMsgStrutt: function (idapp, msg, rec) {
    let myapp = this.getHostByIdApp(idapp);
    if (myapp) {
      myapp +=
        '/registrati/' + rec.aportador_solidario + '/' + msg.from.username + '/' + msg.from.id + '/' + rec.regexpire;
    }

    return myapp;
  },

  getHostByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      let siteport = myapp.portapp && myapp.portapp !== '0' ? ':' + myapp.portapp : '';

      if (process.env.NODE_ENV === 'test') return myapp.host_test + siteport;
      else return myapp.host + siteport;
    } else return '';
  },

  getLinkUserProfile: function (idapp, username) {
    return this.getHostByIdApp(idapp) + '/my/' + username;
  },

  getLinkUserTelegram: async function (idapp, username, myuser) {
    var { User } = require('../models/user');

    let u_tg = '';
    if (myuser)
      u_tg =
        myuser.profile && myuser.profile.hasOwnProperty('username_telegram')
          ? myuser.profile.username_telegram
          : usernametelegram;

    if (!u_tg) {
      u_tg = await User.getUsernameTelegram(idapp, username);
    }

    if (u_tg) return `https://t.me/${u_tg}`;
    else return '';
  },

  getLinkEditUserProfile: function (idapp) {
    return this.getHostByIdApp(idapp) + '/editprofile';
  },

  getConfSiteOptionEnabledByIdApp: function (idapp, option) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      if (myapp.hasOwnProperty('confsite')) {
        if (myapp.confsite.hasOwnProperty('options')) {
          return this.isBitActive(myapp.confsite.options, option);
        }
      }
    }

    return false;
  },

  getContactEmailSupportBydApp: function (idapp, option) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      if (myapp.hasOwnProperty('contacts')) {
        if (myapp.confsite.hasOwnProperty('email')) {
          return myapp.contacts.email;
        }
      }
    }

    return '';
  },

  getEnableTokenExpiredByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp && myapp.confpages && myapp.confpages.hasOwnProperty('enableTokenExpired')) {
      return myapp.confpages.enableTokenExpired;
    }

    return false;
  },

  getConfParamSiteByIdApp: function (idapp, field) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      if (myapp.hasOwnProperty('confsite')) {
        if (myapp.confsite.hasOwnProperty(field)) {
          return myapp.confsite[field];
        }
      }
    }

    return false;
  },

  isAbilitaNave: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    return myapp.abilitanave;
  },

  removeLastSlashFromPath: function (myString) {
    let regex = /\/$/;
    let result = myString;

    try {
      if (myString) result = myString.replace(regex, '');
    } catch (e) {
      return myString;
    }

    return result;
  },

  getdirByIdApp: function (idapp, dirmain = false) {
    let mypath = '';
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      if (process.env.NODE_ENV === 'test') mypath = myapp && myapp.dir_test ? myapp.dir_test : '';
      else mypath = myapp && myapp.dir ? myapp.dir : '';

      if (dirmain) {
        if (!this.sulServer()) {
          mypath += 'public';
        }
      }
    }

    return this.removeLastSlashFromPath(mypath);
  },

  getAdminEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) return myapp.adminemail;
    else return '';
  },

  getreplyToEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) return myapp.replyTo;
    else return '';
  },

  getpathregByIdApp: function (idapp, lang) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      const addstr = myapp.pathreg_add ? myapp.pathreg_add : '';

      return 'registration' + addstr + '/' + lang;
    } else {
      return 'registration' + '/' + lang;
    }
  },

  getparamSiteTableByIdApp: function (idapp, param) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    return myapp && param ? myapp[param] : '';
  },

  getAskToVerifyReg: function (idapp) {
    return this.getConfSiteOptionEnabledByIdApp(idapp, shared_consts.ConfSite.Need_Aportador_On_DataReg_To_Verify_Reg);
  },

  getidMyGroupBySite: function (idapp) {
    return this.getparamSiteTableByIdApp(idapp, 'idMyGroup');
  },

  isManagAndAdminDifferent(idapp) {
    const manag = this.getManagerEmailByIdApp(idapp);
    return manag !== this.getAdminEmailByIdApp(idapp) && manag !== '';
  },

  getManagerEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) return !!myapp.manageremail ? myapp.manageremail : '';
    else return '';
  },

  getEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    return myapp ? myapp.email_from : '';
  },

  getPwdByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    return myapp ? this.decryptdata(myapp.email_pwd) : '';
  },

  getTelegramBotNameByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development')
      return myapp ? myapp.telegram_bot_name_test : '';
    else return myapp ? myapp.telegram_bot_name : '';
  },

  getTelegramSupportChat: function (idapp) {
    try {
      const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
      return myapp && myapp.telegram_support_chat ? myapp.telegram_support_chat : '';
    } catch (e) {
      return '';
    }
  },
  getLinkGruppiTerritorialiTelegram: function (idapp) {
    try {
      const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
      return myapp && myapp.telegram_gruppi_territoriali_senzainvito
        ? myapp.telegram_gruppi_territoriali_senzainvito
        : '';
    } catch (e) {
      return '';
    }
  },

  getTelegramKeyByIdApp: function (idapp) {
    if (this.MYAPPS.length === 0) {
      console.error('❌ this.MYAPPS VUOTI!!', this.MYAPPS);
    }
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development')
      return myapp ? myapp.telegram_key_test : '';
    else return myapp ? myapp.telegram_key : '';
  },

  getArrTelegramFromSite: function () {
    const myapp = this.MYAPPS;
    let arrteleg = [];
    for (const site of myapp) {
      if (site.active) {
        if (process.env.NODE_ENV === 'test') {
          if (site.telegram_bot_name_test) arrteleg.push(site.idapp);
        } else {
          if (site.telegram_bot_name) arrteleg.push(site.idapp);
        }
      }
    }

    return arrteleg;
  },

  getLookupPipeLine(params, proj) {
    let myquery = [
      {
        $lookup: {
          from: params.lk_tab,
          let: { username: '$' + params.lk_LF, idapp: params.idapp },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [{ $eq: ['$' + params.lk_FF, '$$' + params.lk_FF] }, { $eq: ['$idapp', '$$idapp'] }],
                },
              },
            },
          ],
          as: params.lk_as,
        },
      },
      { $project: proj },
    ];

    return myquery;
  },

  getNumTabByTable(table) {
    const myrec = shared_consts.MYTABS.find((rec) => rec.table === table);
    return myrec ? myrec.id : -1;
  },

  getTableByNumTab(numtab) {
    const myrec = shared_consts.MYTABS.find((rec) => rec.id === numtab);
    return myrec ? myrec.table : '';
  },

  getGroupnameLookupPipeLine(params, proj) {
    let myquery = [
      {
        $lookup: {
          from: params.lk_tab,
          let: { groupname: '$' + params.lk_LF, idapp: params.idapp },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [{ $eq: ['$' + params.lk_FF, '$$' + params.lk_FF] }, { $eq: ['$idapp', '$$idapp'] }],
                },
              },
            },
          ],
          as: params.lk_as,
        },
      },
      { $project: proj },
    ];

    return myquery;
  },

  getLookupStandardPipeline: function (params, num) {
    const query = [];

    if (!params) return;

    query.push(
      {
        $lookup: {
          from: params.lk_tab,
          let: { searchId: { $toObjectId: '$' + params.lk_LF } },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [{ $eq: ['$' + params.lk_FF, '$$searchId'] }],
                },
              },
            },
            {
              $project: params.lk_proj,
            },
          ],
          as: params.lk_as + num,
        },
      },
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              {
                $arrayElemAt: ['$' + params.lk_as + num, 0],
              },
              '$$ROOT',
            ],
          },
        },
      }
    );
    return query;
  },

  sanitizzaHtml(html) {
    try {
      return sanitizeHtml(html);
    } catch (e) {
      return html;
    }
  },

  sanitizzaLookup: function (str) {
    return str;
  },

  sanitizzaProjection: function (mioproj) {
    // mioproj = {valore: '$password'};

    const COL_TO_REMOVE = ['$password', '$tokens', '$tokens.token', 'ipaddr'];

    for (let chiave in mioproj) {
      // Elimina questa richiesta della password !
      if (COL_TO_REMOVE.includes(mioproj[chiave])) {
        delete mioproj[chiave];
      }
    }

    return mioproj;
  },

  getLookup: function (params, num, pass_proj, proj_add) {
    const query = [];

    if (!params) return;

    let mylkLF = params.lk_LF;
    if (params.af_objId_tab) {
      const myobj = {};
      myobj['myId' + num] = { $toObjectId: '$' + params.lk_LF };

      if (params.lk_tab === 'users') {
        myobj.namecomplete = {
          $concat: [{ $toLower: '$name' }, { $toLower: '$surname' }, { $toLower: '$username' }],
        };
      }

      query.push({ $addFields: myobj });
      mylkLF = 'myId' + num;
    } else {
    }

    if (params.lk_tab) {
      let proj = params.lk_proj;
      proj = this.sanitizzaProjection(proj);
      if (!!pass_proj) {
        proj = pass_proj;
      }
      proj = Object.assign({}, proj, proj_add);

      query.push({
        $lookup: {
          from: params.lk_tab,
          localField: mylkLF, // field in my collection
          foreignField: params.lk_FF, // field in the 'from' collection
          as: params.lk_as,
        },
      });
      if (params.unwind) {
        query.push({
          $unwind: {
            path: '$' + params.lk_as,
            preserveNullAndEmptyArrays: true,
          },
        });
      }

      if (!params.noarray) {
        query.push({
          $replaceRoot: {
            newRoot: {
              $mergeObjects: [
                {
                  $arrayElemAt: ['$' + params.lk_as, 0],
                },
                '$$ROOT',
              ],
            },
          },
        });
      }

      query.push({ $project: proj });
    }

    return query;
  },

  getFilterParam: function (filter, fieldsearch) {
    let myregexp = {};
    myregexp = new RegExp(filter.trim().replace(' ', '|'), 'ig');

    let query = [];

    const myfilters = [];
    for (const rec of fieldsearch) {
      let data = {};
      if (rec.type === this.FieldType.exact) {
        data[rec.field] = filter.trim();
      } else if (rec.type === this.FieldType.string) {
        data[rec.field] = myregexp;
        if (rec.field === 'profile.username_telegram') {
          myregexp = new RegExp(this.rimuoviAtPrimoCarattere(filter).trim().replace(' ', '|'), 'ig');
          data[rec.field] = myregexp;
        }
      } else if (rec.type === this.FieldType.number || rec.type === this.FieldType.hours) {
        data[rec.field] = parseFloat(filter.trim());
      }
      if (data) myfilters.push(data);
    }

    if (myfilters.length > 0) {
      query = [{ $match: { $or: myfilters } }];
    }

    return query;
  },

  addQueryIdMatch: function (params) {
    myquery = {};

    if (
      shared_consts.TABLES_ID_NUMBER.includes(params.table) ||
      shared_consts.TABLES_ID_STRING.includes(params.table)
    ) {
      myquery = {
        $match: {
          $expr: {
            $eq: ['$_id', params.myid],
          },
        },
      };
    } else if (shared_consts.TABLES_NUM_AS_ID_NUMBER.includes(params.table)) {
      myquery = {
        $match: {
          $expr: {
            $eq: ['$Num', params.myid],
          },
        },
      };
    } else {
      myquery = {
        $match: {
          $expr: {
            $eq: ['$_id', mongoose.Types.ObjectId(params.myid)],
          },
        },
      };
    }

    return myquery;
  },

  contieneIdStatusSkill(filtersearch) {
    for (let i = 0; i < filtersearch.length; i++) {
      const obj = filtersearch[i];
      if (obj.$or) {
        for (let j = 0; j < obj.$or.length; j++) {
          const condition = obj.$or[j];
          if (condition.idStatusSkill) {
            return true;
          }
        }
      }
    }
    return false;
  },

  addNumFavoriteAndBookmarkToQuery(idapp, numtab) {
    let query = [];

    try {
      /*
      query =
        [
          {
            $lookup: {
              from: "reactions",
              let: {
                tab: numtab,
                id: '$_id',
              },
              pipeline: [
                {
                  $match: {
                    $expr: {
                      $and: [
                        { $eq: ['$idrec', '$$id'] },
                        { $eq: ['$tab', numtab] },
                        { $eq: ['$idapp', idapp] },
                      ],
                    },
                  },
                },
                {
                  $group: {
                    _id: "$idrec",
                    numseen: {
                      $sum: {
                        $cond: {
                          if: { $ifNull: ["$seen", false] }, // Check if the field exists and is not null
                          then: 1, // Increment count by 1 if the field exists
                          else: 0, // Otherwise, keep the count unchanged
                        }
                      }
                    },
                    numfav: {
                      $sum: {
                        $cond: {
                          if: { $ifNull: ["$fav", false] }, // Check if the field exists and is not null
                          then: 1, // Increment count by 1 if the field exists
                          else: 0, // Otherwise, keep the count unchanged
                        }
                      }
                    },
                    numbook: {
                      $sum: {
                        $cond: {
                          if: { $ifNull: ["$book", false] }, // Check if the field exists and is not null
                          then: 1, // Increment count by 1 if the field exists
                          else: 0, // Otherwise, keep the count unchanged
                        }
                      }
                    }
                  },
                },
              ],
              as: 'myreact',
            },
          },

          {
            $unwind: {
              path: "$myreact",
              preserveNullAndEmptyArrays: true,
            },
          },
       
        ];

      if (true) {
        // RIMUOVI
        // query = {};
      }

      if (true) {
        // RIMUOVI
        // query = {};
      }

      if (Object.keys(query).length > 0) {
        const numtabbacheca = this.getNumTabByTable(shared_consts.TABLES_MYBACHECAS);
        if (numtab === numtabbacheca) {
          const queryadd = this.getQueryMyBacheca(idapp);
          query = [...query, ...queryadd];
        }
      }
      

      const numtabbacheca = this.getNumTabByTable(shared_consts.TABLES_MYBACHECAS);
      if (numtab === numtabbacheca) {
        const queryadd = this.getQueryMyBacheca(idapp);
        query = [...query, ...queryadd];
      }
      */

      proj = {
        numseen: 1,
        numfav: 1,
        numbook: 1,
        numattend: 1,
      };
    } catch (e) {
      console.error(e);
    }

    return { query, proj };
  },

  getQueryMyBacheca: function (idapp) {
    const arrquery = [
      {
        $lookup: {
          from: 'bookings',
          let: {
            id: '$_id',
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [{ $eq: ['$id_bookedevent', '$$id'] }, { $eq: ['$idapp', idapp] }],
                },
              },
            },
            {
              $project: {
                id_bookedevent: 1,
                username: 1,
                numpeople: 1,
                numpeopleLunch: 1,
                numpeopleDinner: 1,
                numpeopleDinnerShared: 1,
                infoevent: 1,
                msgbooking: 1,
                booked: 1,
                datebooked: 1,
                userId: 1,
                idapp: 1,
              },
            },
          ],
          as: 'mybookings',
        },
      },
      /**
      {
        $lookup: {
          from: "users",
          localField: "myId1",
          foreignField: "_id",
          as: "user",
        },
      },
      {
        $lookup: {
          from: "users",
          let: {
            myid: { $toObjectId: "$mybookings.userId" },
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    {
                      $eq: [
                        "$_id",
                        "$$myid",
                      ],
                    },
                    {
                      $eq: [
                        "$idapp",
                        "13",
                      ],
                    },
                  ],
                },
              },
            },
            {
              $project: { _id: 1, username: 1 },
            }
          ],
  
          as: "myuser",
        }, 
        }**/
    ];

    return arrquery;
  },

  getQueryTable: async function (idapp, params, user) {
    try {
      const { Search } = require('../models/search');

      if (typeof params.startRow !== 'number') {
        throw new Error('startRow must be number');
      } else if (typeof params.endRow !== 'number') {
        throw new Error('endRow must be number');
      }

      let newvers = !!params.newvers;

      let query = [];

      if (params.filter && params.fieldsearch) {
        params.filter = this.sanitizzaHtml(params.filter);
        params.fieldsearch = this.sanitizzaHtml(params.fieldsearch);
        const querytemp = this.getFilterParam(params.filter, params.fieldsearch);
        if (querytemp) {
          query = [...query, ...querytemp];
        }
      }

      let filtriadded = [];

      // if (params.table === 'extralist') {
      //   if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_DELETED))
      //     filtriadded.push({ deleted: true });
      //   else
      //     filtriadded.push({ deleted: { $exists: false } });
      // }

      if (params.filterand) {
        params.filterand = this.sanitizzaHtml(params.filterand);

        if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_NOT_REGISTERED))
          filtriadded.push({ registered: false });

        if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_NOT_CONTACTED)) {
          filtriadded.push({ contacted: { $exists: false } });
        }
        if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_WITH_NOTE))
          filtriadded.push({
            note: { $exists: true },
            $expr: { $gt: [{ $strLenCP: '$note' }, 1] },
          });
        if (params.filterand.includes(shared_consts.FILTER_QUALIFIED)) filtriadded.push({ 'profile.qualified': true });
        if (params.filterand.includes(shared_consts.FILTER_USER_NO_ZOOM))
          filtriadded.push({ 'profile.saw_zoom_presentation': false });
        if (params.filterand.includes(shared_consts.FILTER_ASK_ZOOM_VISTO))
          filtriadded.push({ 'profile.ask_zoom_partecipato': false });
        if (params.filterand.includes(shared_consts.FILTER_USER_NO_INVITANTE))
          filtriadded.push({
            aportador_solidario: { $exists: false },
          });
        if (params.filterand.includes(shared_consts.FILTER_USER_NO_TELEGRAM_ID))
          filtriadded.push({ 'profile.teleg_id': { $lt: 1 } });
        if (params.filterand.includes(shared_consts.FILTER_USER_SI_TELEGRAM_ID))
          filtriadded.push({ 'profile.teleg_id': { $gt: 1 } });
        if (params.filterand.includes(shared_consts.FILTER_USER_CODICE_AUTH_TELEGRAM))
          filtriadded.push({ 'profile.teleg_checkcode': { $gt: 1 } });
        if (params.filterand.includes(shared_consts.FILTER_USER_NO_EMAIL_VERIFICATA))
          filtriadded.push({ verified_email: false });
        if (params.filterand.includes(shared_consts.FILTER_USER_NO_VERIFIED_APORTADOR))
          filtriadded.push({
            verified_by_aportador: { $exists: false },
          });
        if (params.filterand.includes(shared_consts.FILTER_USER_WITHOUT_USERNAME_TELEGRAM))
          filtriadded.push({
            $or: [
              { 'profile.username_telegram': { $exists: false } },
              { 'profile.username_telegram': { $exists: true, $eq: '' } },
            ],
          });
        if (params.filterand.includes(shared_consts.FILTER_USER_NO_DREAM))
          filtriadded.push({
            'profile.my_dream': {
              $exists: false,
            },
          });
        if (params.filterand.includes(shared_consts.FILTER_USER_TELEGRAM_BLOCKED))
          filtriadded.push({ 'profile.teleg_id_old': { $gt: 1 } });
        if (params.filterand.includes(shared_consts.FILTER_USER_PROVINCE))
          filtriadded.push({ 'profile.resid_province': { $exists: true } });
        if (params.filterand.includes(shared_consts.FILTER_USER_COMUNE))
          filtriadded.push({ 'profile.resid_str_comune': { $exists: true } });
        if (params.filterand.includes(shared_consts.FILTER_USER_SENZA_PROVINCE))
          filtriadded.push({ 'profile.resid_province': { $exists: false } });
        if (params.filterand.includes(shared_consts.FILTER_ATTIVI))
          filtriadded.push({
            $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
          });

        // Hours
        if (params.filterand.includes(shared_consts.FILTER_HOURS_ALL))
          filtriadded.push({
            idapp,
            todoId: params.codeId,
            $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
          });
        else if (params.filterand.includes(shared_consts.FILTER_HOURS_MYLIST))
          filtriadded.push({
            idapp,
            userId: params.userId,
            todoId: params.codeId,
            $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
          });

        if (params.filterand.includes(shared_consts.FILTER_NASCOSTI))
          filtriadded.push({
            deleted: { $exists: true, $eq: true },
          });
        if (params.filterand.includes(shared_consts.FILTER_NAVI_NON_PRESENTI))
          filtriadded.push({
            navinonpresenti: { $exists: true, $eq: true },
            $or: [{ subaccount: { $exists: false } }, { subaccount: { $exists: true, $eq: false } }],
          });

        // Iscritti Conacreis
        if (params.filterand.includes(shared_consts.FILTER_REPORTED))
          filtriadded.push({ reported: { $exists: true, $eq: true } });

        if (params.filterand.includes(shared_consts.FILTER_TO_MAKE_MEMBERSHIP_CARD))
          filtriadded.push({
            $or: [{ codiceConacreis: { $exists: false } }, { codiceConacreis: { $exists: true, $eq: '' } }],
          });

        if (params.filterand.includes(shared_consts.FILTER_MEMBERSHIP_CARD_OK))
          filtriadded.push({
            codiceConacreis: { $exists: true },
            $expr: { $gt: [{ $strLenCP: '$codiceConacreis' }, 0] },
          });
      }

      if (params.filtercustom) {
        params.filtercustom = this.sanitizzaHtml(params.filtercustom);
        let condition = {};
        for (const myfilter of params.filtercustom) {
          if (myfilter['userId']) {
            myfilter['userId'] = new ObjectId(myfilter['userId']);
          }
          if (myfilter['_idOBJ']) {
            filtriadded.push({ _id: new ObjectId(myfilter['_idOBJ']) });
          } else if (myfilter['dateTimeStart']) {
            const gte = myfilter['dateTimeStart'].$gte;
            const lte = myfilter['dateTimeStart'].$lte;
            condition = {
              dateTimeStart: {
                $gte: new Date(gte),
                $lte: new Date(lte),
              },
            };
            filtriadded.push(condition);
          } else if (myfilter.hasOwnProperty('pub_to_share')) {
            // non aggiungere niente
          } else {
            filtriadded.push(myfilter);
          }
        }
      }

      if (params.filter_gte) {
        params.filter_gte = this.sanitizzaHtml(params.filter_gte);
        for (let ind = 0; ind < params.filter_gte.length; ind++) {
          for (const [key, value] of Object.entries(params.filter_gte[ind])) {
            if (value > 0) {
              let condition = {};
              condition[key] = { $gte: value };
              filtriadded.push(condition);
            }
          }
        }
      }

      if (params.filtersearch) {
        params.filtersearch = this.sanitizzaHtml(params.filtersearch);
        filtriadded.push(...params.filtersearch);
      }

      if (params.options) {
        if (this.isBitActive(params.options, shared_consts.OPTIONS_SEARCH_ONLY_FULL_WORDS)) {
        }
      }

      if (params.filterextra) {
        params.filterextra = this.sanitizzaHtml(params.filterextra);
        if (params.filterextra.length > 0) query = [...query, ...params.filterextra];
      }

      if (filtriadded) {
        filtriadded = this.sanitizzaHtml(filtriadded);
        if (filtriadded.length > 0) query.push({ $match: { $and: filtriadded } });
      }

      let numrowend = params.endRow - params.startRow;
      if (numrowend < 0) numrowend = 1;

      let projectEnd = { $slice: ['$results', params.startRow, numrowend] };

      let querymatch = {};

      if (idapp > 0) {
        querymatch.idapp = idapp;
      }

      if (params.searchByBoundariesMap) {
        projectEnd = '$results';

        querymatch.coordinate_gps = {
          $geoWithin: {
            $box: [
              [params.searchByBoundariesMap.sw.lng, params.searchByBoundariesMap.sw.lat],
              [params.searchByBoundariesMap.ne.lng, params.searchByBoundariesMap.ne.lat],
            ],
          },
        };
      }

      if (Object.keys(querymatch).length > 0) {
        query.push({ $match: querymatch });
      }

      // console.log('QUERYMATCH', query[0].$match.or);
      // console.log('filter', params.filter);

      if (
        params.querytype === shared_consts.QUERYTYPE_MYGROUP ||
        params.querytype === shared_consts.QUERYTYPE_CIRCUIT
      ) {
        const myq = this.addQueryIdMatch(params);
        if (myq) query.push(myq);

        query.push({ $project: { req_users: 1 } });
        const qa1 = this.getLookup(
          {
            lk_tab: 'users',
            lk_LF: 'req_users.username',
            lk_FF: 'username',
            lk_as: 'user',
          },
          0,
          {
            'user.idapp': 1,
            'user.username': 1,
            'user.name': 1,
            'user.surname': 1,
            'user.profile.img': 1,
            'user.profile.qualifica': 1,
            'user.profile.note': 1,
            'profile.da_contattare': 1,
          }
        );
        if (qa1) query = [...query, ...qa1];
        query.push({ $unwind: '$user' });

        query.push({
          $match: {
            $and: [{ 'user.idapp': params.idapp }],
          },
        });

        query.push({
          $replaceRoot: {
            newRoot: '$user',
          },
        });
      } else if (
        params.querytype === shared_consts.QUERYTYPE_REFUSED_USER_GRP ||
        params.querytype === shared_consts.QUERYTYPE_REFUSED_USER_CIRCUIT
      ) {
        /* 
        const myq = this.addQueryIdMatch(params);
        if (myq)
          query.push(myq);
        */

        const myq = this.addQueryIdMatch(params);
        if (myq) query.push(myq);

        query.push({ $project: { refused_users: 1 } });
        query.push({ $unwind: '$refused_users' });
        const qa1 = this.getLookupPipeLine(
          {
            lk_tab: 'users',
            lk_LF: 'refused_users.username',
            lk_FF: 'username',
            lk_as: 'user',
            idapp,
          },
          {
            'user.idapp': 1,
            'user.username': 1,
            'user.name': 1,
            'user.surname': 1,
            'user.profile.img': 1,
            'user.profile.qualifica': 1,
            'user.profile.note': 1,
            'profile.da_contattare': 1,
          }
        );
        if (qa1) query = [...query, ...qa1];
        query.push({ $unwind: '$user' });
        /*
        query.push({
          $match: {
            $and: [
              {'user.idapp': params.idapp},
            ],
          },
        });
        */

        query.push({
          $replaceRoot: {
            newRoot: '$user',
          },
        });
      } else if (params.querytype === shared_consts.QUERYTYPE_GROUP_CIRCUIT) {
        const myq = this.addQueryIdMatch(params);
        if (myq) query.push(myq);

        query.push({ $project: { req_groups: 1 } });
        const qa1 = this.getLookup(
          {
            lk_tab: 'mygroups',
            lk_LF: 'req_groups.groupname',
            lk_FF: 'groupname',
            lk_as: 'group',
          },
          0,
          {
            'group.idapp': 1,
            'group.groupname': 1,
            'group.photos': 1,
          }
        );
        if (qa1) query = [...query, ...qa1];
        query.push({ $unwind: '$group' });

        if (false) {
          //##++Todo: Query per i limiti GPS!
          query.push({
            $match: {
              $and: [{ 'group.idapp': params.idapp }],
              coordinate_gps: {
                $geoWithin: {
                  $box: [
                    [searchByBoudariesMap.sw.lng, searchByBoudariesMap.sw.lat],
                    [searchByBoudariesMap.ne.lng, searchByBoudariesMap.ne.lat],
                  ],
                },
              },
            },
          });
        } else {
          query.push({
            $match: {
              $and: [{ 'group.idapp': params.idapp }],
            },
          });
        }

        query.push({
          $replaceRoot: {
            newRoot: '$group',
          },
        });
      } else if (params.querytype === shared_consts.QUERYTYPE_REFUSED_GROUP_CIRCUIT) {
        const myq = this.addQueryIdMatch(params);
        if (myq) query.push(myq);

        query.push({ $project: { refused_groups: 1 } });
        query.push({ $unwind: '$refused_groups' });
        const qa1 = this.getGroupnameLookupPipeLine(
          {
            lk_tab: 'mygroups',
            lk_LF: 'refused_groups.groupname',
            lk_FF: 'groupname',
            lk_as: 'group',
            idapp,
          },
          {
            'group.idapp': 1,
            'group.groupname': 1,
            'group.descr': 1,
            'group.photos': 1,
          }
        );
        if (qa1) query = [...query, ...qa1];
        query.push({ $unwind: '$group' });
        /*
        query.push({
          $match: {
            $and: [
              {'user.idapp': params.idapp},
            ],
          },
        });
        */

        query.push({
          $replaceRoot: {
            newRoot: '$group',
          },
        });
      } else if (params.querytype === shared_consts.QUERYTYPE_LIST_MOVEMENTS) {
        const { Movement } = require('../models/movement');

        const myquery = await Movement.getQueryMovsByCircuitId(params.idapp, params.username, '', '', params.myid);

        query.push(...myquery);
      } else if (params.querytype === shared_consts.QUERYTYPE_LIST_MOVEMENTS_GROUPNAME) {
        const { Movement } = require('../models/movement');

        const myquery = await Movement.getQueryMovsByCircuitId(params.idapp, '', params.groupname, '', params.myid);

        query.push(...myquery);
      } else if (params.querytype === shared_consts.QUERYTYPE_LIST_MOVEMENTS_CONTOCOM) {
        const { Movement } = require('../models/movement');

        const myquery = await Movement.getQueryMovsByCircuitId(params.idapp, '', '', params.contocom, params.myid);

        query.push(...myquery);
      } else if (params.querytype === shared_consts.QUERYTYPE_LIST_ALLMOVEMENTS) {
        const { Movement } = require('../models/movement');

        const myquery = await Movement.getQueryAllUsersMovsByCircuitId(params.idapp, params.myid);

        query.push(...myquery);
      }

      if (params.filterextra2) {
        if (params.filterextra2.length > 0) query = [...query, ...params.filterextra2];
      }

      if (newvers) {
        // NUOVA VERSIONE
        let proj = params.lookup2 ? params.lookup2.lk_proj : null;

        proj = this.sanitizzaProjection(proj);

        if (params.proj0) {
          query.push({ $project: params.proj0 });
        }

        if (params.unwind0) {
          query.push({ $unwind: params.unwind0 });
        }

        let objadd = { query: [], proj: {} };

        let numtab = this.getNumTabByTable(params.table);

        const q1 = this.getLookup(params.lookup1, 1, proj, objadd.proj);
        if (q1) query = [...query, ...q1];

        if (params.unwind1) {
          query.push({ $unwind: params.unwind1 });
        }

        if (params.lookupPipeline1) {
          const q1p = this.getLookupStandardPipeline(params.lookupPipeline1, 1);
          if (q1p) query = [...query, ...q1p];
        }

        params.lookup2 = this.sanitizzaLookup(params.lookup2);
        params.lookup3 = this.sanitizzaLookup(params.lookup3);
        params.lookup4 = this.sanitizzaLookup(params.lookup4);
        params.lookup5 = this.sanitizzaLookup(params.lookup5);

        const q2 = this.getLookup(params.lookup2, 2, proj, objadd.proj);
        if (q2) query = [...query, ...q2];

        if (params.lookupPipeline2) {
          const q2p = this.getLookupStandardPipeline(params.lookupPipeline2, 2);
          if (q2p) query = [...query, ...q2p];
        }

        const q3 = this.getLookup(params.lookup3, 3, proj, objadd.proj);
        if (q3) query = [...query, ...q3];

        const q4 = this.getLookup(params.lookup4, 4, proj, objadd.proj);
        if (q4) query = [...query, ...q4];

        const q5 = this.getLookup(params.lookup5, 5, proj, objadd.proj);
        if (q5) query = [...query, ...q5];

        if (params.filtersearch2) {
          if (params.filtersearch2.length > 0) {
            query.push({ $match: { $and: params.filtersearch2 } });
          }
        }

        let filteradmin = false;
        if (params.filtercustom) {
          let condition = {};
          for (const myfilter of params.filtercustom) {
            if (!!myfilter.admins) {
              filteradmin = true;
            }

            if (
              myfilter['pub_to_share'] === shared_consts.PUBTOSHARE.ONLY_TABLE_FOLLOW &&
              params.table === 'mygroups'
            ) {
              let arraygroups = [];

              if (user && user.profile.mygroups) {
                arraygroups = user.profile.mygroups.map((rec) => rec.groupname);
              }
              // prendere i gruppi dell'utente

              // Cerca tra i gruppi di ogni record, se combaciano almeno 1
              condition = {
                'profile.mygroups': {
                  $elemMatch: {
                    groupname: { $in: arraygroups },
                  },
                },
              };

              query.push({ $match: { $and: [condition] } });
            } else if (
              myfilter['pub_to_share'] === shared_consts.PUBTOSHARE.ONLY_TABLE_FOLLOW &&
              params.table === 'circuits'
            ) {
              let arraycircuits = [];

              if (user && user.profile.mycircuits) {
                arraycircuits = user.profile.mycircuits.map((rec) => rec.circuitname);
              }
              // prendere i gruppi dell'utente

              // Cerca tra i gruppi di ogni record, se combaciano almeno 1
              condition = {
                'profile.mycircuits': {
                  $elemMatch: {
                    circuitname: { $in: arraycircuits },
                  },
                },
              };

              query.push({ $match: { $and: [condition] } });
            } else if (shared_consts.TABLES_ENABLE_GETREC_BYID.includes(params.table)) {
              // Rimuovi dalla query quelli non visibili

              let arraygroups = [];

              if (user && user.profile.mygroups) {
                arraygroups = user.profile.mygroups.map((rec) => rec.groupname);
              }
              // prendere i gruppi dell'utente

              // Cerca tra i gruppi di ogni record, se combaciano almeno 1
              condition = {
                'profile.mygroups': {
                  $elemMatch: {
                    groupname: { $in: arraygroups },
                  },
                },
              };

              query.push({
                $match: {
                  $or: [
                    {
                      $and: [condition, { pub_to_share: 1 }],
                    },
                    {
                      $or: [
                        { pub_to_share: { $exists: false } },
                        { pub_to_share: { $exists: true, $eq: shared_consts.PUBTOSHARE.ALL } },
                      ],
                    },
                  ],
                },
              });
            }
          }
        }

        if (params.table === shared_consts.TABLES_MYBACHECAS) {
          const myq = this.getQueryMyBacheca(idapp);
          query.push(myq[0]);
        }

        if (params.filtersearch3or) {
          if (params.filtersearch3or.length > 0) {
            query.push({ $match: { $or: params.filtersearch3or } });
          }
        }

        if (params.filtersearch3and) {
          if (params.filtersearch3and.length > 0) {
            // Se c'è statusskill allora glielo metto in OR

            if (
              params.table === 'myskills' &&
              params.filtersearch &&
              !this.contieneIdStatusSkill(params.filtersearch)
            ) {
              query.push({
                $match: {
                  $or: [{ $and: params.filtersearch3and }, { idStatusSkill: { $in: [2] } }],
                },
              });
            } else {
              query.push({ $match: { $and: params.filtersearch3and } });
            }
          }
        }

        if (params.filter && params.fieldsearch_last) {
          const querytemp = this.getFilterParam(params.filter, params.fieldsearch_last);
          if (querytemp) {
            query = [...query, ...querytemp];
          }

          // Save the search:
          if (user._id) {
            const mysearch = new Search({ idapp, userId: user._id, text: params.filter });
            await mysearch.save();
          }
        }

        if (params.table === 'mygroups' || params.table === 'circuits') {
          // BINARY CHECK (?): const filter = [{ visibility: { $bitsAnyClear: [1] } }];
          // if (!User.isAdmin(req.user.perm)) {
          // not Visibility_Group.HIDDEN
          if (!filteradmin) {
            if (user) {
              const filter = [
                {
                  $or: [
                    {
                      visibility: {
                        $nin: [shared_consts.Visibility_Group.HIDDEN],
                      },
                    },
                    {
                      createdBy: {
                        $eq: user.username,
                      },
                    },
                  ],
                },
              ];
              query.push({ $match: { $and: filter } });
            } else {
              const filter = [
                {
                  visibility: {
                    $nin: [shared_consts.Visibility_Group.HIDDEN],
                  },
                },
              ];
              query.push({ $match: { $and: filter } });
            }
          }
          // }
        }

        if (params.options && this.isBitActive(params.options, shared_consts.OPTIONS_ADD_COUNT_FAVORITE)) {
          // objadd = this.addNumFavoriteAndBookmarkToQuery(idapp, numtab);
          // if (Object.keys(objadd.query).length > 0)
          //   query = [...query, ...objadd.query];
        }
      } else {
        // VECCHIA VERSIONE
        const q1 = this.getLookup(params, 1);
        if (q1) query = [...query, ...q1];
      }

      if (params.sortBy && !params.searchByBoundariesMap) {
        // maybe we want to sort by blog title or something
        const mysort = params.sortBy && Object.keys(params.sortBy).length > 0 ? { $sort: params.sortBy } : {};
        // console.log('sortBy', params.sortBy);
        // console.table(mysort);
        if (mysort.$sort) {
          query.push(mysort);
        }
      }

      query.push(
        {
          $group: {
            _id: null,
            // get a count of every result that matches until now
            count: { $sum: 1 },
            // keep our results for the next operation
            results: { $push: '$$ROOT' },
          },
        }
        // and finally trim the results to within the range given by start/endRow
      );
      if (projectEnd) {
        query.push({
          $project: {
            count: 1,
            rows: projectEnd,
          },
        });
      }

      if (this.testing()) {
        // console.log('query', query);
      }
      // console.log('query', query);

      return query;
    } catch (e) {
      console.error('Err query:', e);
    }
  },

  startTimeLog(name) {
    if (this.testing()) {
      // console.log('inizio', name);
      try {
        console.time(name);
      } catch (e) {}
    }
  },

  endTimeLog(name) {
    if (this.testing()) {
      // console.log('   ... fine', name);
      console.timeEnd(name);
    }
  },

  async executeQueryTable(mythistable, idapp, params, user) {
    let query = await this.getQueryTable(idapp, params, user);

    try {
      // console.log('query', query);
      this.startTimeLog('Query 1');

      const [ris] = await mythistable.aggregate(query);

      if (ris) {
        if (params.table === 'users') {
          for (const myrec of ris.rows) {
            if (myrec.ipaddr) myrec.ipaddr = '';
            if (myrec.tokens) myrec.tokens = [];
            if (myrec.password) myrec.password = '';
          }
        } else if (params.table === 'mygroups') {
          for (const myrec of ris.rows) {
            if (myrec.pwd_cryp) myrec.pwd_cryp = '';
          }
        }

        this.endTimeLog('Query 1');

        // console.table(ris.rows);
        // console.log('ROW ', ris.count);
        return { count: ris.count, rows: ris.rows };
      } else {
        return { count: 0, rows: [] };
      }
    } catch (e) {
      console.log('error', e);
    }
  },
  async DuplicateAllRecords(mythistable, idapporig, idappdest) {
    console.log(mythistable.name);

    const numrec = await mythistable.findAllIdApp(idappdest).then((arrrec) => {
      if (arrrec && arrrec.length > 1) {
        return arrrec.length; // Has Already set
      } else {
        return 0;
      }
    });

    // Before check if exist more than 1 record in the destination,
    // if Yes don't copy
    if (numrec <= 0) {
      return await mythistable.findAllIdApp(idapporig).then(async (arrrec) => {
        let num = 0;

        for (let ind = 0; ind < arrrec.length; ind++) {
          let newrec = new mythistable(arrrec[ind]);
          newrec._id = new ObjectId();
          newrec.idapp = idappdest;

          try {
            const rec = await newrec.save();
            if (rec) {
              num++;
            }
          } catch (e) {
            console.error('e', e);
          }
        }
        return num;
      });
    }
  },

  isBitActive(bit, whattofind) {
    return (bit & whattofind) === whattofind;
  },

  isBitAttivoESelez(bit, whattofind, loSono) {
    const abil = this.isBitActive(bit, whattofind);

    return (abil && loSono) || !abil;
  },

  SetBit(myval, bit) {
    let myvalout = myval;
    myvalout |= bit;
    return myvalout;
  },

  async snooze(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  },

  IncDateNow(secs) {
    let mydate = new Date(new Date().getTime() + secs);
    // console.log('mydate', mydate);
    return mydate;
  },

  isdiffSecDateLess(mydatediffstr, secs) {
    let mydate = new Date();
    // console.log('mydate', mydate);
    let mydata2 = new Date(mydatediffstr);
    let ris = (mydate.getTime() - mydata2.getTime()) / 1000;
    return ris < secs;
  },

  AddDate(mydate, days) {
    let date = new Date(mydate);
    date.setTime(date.getTime() + days * 86400000);
    return date;
  },

  isMonToSat() {
    const dayOfWeek = new Date(new Date()).getDay();
    return dayOfWeek >= 1 && dayOfWeek <= 6;
  },

  isMonToFri() {
    const dayOfWeek = new Date(new Date()).getDay();
    return dayOfWeek >= 1 && dayOfWeek <= 5;
  },

  // var startTime = '15:10:10';
  // var endTime = '22:30:00';
  isBetweenTwoTime(startTime, endTime) {
    currentDate = new Date();

    startDate = new Date(currentDate.getTime());
    startDate.setHours(startTime.split(':')[0]);
    startDate.setMinutes(startTime.split(':')[1]);
    startDate.setSeconds(startTime.split(':')[2]);

    endDate = new Date(currentDate.getTime());
    endDate.setHours(endTime.split(':')[0]);
    endDate.setMinutes(endTime.split(':')[1]);
    endDate.setSeconds(endTime.split(':')[2]);

    valid = startDate < currentDate && endDate > currentDate;

    return valid;
  },

  getlimiti(mypos) {
    if (mypos.col < 0) {
      mypos.col = 0;
    }

    if (mypos.riga < 0) {
      mypos.riga = 0;
      mypos.col = 0;
    }

    return mypos;
  },

  getRigaColByPosUp(mypos) {
    if (mypos.numup > 0) {
      mypos.riga = mypos.riga - mypos.numup;
      mypos.col = Math.ceil(mypos.col / Math.pow(2, mypos.numup));
    }
    if (mypos.numup < 0) {
      mypos.riga = mypos.riga - mypos.numup;
      mypos.col = Math.ceil(mypos.col * Math.pow(2, -mypos.numup));
    }
    if (mypos.riga < 1) {
      mypos.riga = 1;
    }
    if (mypos.col < 1) {
      mypos.col = 1;
    }
  },

  getRigaColGenerica(idapp, riga, col, numup) {
    mypos = {
      idapp,
      riga,
      col,
      numup,
    };

    if (idapp === this.AYNI) {
      this.getRigaColByPosUp(mypos);
      ris = this.getlimiti(mypos);
    }

    return ris;
  },

  getRigaColSognatoreByDonatore(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 6);
  },

  getRigaColMediatoreByFuoco(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 3);
  },

  getRigaColSognatoreByMediatore(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 3);
  },

  appendLeadingZeroes(n) {
    if (n <= 9) {
      return '0' + n;
    }
    return n;
  },

  getWeekDayByLangByNumCar(date, lang, num) {
    if (!lang) lang = 'it';

    const dayOfWeek = new Date(date).getDay();

    const myday = {
      it: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
      enUs: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
      fr: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
      es: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'iernes', 'Sábado'],
      pt: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'],
      de: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
      si: ['Nedelja', 'Ponedeljek', 'Torek', 'Sreda', 'četrtek', 'Petek', 'Sobota'],
    };

    if (num > 0) {
      return isNaN(dayOfWeek) ? '' : myday[lang][dayOfWeek].substring(0, num);
    } else {
      return isNaN(dayOfWeek) ? '' : myday[lang][dayOfWeek];
    }
  },

  isSunday(mydate) {
    const dayOfWeek = new Date(mydate).getDay();
    return dayOfWeek === 0;
  },

  isMonday(mydate) {
    const dayOfWeek = new Date(mydate).getDay();
    return dayOfWeek === 1;
  },

  isSundayDate(mydate) {
    const dayOfWeek = mydate.getDay();
    return dayOfWeek === 0;
  },

  isMondayDate(mydate) {
    const dayOfWeek = mydate.getDay();
    return dayOfWeek === 1;
  },

  getWeekDayByLang(date, lang) {
    return this.getWeekDayByLangByNumCar(date, lang, 3);
  },

  getWeekDayByLangTot(date, lang) {
    return this.getWeekDayByLangByNumCar(date, lang, 0);
  },

  getWeekDay(date) {
    //Create an array containing each day, starting with Sunday.
    const weekdays = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedí', 'Venerdì', 'Sabato'];
    //Use the getDay() method to get the day.
    const day = date.getDay();
    //Return the element that corresponds to that index.
    return weekdays[day];
  },

  getstrDateTimeShort(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        this.getWeekDayByLang(mydate, lang) +
        ' ' +
        this.appendLeadingZeroes(mydate.getDate()) +
        '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) +
        ' ORE ' +
        this.appendLeadingZeroes(mydate.getHours()) +
        ':' +
        this.appendLeadingZeroes(mydate.getMinutes())
      );
    } else {
      return '';
    }
  },

  getstrTime(mytimestamp) {
    // Create a Date object with your desired date
    const date = new Date(mytimestamp); // You can replace this with your specific date

    // Get the hour and minute components from the date
    const hours = date.getHours();
    const minutes = date.getMinutes();

    // Format the hour and minute components as HH:MM
    const formattedTime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
    return formattedTime;
  },

  getstrDateShort(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        this.getWeekDayByLang(mydate, lang).substring(0, 3) +
        ' ' +
        this.appendLeadingZeroes(mydate.getDate()) +
        '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1)
      );
    } else {
      return '';
    }
  },

  getstrDate_DD_MM_YYYY(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        this.appendLeadingZeroes(mydate.getDate()) +
        '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) +
        '/' +
        this.appendLeadingZeroes(mydate.getFullYear())
      );
    } else {
      return '';
    }
  },

  getstrDateYYYY_MM_DD(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        mydate.getFullYear() +
        '-' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) +
        '-' +
        this.appendLeadingZeroes(mydate.getDate())
      );
    } else {
      return '';
    }
  },

  getstrUTCDateYYYY_MM_DD(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        mydate.getUTCFullYear() +
        '-' +
        this.appendLeadingZeroes(mydate.getUTCMonth() + 1) +
        '-' +
        this.appendLeadingZeroes(mydate.getUTCDate())
      );
    } else {
      return '';
    }
  },

  getstrDateLong(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        this.getWeekDayByLang(mydate, lang) +
        ' ' +
        this.appendLeadingZeroes(mydate.getDate()) +
        '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) +
        '/' +
        mydate.getFullYear()
      );
    } else {
      return '';
    }
  },

  getstrDateLongTot(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        this.getWeekDayByLangTot(mydate, lang) +
        ' ' +
        this.appendLeadingZeroes(mydate.getDate()) +
        '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) +
        '/' +
        mydate.getFullYear()
      );
    } else {
      return '';
    }
  },

  getstrDateLongFile(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return (
        mydate.getFullYear() +
        '-' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) +
        '-' +
        this.appendLeadingZeroes(mydate.getDate()) +
        ' (' +
        this.getWeekDayByLang(mydate, lang) +
        ')'
      );
    } else {
      return '';
    }
  },

  getlinkzoom(rec) {
    if (rec === null) {
      rec = {
        typeconf: this.TYPECONF_ZOOM,
        id_conf_zoom: '',
      };
    }
    let typeconf = rec.typeconf;
    if (typeconf === '') typeconf = this.TYPECONF_ZOOM;

    let mylink = 'https://zoom.us/j/';
    if (typeconf === this.TYPECONF_JITSI) mylink = 'https://meet.jit.si/';

    if (rec.id_conf_zoom === '') {
      rec.id_conf_zoom = '6668882000';
    }

    return mylink + rec.id_conf_zoom;
  },

  getmd5(mystr) {
    return CryptoJS.MD5(mystr ? mystr.toLowerCase() : '').toString();
  },

  getHash(mystr) {
    return CryptoJS.SHA512(mystr, { outputLength: 256 }).toString();
  },

  encrypt(msg, pass) {
    try {
      // Genera salt e IV casuali
      const salt = CryptoJS.lib.WordArray.random(128 / 8); // 16 byte
      const iv = CryptoJS.lib.WordArray.random(128 / 8); // 16 byte

      // Deriva la chiave usando PBKDF2
      const key = CryptoJS.PBKDF2(pass, salt, {
        keySize: keySize / 32,
        iterations: iterations,
      });

      // Critta il messaggio
      const encrypted = CryptoJS.AES.encrypt(msg, key, {
        iv: iv,
        padding: CryptoJS.pad.Pkcs7,
        mode: CryptoJS.mode.CBC,
      });

      // Concatena salt, IV e ciphertext con un delimitatore
      const transitmessage = `${salt.toString()}|${iv.toString()}|${encrypted.toString()}`;
      return transitmessage;
    } catch (error) {
      console.error('❌❌❌ Errore durante la crittazione:', error.message);
      throw error;
    }
  },

  decrypt(transitmessage, pass) {
    try {
      let saltHex, ivHex, encrypted;

      // Controlla se il messaggio è nel nuovo formato (con delimitatore)
      if (transitmessage.includes('|')) {
        const parts = transitmessage.split('|');
        if (parts.length !== 3) {
          throw new Error('❌❌❌ Formato del messaggio non valido.');
        }
        [saltHex, ivHex, encrypted] = parts;
      } else {
        // Vecchio formato: salt (32 caratteri), IV (32 caratteri), ciphertext (resto)
        saltHex = transitmessage.substr(0, 32);
        ivHex = transitmessage.substr(32, 32);
        encrypted = transitmessage.substring(64);
      }

      // Converte salt e IV da esadecimale
      const salt = CryptoJS.enc.Hex.parse(saltHex);
      const iv = CryptoJS.enc.Hex.parse(ivHex);

      // Deriva la chiave usando PBKDF2
      const key = CryptoJS.PBKDF2(pass, salt, {
        keySize: keySize / 32,
        iterations: iterations,
      });

      // Decritta il messaggio
      const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
        iv: iv,
        padding: CryptoJS.pad.Pkcs7,
        mode: CryptoJS.mode.CBC,
      });

      // Restituisci il messaggio originale in formato UTF-8
      let ris = decrypted.toString(CryptoJS.enc.Utf8);

      // console.log('RIS', ris);

      return ris;
    } catch (error) {
      console.error('❌❌❌ Errore durante la decrittazione:', error.message);
      throw error;
    }
  },
  cryptdata(mydata) {
    if (mydata === '') return '';
    // Encrypt
    //return CryptoJS.AES.encrypt(JSON.stringify(mydata), process.env.SECRK);
    return this.encrypt(mydata, process.env.SECRK);
  },

  decryptdata(mydatacrypted) {
    if (!mydatacrypted) return '';

    try {
      // console.log('SECRK', process.env.SECRK);
      const decr = this.decrypt(mydatacrypted, process.env.SECRK);
      // console.log('decr:', mydatacrypted, '->', decr);
      return decr;
    } catch (error) {
      console.error('❌❌❌❌❌ Decryption error:', error);
      return '';
    }
  },

  BoolToInt(mybool) {
    return mybool ? -1 : 0;
  },

  StrToBool(mystr) {
    return mystr === '-1' ? true : false;
  },

  writelogfile(mystr, filename) {
    //   fs.appendFile(FILELOG, mystr, function (err) {
    //     if (err) throw err;
    //     console.log('Saved!');
    //   });
    try {
      mystr = this.getstrDateTimeShort(new Date(), 'it') + ': ' + mystr;

      const stream = fs.createWriteStream(filename, { flags: 'a' });
      stream.write('\n' + mystr);
      stream.end();
    } catch (e) {}
  },

  async readlogfile(idapp, filename) {
    try {
      return await fs.readFileSync(idapp + '/' + filename, 'utf8');
    } catch (e) {
      return '';
    }
  },

  async readfilecontent(filename) {
    try {
      const cont = await fs.readFileSync(filename, 'utf8');
      return cont;
    } catch (e) {
      return '';
    }
  },

  async getVersServer() {
    try {
      return await this.readfilecontent(__dirname + '/../version.txt');
    } catch (e) {
      return '';
    }
  },

  writelog(mystr) {
    this.writelogfile(mystr, FILELOG);
  },

  writeEventsLog(mystr) {
    this.writelogfile(mystr, FILEEVENTS);
  },

  writeManagersLog(mystr) {
    this.writelogfile(mystr, FILEMANAGERS);
  },
  writeTransactionLog(mystr) {
    this.writelogfile(mystr, FILETRANSACTION);
  },
  writeErrorLog(mystr) {
    this.writelogfile(mystr, FILEERRLOGIN);
  },

  writeSostituzioniLog(mystr) {
    this.writelogfile(mystr, FILESOSTITUZIONI);
  },

  writeIPToBan(mystr) {
    this.writelogfile(mystr, FILEIP_TO_BAN);
  },

  writeFlottaLog(idapp, mystr, riga, col) {
    this.mkdirpath(idapp + '/');
    this.writelogfile(mystr, idapp + '/' + riga + '_' + col + '.txt');
  },

  writeNaveLog(mystr) {
    this.writelogfile(mystr, FILENAVE);
  },

  async copyFile(src, dest) {
    return new Promise((resolve, reject) => {
      const readStream = fs.createReadStream(src);
      const writeStream = fs.createWriteStream(dest);

      readStream.on('error', reject);
      writeStream.on('error', reject);
      writeStream.on('finish', resolve);

      readStream.pipe(writeStream);
    });
  },

  /**
   * Sposta un file da oldPath a newPath.
   * Se rename fallisce con EXDEV (diverso device), fa copia + delete.
   * Se la cartella di destinazione non esiste, la crea.
   * @param {string} oldPath
   * @param {string} newPath
   * @param {(err?: Error) => void} [callback] - opzionale, se non passato ritorna Promise
   */
  async move(oldPath, newPath, callback) {
    const moveOp = async () => {
      // Assicurati che la cartella di destinazione esista
      const dir = path.dirname(newPath);
      console.log('crea directory ricorsivamente', dir);
      await fs.promises.mkdir(dir, { recursive: true });
      if (!(await fs.promises.stat(dir).catch(() => false))) {
        console.log('directory non creata, riprovo');
        await fs.promises.mkdir(dir, { recursive: true });
      }

      try {
        const newOldPath = oldPath.replace(/\/$/, '');
        const newNewPath = newPath.replace(/\/$/, '');
        await fs.promises.rename(newOldPath, newNewPath);
        console.log(`File spostato: ${oldPath} -> ${newPath}`);
      } catch (err) {
        if (err.code === 'EXDEV') {
          // Spostamento tra device diversi: copia + elimina
          await copyFile(oldPath, newPath);
          await fs.promises.unlink(oldPath);
          console.log(`File copiato e cancellato: ${oldPath} -> ${newPath}`);
        } else if (err.code === 'ENOENT') {
          throw new Error(`File non trovato: ${oldPath}`);
        } else {
          throw err;
        }
      }
    };

    if (callback && typeof callback === 'function') {
      moveOp()
        .then(() => callback())
        .catch(callback);
    } else {
      return moveOp();
    }
  },

  copy(oldPath, newPath, callback) {
    fs.copyFile(oldPath, newPath, (err) => {
      if (err) throw err;
      // console.log('source.txt was copied to destination.txt');
    });
  },

  extractFileName: function (filename) {
    return path.basename(filename);
  },

  extractFilePath: function (filename) {
    return path.dirname(filename);
  },

  async deleteFile(filePath) {
    try {
      await fs.promises.unlink(filePath);
      // console.log(`File eliminato con successo: ${filePath}`);
    } catch (err) {
      console.error(`Errore durante l'eliminazione del file: ${filePath}`, err);
      throw err;
    }
  },

  async delete(mypath, alsothumb, callback) {
    try {
      if (await this.isFileExistsAsync(mypath)) {
        await fs.promises.unlink(mypath);
        // console.log(`deleted file ${mypath}`);
        if (alsothumb) {
          let img_small = '';
          try {
            img_small = path.dirname(mypath) + '/' + server_constants.PREFIX_IMG_SMALL + path.basename(mypath);
            if (await this.isFileExistsAsync(img_small)) {
              await fs.promises.unlink(img_small);
              console.log(`deleted file ${img_small}`);
            } else {
              // console.warn(`File not found: ${img_small}`);
            }
            console.log(`deleted file ${img_small}`);
          } catch (e) {
            console.error(`Error deleting file ${img_small}`, e?.message);
          }
        }
      }
    } catch (e) {
      console.error(`Error deleting file ${mypath}`, e?.message);
      callback(e);
      return;
    }
    callback();
  },

  async mkdirpath(dirPath) {
    try {
      if (!(await this.isFileExistsAsync(dirPath))) {
        fs.mkdirSync(dirPath, { recursive: true });
      }
    } catch (e) {
      if (dirPath !== path.dirname(dirPath)) {
        const myname = path.dirname(dirPath);
        await this.mkdirpath(myname);
        // this.mkdirpath(dirPath);
      }
    }
  },

  getNomeCognomeTelegram(msg) {
    if (msg && msg.chat) {
      let nomecognome = msg.chat.first_name ? msg.chat.first_name : '';
      nomecognome += msg.chat.last_name ? ' ' + msg.chat.last_name : '';

      return nomecognome;
    }
    return '';
  },

  extractNameAndSurnameByComplete(name_complete) {
    const suffissoCognome = ['Del', 'La', 'De', 'Lo'];

    let campi = name_complete.split(' ');
    let namesurname = {
      name: '',
      surname: '',
    };

    if (campi.length === 2) {
      namesurname.name = campi[0];
      namesurname.surname = campi[1];
    } else if (campi.length === 3) {
      if (suffissoCognome.includes(campi[1])) {
        namesurname.name = campi[0];
        namesurname.surname = campi[1] + ' ' + campi[2];
      } else {
        namesurname.name = campi[0] + ' ' + campi[1];
        namesurname.surname = campi[2];
      }
    } else if (campi.length === 4) {
      namesurname.name = campi[0] + ' ' + campi[1];
      namesurname.surname = campi[2] + ' ' + campi[3];
    } else if (campi.length > 4) {
      namesurname.name = campi[0] + ' ' + campi[1];
      namesurname.surname = ' ' + campi[2];
      for (let ind = 3; ind < campi.length; ind++) {
        namesurname.surname += ' ' + campi[ind];
      }
    }

    return namesurname;
  },

  getiPAddressUser(req) {
    try {
      const striniziale = '::ffff:';
      if (req.ip.indexOf(striniziale) >= 0) {
        return req.ip.substring(striniziale.length);
      } else {
        return req.ip; // Express
      }
    } catch (e) {
      return '';
    }
  },
  removespaces(mystr) {
    return mystr.replace(/\s+/g, '');
  },

  async ModificheConsentite(req, table, fieldsvalue, idrec, user) {
    const { Circuit } = require('../models/circuit');

    if (table === 'sharewithus') {
      return true;
    }

    if (table === 'hours') {
      return true;
    }
    if (table === 'users') {
      if (idrec && req.user._id.toString() === idrec) {
        if (Object.keys(fieldsvalue).some((key) => key.startsWith('profile.'))) {
          return true;
        }
        if (Object.keys(fieldsvalue).some((key) => server_constants.User_Fields.includes(key))) {
          return true;
        }
      }
      if ('aportador_solidario' in fieldsvalue) {
        return true;
      }
      if ('ALL' in fieldsvalue) {
        //++Todo: Cancellalo solo se sono io il creatore dell'utente ... o se posso!
        return true;
      }
    } else if (table === 'circuits') {
      if (idrec) {
        // Permetti di fare modifiche se è un admin del circuito
        return await Circuit.isCircuitAdmin(idrec, req.user ? req.user.username : '');
      }
    } else if (table === 'accounts') {
      if (idrec) {
        if ('fidoConcesso' in fieldsvalue) {
          // Permetti di fare modifiche se è un admin del circuito
          return await Circuit.isCircuitAdmin(idrec, req.user ? req.user.username : '');
        }
      }
    }

    if (shared_consts.TABLES_PERM_CHANGE_FOR_USERS.includes(table)) {
      return true;
    }
    if (shared_consts.TABLES_PERM_NEWREC.includes(table)) {
      return true;
    }

    return false;
  },

  NotifyIfDelRecord(table) {
    if (table === 'users' || table === 'extralist' || table === 'groups' || table === 'graduatorias') {
      return true;
    }

    return false;
  },

  getFirstWord(mystr) {
    const myarr = mystr.split(' ');
    if (myarr.length > 0) return myarr[0];
    else return mystr;
  },

  getFirst2Car(mystr) {
    if (!!mystr) return mystr.substring(0, 2) + '.';
    else return '';
  },

  getmaxcol(riga) {
    return Math.pow(2, riga - 1);
  },

  getPrimoFuocoByIndCol(col) {
    // let ris = Math.ceil(col - (col % 8)) + 1;
    let ris = (Math.ceil(col / 8) - 1) * 8 + 1;
    if (ris <= 0) ris = 1;
    return ris;
  },

  getPrimaColFlotta(col) {
    // let ris = Math.ceil(col - (col % 8)) + 1;
    let ris = (Math.ceil(col / 64) - 1) * 64 + 1;
    if (ris <= 0) ris = 1;
    return ris;
  },

  getStrMsgByTipoMsg(tipomsg) {
    let mystr = '';

    if (tipomsg === this.TipoMsg.SEND_MSG_EFFETTUA_IL_DONO)
      mystr = 'Inviato Messaggio per Effettuare il Dono a Tutta la Flotta';
    else if (tipomsg === this.TipoMsg.SEND_MSG_SOLLECITO_DONATORI_NO_DONO)
      mystr = "Inviato Messaggio per RICORDARE di Effettuare il Dono a chi ancora non l'ha fatto";
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_MEDIATORI) mystr = 'Inviato Messaggio ai Mediatori';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_SOGNATORE) mystr = 'Inviato Messaggio al Sognatore';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_UTENTE_SOSTITUITO) mystr = "Inviato Messaggio all'utente Sostituito";
    else if (tipomsg === this.TipoMsg.SEND_MSG_DONO_RICEVUTO_CORRETTAMENTE)
      mystr = 'Inviato Messaggio Dono Ricevuto Correttamente';

    return mystr;
  },

  getflagtelegrambyLang(lang) {
    if (lang === 'it') return '🇮🇹';
    else if (lang === 'si') return '🇸🇮';
    else if (lang === 'es') return '🇪🇸';
    else if (lang === 'enUs') return '🇬🇧';
    else if (lang === 'uk') return '🇬🇧';
    else if (lang === 'fr') return '🇫🇷';
  },

  blockwords(mystr) {
    const arrwords = ['http', 'Http', 'Asasfasfas', '://', 'mrbit', 'admin', 'superuser'];
    try {
      for (const myword of arrwords) {
        if (mystr) {
          if (mystr.includes(myword)) {
            return true;
          }
        }
      }
    } catch (e) {}

    return false;
  },

  isAlphaNumeric(str) {
    let code, i, len;

    for (i = 0, len = str.length; i < len; i++) {
      code = str.charCodeAt(i);
      if (
        !(code > 47 && code < 58) && // numeric (0-9)
        !(code > 64 && code < 91) && // upper alpha (A-Z)
        !(code > 96 && code < 123)
      ) {
        // lower alpha (a-z)
        return false;
      }
    }
    return true;
  },

  isAlphaNumericAndSpecialCharacter(str) {
    let code, i, len;

    for (i = 0, len = str.length; i < len; i++) {
      code = str.charCodeAt(i);
      if (
        !(code > 47 && code < 58) && // numeric (0-9)
        !(code > 63 && code < 91) && // upper alpha (A-Z)  // e @
        code !== 46 && // . (punto)
        code !== 95 && // _
        code !== 45 && // -
        !(code > 96 && code < 123)
      ) {
        // lower alpha (a-z)
        return false;
      }
    }
    return true;
  },

  categorizeQueryString(queryObj) {
    let query = {};
    let order = {};
    //extract query, order, filter value
    for (const i in queryObj) {
      if (queryObj[i]) {
        // extract order
        if (i === 'order') {
          order['sort'] = queryObj[i];
          continue;
        }
        // extract range
        if (i === 'range') {
          let range_arr = [];
          let query_arr = [];
          // multi ranges
          if (queryObj[i].constructor === Array) {
            for (const r of queryObj[i]) {
              range_arr = r.split('-');
              query_arr.push({
                price: { $gt: range_arr[0], $lt: range_arr[1] },
              });
            }
          }
          // one range
          if (queryObj[i].constructor === String) {
            range_arr = queryObj[i].split('-');
            query_arr.push({
              price: { $gt: range_arr[0], $lt: range_arr[1] },
            });
          }
          Object.assign(query, { $or: query_arr });
          delete query[i];
          continue;
        }
        query[i] = queryObj[i];
      }
    }
    return { query, order };
  },

  dateToEpoch(thedate) {
    const time = new Date(thedate).getTime();
    return time - (time % 86400000);
  },

  dateToEpochStr(thedate) {
    const time = new Date(thedate).getTime();
    return new Date(time - (time % 86400000));
  },

  async loadApps() {
    const Site = require('../models/site');

    try {
      this.MYAPPS = await Site.findAll(0);
      console.log('🟢 *** this.MYAPPS');
    } catch (e) {
      console.error('loadApps', e);
    }
  },

  async getApps() {
    if (this.MYAPPS.length <= 0) {
      await this.loadApps();
    }

    return this.MYAPPS;
  },

  getPaymentTypesById(idmetodo) {
    return shared_consts.PaymentTypes[idmetodo];
  },

  getVersionint(versionstr) {
    let versionarr = versionstr.split('.');
    let version = 0;

    versionarr = versionarr.reverse();

    for (let i = 0; i < versionarr.length; i++) {
      version += versionarr[i] * Math.pow(10, i * 2);
    }

    return parseInt(version);
  },

  convstrToInt(mystr) {
    try {
      const parsed = parseInt(mystr, 10); // Specifica la base come 10
      return isNaN(parsed) ? 0 : parsed; // Restituisce 0 se il valore è NaN
    } catch (e) {
      return 0; // Se c'è un errore, restituisce 0
    }
  },

  isValidNumber(value) {
    // Controlla se il valore è un numero valido
    return !isNaN(value) && value !== null && value !== '';
  },

  invertescapeslash(mystr) {
    return mystr.replace(/Ç/g, '/');
  },

  isNumber(n) {
    return typeof n != 'boolean' && !isNaN(n) && n;
  },
  getNumObj(obj) {
    let count = 0;
    for (let properties in obj) {
      count = count + 1;
    }
    return count;
  },

  getNomeCognomeEUserNameByUser(user) {
    if (!user) return '';

    const name = user.name?.trim() || '';
    const surname = user.surname?.trim() || '';
    const username = user.username?.trim() || '';

    // Se ci sono nome e cognome
    if (name && surname) {
      return username ? `${name} ${surname} (${username})` : `${name} ${surname}`;
    }

    // Se c'è solo il nome
    if (name) {
      return username ? `${name} (${username})` : name;
    }

    // Se c'è solo il cognome
    if (surname) {
      return username ? `${surname} (${username})` : surname;
    }

    // Se c'è solo username
    if (username) {
      return username;
    }

    // Nessun dato disponibile
    return '';
  },

  sulServer() {
    return process.env.LOCALE !== '1';
  },

  refreshAllTablesInMem(idapp, table, updatebot, username) {
    const telegrambot = require('../telegram/telegrambot');

    if (table === shared_consts.TAB_BOTS || updatebot) {
      telegrambot.reloadMenuBot(idapp);
    }

    if (table === shared_consts.TAB_USERS) {
      telegrambot.deleteRecInMemByUsername(idapp, username);
    } else if (table === shared_consts.TAB_SITES) {
      telegrambot.reloadSites();
    }
  },

  downloadImage(url, filepath) {
    const download = require('image-downloader');

    try {
      return download.image({
        url,
        dest: filepath,
      });
    } catch (e) {
      console.error('Err download image', e);
      return false;
    }
  },

  async checkStr(msg, mystr, user, cmd) {
    if (msg.includes(mystr)) {
      if (cmd === 1) {
        msg = msg.replace(mystr, await this.getTimeExpReg(user.idapp, user.username));
      }
    }
    return msg;
  },

  getUnsubsribeUrl(mylocalsconf) {
    if (mylocalsconf) {
      const baseurl = this.getHostByIdApp(mylocalsconf.idapp);
      const urlunsibscribe =
        baseurl +
        '/unsubscribe?em=' +
        mylocalsconf.hashemail +
        '&mc=' +
        mylocalsconf.dataemail.mailchimpactive +
        '&email=' +
        mylocalsconf.emailto;
      return urlunsibscribe;
    }
    return '';
  },

  getUnsubsribeUrl_User(user) {
    if (user && user.email) {
      const hash = this.getHash(user.email + user.username);
      return this.getHostByIdApp(user.idapp) + '/unsubscribe_user?em=' + hash + '&email=' + user.email;
    }
    return '';
  },

  async convertSpecialTags(user, msg) {
    try {
      if (!msg) return msg;

      if (user) {
        // Usa replaceAll() per sostituire TUTTE le occorrenze (non solo la prima)
        if (msg.includes('{host}')) {
          msg = msg.replaceAll('{host}', this.getHostByIdApp(user.idapp));
        }
        if (msg.includes('{appname}')) {
          msg = msg.replaceAll('{appname}', this.getNomeAppByIdApp(user.idapp));
        }

        msg = msg.replaceAll('{username}', user.username || '');
        msg = msg.replaceAll('{name}', user.name || user.username || '');
        msg = msg.replaceAll('{surname}', user.surname || '');
        msg = msg.replaceAll('{urlunsubscribe_user}', this.getUnsubsribeUrl_User(user));
        msg = msg.replaceAll('{aportador_solidario}', user.aportador_solidario || '');

        // Usa optional chaining per evitare errori se user.profile è undefined
        if (user.profile?.link_payment) {
          msg = msg.replaceAll('{link_paypalme}', user.profile.link_payment);
        }
        if (user.profile?.revolut) {
          msg = msg.replaceAll('{revolut}', user.profile.revolut);
        }
        if (user.profile?.payeer_id) {
          msg = msg.replaceAll('{payeer_id}', user.profile.payeer_id);
        }
        if (user.profile?.advcash_id) {
          msg = msg.replaceAll('{advcash_id}', user.profile.advcash_id);
        }
        if (user.profile?.email_paypal) {
          msg = msg.replaceAll('{email_paypal}', user.profile.email_paypal);
        }
        if (user.profile?.note_payment) {
          msg = msg.replaceAll('{note_payment}', user.profile.note_payment);
        }
      }

      msg = msg.replaceAll('{link_chathelp}', this.HELP_CHAT);
    } catch (e) {
      console.error('Errore in convertSpecialTags:', e);
    }

    return msg;
  },

  getUserInfoMsg: function (idapp, myuser, usernametelegram = '', name_telegram = '', surname_telegram = '') {
    let msg = '';
    try {
      const name = myuser.username + (myuser.name ? ` (${myuser.name} ${myuser.surname})` : '');
      const linkuserprof = this.getHostByIdApp(idapp) + '/my/' + myuser.username;

      msg = `<br>👉🏻 <a href="${linkuserprof}">${name}</a><br>email: ${myuser.email}`;

      let u_tg =
        myuser.profile && myuser.profile.hasOwnProperty('username_telegram')
          ? myuser.profile.username_telegram
          : usernametelegram;
      let name_tg =
        myuser.profile && myuser.profile.hasOwnProperty('firstname_telegram')
          ? myuser.profile.firstname_telegram
          : name_telegram;
      let surname_tg =
        myuser.profile && myuser.profile.hasOwnProperty('lastname_telegram')
          ? myuser.profile.lastname_telegram
          : surname_telegram;

      let namesurnametg = '';
      if (name_tg || surname_tg) namesurnametg = ` [${name_tg} ${surname_tg}]`;

      if (u_tg) {
        msg += `<br><br>Apri chat 👉🏻 https://t.me/${u_tg}`;
      }
    } catch (e) {
      console.error('getUserInfoMsg', e);
    }

    return msg;
  },

  getUserShortInfoMsg: function (idapp, myuser, usernametelegram = '', name_telegram = '', surname_telegram = '') {
    let msg = '';
    try {
      const name = myuser.username + (myuser.name ? ` (${myuser.name} ${myuser.surname})` : '');
      const linkuserprof = this.getLinkUserProfile(idapp, myuser.username);

      let u_tg =
        myuser.profile && myuser.profile.hasOwnProperty('username_telegram')
          ? myuser.profile.username_telegram
          : usernametelegram;

      msg = `<br>Vedi Profilo sulla APP di <a href="${linkuserprof}">${name}</a>`;
      msg += `<br>Telegram: @${u_tg}`;
    } catch (e) {
      console.error('getUserInfoMsg', e);
    }

    return msg;
  },

  getlinkRelativeRequestNewPassword: function (idapp, email, tokenforgot) {
    const strlinkreg = process.env.LINK_UPDATE_PASSWORD + `?idapp=${idapp}&email=${email}&tokenforgot=${tokenforgot}`;
    return strlinkreg;
  },

  getlinkRequestNewPassword: function (idapp, email, tokenforgot) {
    const strlinkreg = this.getHostByIdApp(idapp) + this.getlinkRelativeRequestNewPassword(idapp, email, tokenforgot);
    return strlinkreg;
  },

  execScriptOnServer: async function (idapp, script, dir, listafiles, extfiles) {
    return new Promise(async (resolve, reject) => {
      exec(script, async (error, stdout, stderr) => {
        // Aggiunto async qui
        if (error) {
          console.error(`error: ${error}`);
          return reject(`Execution failed: ${error}`);
        }
        if (stderr) {
          console.error(`stderr: ${stderr}`);
        }
        // Se vuoi mantenere questa parte, puoi decommentarla
        // if (stdout) {
        //     console.log(`OUT: ${stdout}`);
        // }

        let arrout = [];
        let arrscripts = stdout
          .split('\n')
          .filter((script) => script.trim() !== '')
          .map((script) => script.replace(/\//g, ''));

        for (let i = 0; i < arrscripts.length; i++) {
          let label = arrscripts[i];
          let sock = false;
          let description = '';
          if (listafiles) {
            if (arrscripts[i].endsWith('.sh')) {
              let labelFilePath = path.join(__dirname, '..', '..', '..', dir, `${arrscripts[i]}`);

              // Verifica se il file esiste
              try {
                // console.log(__dirname);

                // console.log(labelFilePath);

                // Leggi il contenuto del file .label
                const labelContent = await fs.readFileSync(labelFilePath, 'utf-8');
                const lines = labelContent.split('\n');

                for (let i = 0; i < lines.length; i++) {
                  let linea = lines[i];
                  // se la linea inizia con #DATA
                  if (linea.indexOf('#DATA') !== -1) {
                    // estrai la linea separata da |
                    let arrparams = linea.split('|');

                    if (arrparams && arrparams.length > 0) {
                      let paramstr = arrparams[1].trim();
                      let value = arrparams[2].trim();

                      if (paramstr === 'TITLE') {
                        label = value;
                      } else if (paramstr === 'DESCRIZ') {
                        description = value;
                      } else if (paramstr === 'SOCK') {
                        sock = value ? value.toLowerCase() === 'true' : false;
                      }
                    }
                  }
                }
              } catch (error) {
                // Se desideri, puoi tenere un avviso o un log qui
              }
            }
          } else {
            label = '';
          }

          if (label && (!extfiles || (extfiles && arrscripts[i].endsWith('.' + extfiles)))) {
            arrout.push({ label, value: arrscripts[i], description, sock });
          }
        }

        resolve({ error, stdout, stderr, arrout });
      });
    });
  },
  execScriptNoOutput: function (script) {
    return new Promise((resolve, reject) => {
      console.log('execScriptNoOutput:', script);
      exec(script, (error, stdout, stderr) => {
        if (error) {
          console.error(`Exec error: ${error.message}`);
          return reject(error);
        }
        if (stderr) {
          console.warn(`Stderr (non bloccante): ${stderr}`);
        }
        resolve(true);
      });
    });
  },

  execScriptWithInputOnServer: async function (idapp, script) {
    return new Promise((resolve, reject) => {
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });

      // Esegui il processo
      const child = spawn('bash', [script]);

      let outputData = '';
      let errorData = '';

      // Gestisci l'output standard
      child.stdout.on('data', (data) => {
        outputData += data.toString(); // Accumulate output data
        console.log(`Output: ${data}`);

        // Chiedi l'input dell'utente
        rl.question('Your input: ', (answer) => {
          child.stdin.write(`${answer}\n`); // Invia la risposta allo script
        });
      });

      // Gestisci l'output di errore
      child.stderr.on('data', (data) => {
        errorData += data.toString(); // Accumulate error data
        console.error(`Error: ${data}`);
      });

      // Gestisci la chiusura del processo
      child.on('close', (code) => {
        console.log(`Process exited with code ${code}`);
        rl.close(); // Chiusura dell'interfaccia readline

        // Risolvi la promessa con i dati raccolti
        if (code !== 0) {
          reject(new Error(`Process exited with code ${code}: ${errorData}`)); // Rifiuta in caso di errore
        } else {
          resolve({ stdout: outputData, stderr: errorData });
        }
      });

      // Gestisci errori nel processo
      child.on('error', (error) => {
        reject(new Error(`Failed to start process: ${error.message}`));
      });
    });
  },

  // general.js

  execScriptByTelegram: function (idapp, msg, scriptPath, testo) {
    const { exec } = require('child_process');
    const telegrambot = require('../telegram/telegrambot');
    const idchat = msg.chat.id;

    console.log(`👉 execScriptByTelegram: ${testo} (${scriptPath})`);
    telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, testo);

    // Opzioni per simulare una shell più completa
    const options = {
      shell: '/bin/bash',
      env: {
        ...process.env, // eredita le variabili d'ambiente correnti
        PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
      },
      timeout: 60000, // 60 secondi max
    };

    exec(scriptPath, options, (error, stdout, stderr) => {
      let logMsg = '';
      let telegramMsg = '';

      if (error) {
        logMsg = `❌ Script fallito: ${error.message}`;
        telegramMsg = `❌ ERRORE: ${error.message}`;
        console.error(logMsg);
      } else if (stderr && !error) {
        // Nota: alcuni script scrivono info su stderr anche se OK → non sempre è errore!
        logMsg = `⚠️ Stderr (non fatale): ${stderr}`;
        telegramMsg = `⚠️ Avviso: ${stderr.substring(0, 300)}`; // tronca per Telegram
        console.warn(logMsg);
      } else {
        logMsg = '✅ Script completato con successo';
        telegramMsg = stdout ? stdout.substring(0, 300) : '✅ Completato';
        console.log(logMsg);
      }

      // Invia sempre un feedback a Telegram
      telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, telegramMsg);
    });
  },

  capitalize(value) {
    if (!value) {
      return '';
    }
    const myval = value.toString();
    return myval.charAt(0).toUpperCase() + myval.slice(1);
  },
  truncateString(str, maxLength) {
    if (str.length > maxLength) {
      return str.slice(0, maxLength) + '...';
    } else {
      return str;
    }
  },

  removeFileExtension(filename) {
    // Trova l'ultima occorrenza del punto nel nome del file
    const lastDotIndex = filename.lastIndexOf('.');

    // Se non c'è un punto o il punto è all'inizio del nome file,
    // restituisci il nome file originale
    if (lastDotIndex === -1 || lastDotIndex === 0) {
      return filename;
    }

    // Altrimenti, restituisci la parte del nome file prima dell'ultimo punto
    return filename.substring(0, lastDotIndex);
  },

  removeSpanAndDivTags(text) {
    // Rimozione dei tag <span> e </span> dalla stringa di testo
    const spanRegex = /<span[^>]*>|<\/span>/gi;
    const cleanedText = text.replace(spanRegex, '');

    // Rimozione dei tag <div> e </div> dalla stringa di testo
    const divRegex = /<div[^>]*>|<\/div>/gi;
    const finalText = cleanedText.replace(divRegex, '');

    return finalText;
  },

  firstchars(value, numchars = 200, continua, link) {
    if (!value) {
      return '';
    }
    try {
      value = this.convertHTMLTagsForTelegram(value);
      value = this.removeSpanAndDivTags(value);
      truncatedValue = this.truncateString(value, numchars);

      if (str.length < maxLength) {
        continua = false;
      }

      // Aggiungi il testo aggiuntivo per indicare il troncamento
      if (continua) {
        if (link) {
          truncatedValue += `<em> (... <a href="${link}">continua sulla App</a>)</em>`;
        } else {
          truncatedValue += `<em> (... continua sulla App)</em>`;
        }
      } else {
        truncatedValue += ' ...';
      }

      return truncatedValue;
    } catch (e) {
      return value;
    }
  },

  removeFontTags(text) {
    // Rimozione dei tag <font> e </font> dalla stringa di testo
    const regex = /<font[^>]*>|<\/font>/gi;
    const cleanedText = text.replace(regex, '');

    return cleanedText;
  },

  removeAtChar(mystr) {
    if (mystr && mystr[0] === '@') {
      return (mystr = mystr.substring(1));
    }

    return mystr;
  },

  updateQueryStringParameter(uri, key, value) {
    if (uri === '' || !uri) return '';
    var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
    var separator = uri.indexOf('?') !== -1 ? '&' : '?';
    if (uri.match(re)) {
      return uri.replace(re, '$1' + key + '=' + value + '$2');
    } else {
      return uri + separator + key + '=' + value;
    }
  },

  isArray(val) {
    return Array.isArray(val);
  },

  getContentByTypeMsg(typemsg) {},

  getContentNotifByParams(params, content, typesend) {
    if (typesend === shared_consts.TypeSend.TELEGRAM) {
      let myhost = this.getHostByIdApp(params.idapp);

      if (params.textaddTelegram) {
        content = content + '\n' + params.textaddTelegram;
      }

      if (params.linkaddTelegram) {
        if (params.openUrl)
          content = content + '\n' + '<a href="' + myhost + params.openUrl + '">' + params.linkaddTelegram + '</a>';
      }

      /*if (params.openUrl)
        content = content + '\n' + '<a href="' + myhost + params.openUrl + '">' + i18n.__('OPEN PAGE') + '</a>';
   
       */
    }

    return content;
  },

  getAhref(username, link) {
    return `<a href='${link}'>${username}</a>`;
  },

  replaceStringAtEnd(inputString, searchString, replacement) {
    // Create a regular expression that matches the searchString at the end of the inputString
    const regex = new RegExp(searchString + '$');

    // Use the replace method with the regular expression to replace the string
    const result = inputString.replace(regex, replacement);

    return result;
  },

  getstrDateTimeEvent(myevent) {
    let mystr = '';

    // is same day?
    if (this.getstrDateShort(myevent.dateTimeStart) === this.getstrDateShort(myevent.dateTimeEnd)) {
      mystr = i18n.__(
        'DATEDAYONLY',
        this.getstrDateLong(myevent.dateTimeStart),
        this.getstrTime(myevent.dateTimeStart),
        this.getstrTime(myevent.dateTimeEnd)
      );
    } else {
      if (!myevent.dateTimeEnd || isNaN(myevent.dateTimeEnd.getTime())) {
        mystr = i18n.__(
          'DATE_1DAY',
          this.getstrDateLong(myevent.dateTimeStart),
          this.getstrTime(myevent.dateTimeStart)
        );
      } else {
        mystr = i18n.__(
          'DATE_2DAYS',
          this.getstrDateLong(myevent.dateTimeStart),
          this.getstrTime(myevent.dateTimeStart),
          this.getstrDateLong(myevent.dateTimeEnd),
          this.getstrTime(myevent.dateTimeEnd)
        );
      }
    }

    return '🗓 ' + mystr;
  },

  getDoveStrByAnnuncio(myrec) {
    let dove = '🏠 ';
    if (myrec.mycities && myrec.mycities.length > 0) {
      dove += myrec.mycities
        .map((city) => {
          return `${city.comune} (${city.prov})`;
        })
        .join(', ');
    }
    return dove;
  },

  async getDescrEstesaStrByEvent(myrec) {
    let mystr = '';

    mystr = await this.firstchars(this.replaceStringAtEnd(myrec.note, '</div>', ''), 800);
    if (mystr) mystr = 'ℹ️ ' + mystr;
    return mystr;
  },

  async getEventForTelegram(myrec, mydescr, userorig) {
    try {
      let datastr = this.getstrDateTimeEvent(myrec);
      let dovestr = this.getDoveStrByAnnuncio(myrec);
      let descrestesa = await this.getDescrEstesaStrByEvent(myrec);
      let organizedby = myrec.organisedBy;
      let contributo = myrec.contribstr;

      let newdescr = i18n.__('NEW_EVENT', userorig, datastr, mydescr, dovestr);
      let newdescrtelegram = i18n.__('NEW_EVENT_TELEGRAM', datastr, mydescr, dovestr, descrestesa, userorig);
      if (organizedby) {
        newdescrtelegram += i18n.__('ORGANIZED_BY', organizedby);
      }
      if (contributo) {
        newdescrtelegram += i18n.__('CONTRIB', contributo);
      }
      newdescrtelegram += i18n.__('EVENT_ADDED_FROM', userorig);

      return { newdescr, newdescrtelegram };
    } catch (e) {
      console.error('Error', e);
    }
  },

  addRowTelegram(icon, title, content, acapo, acapofine, islink) {
    let mystr = '';

    let descrcontent = content;
    if (islink) {
      title = `<a href="${content}">${title}</a>`;
      descrcontent = '';
    }

    if (title) {
      if (icon) mystr += `${icon} `;

      mystr += `<strong>${title}</strong>`;

      if (acapo) {
        mystr += `\n`;
      } else {
        mystr += `: `;
      }
    }
    if (descrcontent) {
      mystr += `${descrcontent}\n`;

      if (acapofine) {
        mystr += `\n`;
      }
    }

    return mystr;
  },

  async getInCambioDiByRec(myrec) {
    const { Contribtype } = require('../models/contribtype');

    try {
      const idapp = myrec.idapp;
      if (!idapp) return '';
      const contribtype = await Contribtype.findAllIdApp(idapp);

      let mystr = '';
      for (const contribId of myrec.idContribType) {
        const reccontr = contribtype.find((rec) => rec._id.toString() === contribId);
        if (reccontr) {
          if (mystr) {
            mystr += ', ';
          }
          mystr += reccontr.label;
        }
      }
      return mystr;
    } catch (e) {}
  },

  async getStatusSkillByRec(myrec, onlyifNoDiPersona) {
    const { StatusSkill } = require('../models/statusSkill');

    try {
      const idapp = myrec.idapp;
      if (!idapp) return '';
      const statusskill = await StatusSkill.findAllIdApp(idapp);

      let mystr = '';
      if (onlyifNoDiPersona) {
        if (myrec.idStatusSkill.length === 1 && myrec.idStatusSkill[0] === shared_consts.STATUSSKILL_DI_PERSONA)
          return '';
      }
      for (const status of myrec.idStatusSkill) {
        const reccontr = statusskill.find((rec) => rec._id === status);
        if (reccontr) {
          if (mystr) {
            mystr += ' e ';
          }
          mystr += reccontr.descr;
        }
      }
      return mystr;
    } catch (e) {}
  },

  getIconByAdType(adType) {
    if (adType === shared_consts.AdType.OFFRO) return '🟢';
    else if (adType === shared_consts.AdType.CERCO) return '🔴';

    return '';
  },

  async getCategoriaSkillByRec(myrec) {
    const { Skill } = require('../models/skill');
    const { Sector } = require('../models/sector');

    try {
      // console.log('myrec', myrec);
      const idapp = myrec.idapp;
      if (!idapp) return '';

      const skillrec = await Skill.findAllIdApp(idapp);
      const sectorrec = await Sector.findAllIdApp(idapp);

      let mystr = '';
      const recsec = sectorrec.find((rec) => rec._id === myrec.idSector);
      if (recsec) {
        mystr += recsec.descr;
      }
      const rec = skillrec.find((rec) => rec._id === myrec.idSkill);
      if (rec) {
        mystr += ' 👉🏻 ' + rec.descr;
      }
      return mystr;
    } catch (e) {}
  },
  async getCategoriaGoodByRec(myrec) {
    const { SectorGood } = require('../models/sectorgood');
    // const { Good } = require('../models/good');

    // const goodrec = await Good.findAllIdApp(idapp);

    try {
      const idapp = myrec.idapp;
      if (!idapp) return '';
      const secgoodrec = await SectorGood.findAllIdApp(idapp);

      let mystr = '';
      const rec = secgoodrec.find((rec) => rec._id === myrec.idSectorGood);
      if (rec) {
        mystr += rec.descr;
      }
      return mystr;
    } catch (e) {}
  },
  async getCategoriaBachecaByRec(myrec) {
    const { SectorBacheca } = require('../models/sectorbacheca');

    try {
      const idapp = myrec.idapp;
      if (!idapp) return '';
      const secbachecarec = await SectorBacheca.findAllIdApp(idapp);

      let mystr = '';
      const rec = secbachecarec.find((rec) => rec._id === myrec.idSectorBacheca);
      if (rec) {
        mystr += rec.descr;
      }
      return mystr;
    } catch (e) {}
  },

  getComuniEProvinceByRec(myrec) {
    try {
      let mystr = '';
      if (myrec.hasOwnProperty('mycities')) {
        let ind = 0;
        for (const city of myrec.mycities) {
          if (ind > 0) mystr += ', ';
          mystr += city.comune + ' (' + city.prov + ')';
          ind++;
        }
      }
      return mystr;
    } catch (e) {}
  },

  convertAndTruncateHTMLForTelegram(text, maxLength = 200, link = '') {
    const tagMap = {
      '<ul>': '',
      '<ol>': '',
      '<li>': '\n• ',
      '</ul>': '',
      '</ol>': '',
      '</li>': '',
      '<br>': '\n',
      '<div>': '',
      '</div>': '\n',
    };

    // Rimuoviamo specificamente <b>\n</b>
    text = text.replace(/<b>\n<\/b>/g, '');

    Object.keys(tagMap).forEach((tag) => {
      const replacement = tagMap[tag];
      text = text.replaceAll(tag, replacement);
    });

    text = text
      .split('\n')
      .map((line) => line.trim())
      .join('\n');
    text = text
      .replace(/\n{3,}/g, '\n\n')
      .replace(/\n+$/, '')
      .trim();

    if (text.length <= maxLength) {
      return text;
    }

    // Tronchiamo il testo
    let truncated = text.slice(0, maxLength);
    let openTags = [];

    // Troviamo tutti i tag aperti
    const regex = /<(\w+)[^>]*>/g;
    let match;
    while ((match = regex.exec(truncated)) !== null) {
      if (!truncated.includes(`</${match[1]}>`, match.index)) {
        openTags.push(match[1]);
      }
    }

    // Rimuoviamo l'ultima parola parziale
    truncated = truncated.replace(/\S+$/, '');

    let strcontinua = '';
    if (link) {
      strcontinua = `<em> (... <a href="${link}">continua sulla App</a>)</em>`;
    } else {
      strcontinua = `<em> (... continua sulla App)</em>`;
    }

    // Aggiungiamo '...' e chiudiamo i tag aperti
    truncated += strcontinua;
    while (openTags.length > 0) {
      truncated += `</${openTags.pop()}>`;
    }

    return truncated;
  },

  convertHTMLTagsForTelegram(text) {
    const tagMap = {
      '<ul>': '',
      '<ol>': '',
      '<li>': '\n• ',
      '</ul>': '',
      '</ol>': '',
      '</li>': '',
      '<br>': '\n',
      '<div>': '',
      '</div>': '\n',
    };

    // Rimuoviamo i tag vuoti o contenenti solo spazi bianchi o newline
    text = text.replace(/<(b|i|strong|em)>(\s|\n)*<\/\1>/g, '');

    Object.keys(tagMap).forEach((tag) => {
      const replacement = tagMap[tag];
      text = text.replaceAll(tag, replacement);
    });

    // Rimuoviamo gli spazi bianchi all'inizio e alla fine di ogni riga
    text = text
      .split('\n')
      .map((line) => line.trim())
      .join('\n');

    // Rimuoviamo le righe vuote consecutive, lasciandone solo una
    text = text.replace(/\n{3,}/g, '\n\n');

    // Rimuoviamo le righe vuote alla fine del testo
    text = text.replace(/\n+$/, '');

    text = text.replace(/<b>\n<\/b>/g, '');

    return text.trim();
  },

  async getAnnuncioForTelegram(myrec, tablerec, mydescr, userorig, nuovo) {
    try {
      let newdescr = '';

      let out = '';

      let tiposcambio = '';
      let iconascambio = '🟢';

      let lang = 'it';

      let datastr = '';
      let dovestr = '';
      let organizedby = '';
      let contributo = '';

      let img = '';

      let descrperNotif = '';

      if (tablerec === shared_consts.TABLES_MYGOODS || tablerec === shared_consts.TABLES_MYSKILLS) {
        iconascambio = this.getIconByAdType(myrec.adType);
        if (myrec.adType === shared_consts.AdType.OFFRO) tiposcambio = 'Offro';
        else if (myrec.adType === shared_consts.AdType.CERCO) tiposcambio = 'Cerco';
      } else if (tablerec === shared_consts.TABLES_MYHOSPS) {
        iconascambio = this.getIconByAdType(myrec.adType);
        if (myrec.adType === shared_consts.AdType.CERCO) tiposcambio = 'Cerco';
        else tiposcambio = 'Offro';

        if (myrec.typeHosp === shared_consts.TYPEHOSP_OSPITALITA) tiposcambio += ' Ospitalità';
        else if (myrec.typeHosp === shared_consts.TYPEHOSP_SCAMBIOCASA) tiposcambio += ' Scambio Casa';
      } else if (tablerec === shared_consts.TABLES_MYBACHECAS) {
        datastr = this.getstrDateTimeEvent(myrec);
        organizedby = myrec.organisedBy;
        contributo = myrec.contribstr;
      }

      if (tablerec === shared_consts.TABLES_MYGOODS) {
        newdescr = i18n.__('Good', userorig, mydescr);
        descrperNotif = i18n.__('Good: %', mydescr);
      } else if (tablerec === shared_consts.TABLES_MYSKILLS) {
        newdescr = i18n.__('Service', userorig, mydescr);
        descrperNotif = i18n.__('Service: %', mydescr);
      } else if (tablerec === shared_consts.TABLES_MYHOSPS) {
        newdescr = i18n.__('Hosp', userorig, mydescr);
        descrperNotif = i18n.__('Hosp %s', userorig, mydescr);
      } else if (tablerec === shared_consts.TABLES_MYBACHECAS) {
        newdescr = i18n.__('Event', userorig, mydescr);
        let dovestr = this.getDoveStrByAnnuncio(myrec);
        descrperNotif = i18n.__('NEW_EVENT', userorig, datastr, mydescr, dovestr);
      }

      let contatto = userorig;
      let contatto_telegram = '';
      try {
        let username_telegram = myrec.profile.username_telegram;

        if (username_telegram) {
          contatto_telegram = `@${username_telegram}`;
          contatto = contatto_telegram;
        } else {
          contatto = myrec.email;
        }
      } catch (e) {}

      let organizedBy = '';
      let cat = '';
      let status = '';
      let online = false;
      let solo_online = false;
      let writtenby = myrec.profile.username_telegram;
      let sitoweb = '';
      let contact_phone = '';
      let contact_email = '';
      if (tablerec === shared_consts.TABLES_MYGOODS) {
        cat = await this.getCategoriaGoodByRec(myrec);
      } else if (tablerec === shared_consts.TABLES_MYSKILLS) {
        cat = await this.getCategoriaSkillByRec(myrec);
        online = myrec.idStatusSkill.findIndex((id) => id === shared_consts.STATUSSKILL_ONLINE) >= 0;
        solo_online = online && myrec.idStatusSkill.length === 1;
        status = await this.getStatusSkillByRec(myrec, true);
        if (status) {
          status = ' ' + status;
        }
      } else if (tablerec === shared_consts.TABLES_MYHOSPS) {
        cat = '';
      } else if (tablerec === shared_consts.TABLES_MYBACHECAS) {
        cat = await this.getCategoriaBachecaByRec(myrec);
        if (myrec.website) {
          sitoweb = myrec.website;
        }

        contact_phone = myrec.contact_phone || '';
        contact_email = myrec.contact_email || '';

        if (myrec.organisedBy) {
          organizedBy = myrec.organisedBy;
          contatto = '';
        } else {
          if (myrec.mygrp && myrec.mygrp.title) {
            organizedBy = myrec.mygrp.title;
            contatto = '';
          }
        }
        if (
          (myrec.contact_telegram && myrec.contact_telegram?.toLowerCase() !== contatto_telegram?.toLowerCase()) ||
          !contatto
        ) {
          contatto += '\n' + myrec.contact_telegram;
        }
        /*if (myrec.contact_phone) {
          contatto += ' 📞 ' + myrec.contact_phone;
        }*/
      }
      // let out = i18n.__('NEW_ANNUNCIO_TELEGRAM', mydescr, dovestr, descrestesa, userorig);

      if (tiposcambio) out += this.addRowTelegram(iconascambio, tiposcambio + status, '', true, false);

      const url = this.getHostByIdApp(myrec.idapp) + shared_consts.getDirectoryByTable(tablerec, true) + myrec._id;

      out += `<strong>${myrec.descr}</strong>\n\n`;

      if (datastr) out += this.addRowTelegram('', '', datastr, true, true);

      if (cat) out += this.addRowTelegram('⭐️', 'Categoria', cat, true, true);

      let descrcontent = this.convertAndTruncateHTMLForTelegram(myrec.note, 500, url);

      // let descrcontent = this.firstchars(this.removeLastSpaceAndACapo(note), 200, true, url);

      descrcontent = this.cleanHtmlForTelegram(descrcontent);

      // descrcontent = '<span size="3"><b>Prova Pao</b> Ciaooo</span>';

      if (descrcontent) out += this.addRowTelegram('', '', '📝 ' + descrcontent, true, true);

      const localita = this.getComuniEProvinceByRec(myrec);

      if (!solo_online) out += this.addRowTelegram('🏠', 'Località', localita, false, true);

      const incambiodi = await this.getInCambioDiByRec(myrec);
      if (incambiodi) out += this.addRowTelegram('⚖️', 'In cambio di', incambiodi, false, true);

      if (organizedBy) {
        out += this.addRowTelegram('🏠', 'Organizzato da', organizedBy, false, true);
      } else {
        out += this.addRowTelegram('👤', 'Contatto', contatto, false, true);
      }

      if (contributo) out += this.addRowTelegram('💰', 'Contributo Richiesto', contributo, false, true);

      if (contact_phone || contact_email || sitoweb) {
        out += this.addRowTelegram(
          'ℹ️',
          'Per Info:',
          (contact_phone ? '📞 ' + contact_phone + ' ' : '') +
            (contact_email ? ' 📨 ' + contact_email + ' ' : '') +
            (sitoweb ? ' 🌐 ' + sitoweb + ' ' : ''),
          true,
          true
        );
      }

      if (writtenby && organizedBy) {
        out += this.addRowTelegram('✍️', 'Redatto da', '@' + writtenby, false, true);
      }

      let cosastr = 'Annuncio';
      if (tablerec === shared_consts.TABLES_MYBACHECAS) {
        cosastr = 'Evento';
      }

      out += this.addRowTelegram('', `👉🏻 Vedi ${cosastr} completo su RISO`, url, true, true, true);

      if (myrec.photos && myrec.photos.length > 0) {
        // prende la prima foto !        ;
        if (myrec.photos[0].imagefile)
          img = this.getURLImg(myrec.idapp, tablerec, myrec.username, myrec.photos[0].imagefile, false);
      }

      console.log('out', out);

      // out += i18n.__('ADDED_FROM', );

      return { newdescr, newdescrtelegram: out, img, descrperNotif };
    } catch (e) {
      console.error('Error', e);
    }
  },

  generateVapiKey() {
    const webpush = require('web-push');

    const vapidKeys = webpush.generateVAPIDKeys();

    const msg1 = 'VAPID Public Key:' + vapidKeys.publicKey;
    const msg2 = 'VAPID Private Key:' + vapidKeys.privateKey;

    console.log(msg1);
    console.log(msg2);
    return { msg: msg1 + ' ' + msg2 };
  },

  differentObjects(obj1, obj2) {
    try {
      const fieldsUpdated = [];
      const obj1Fields = obj1 ? Object.keys(obj1) : [];
      const obj2Fields = obj2 ? Object.keys(obj2) : [];

      const commonFields = obj1Fields.filter((field) => obj2Fields.includes(field));

      commonFields.forEach((field) => {
        if (obj1 && obj2 && (obj1[field] && obj1[field].toString()) !== (obj2[field] && obj2[field].toString())) {
          if (obj1[field] !== undefined && obj2[field] !== undefined) fieldsUpdated.push(field);
        }
      });
      return fieldsUpdated;
    } catch (e) {
      console.error('Err:', e);
    }
  },

  isObjectEmpty(obj) {
    return Object.keys(obj).length === 0;
  },

  getUnitsMeasure(unit, short) {
    const unitrec = shared_consts.Units_Of_Measure_ListBox.find((rec) => rec.value === unit);
    return unitrec ? (short ? unitrec.short : unitrec.label) : '';
  },

  getIdUnitsByText(unitstr) {
    const unitrec = shared_consts.Units_Of_Measure_ListBox.find((rec) => rec.short === unitstr);
    return unitrec ? unitrec.value : 0;
  },

  getWeightAndUnitByText(text) {
    const result = [];

    text = text.replace(',', '.');
    const regex = /^(\d+\.?\d*)\s*(ml|gr|g|l|kg|uova)\s*$/; // Aggiunto un punto dopo \d+ per accettare anche i numeri con la virgola

    const match = regex.exec(text);
    if (match) {
      let peso = parseFloat(match[1]); // Cambiato da parseInt a parseFloat per gestire i numeri con la virgola
      let unita = match[2];
      if (unita === 'gr') unita = 'g';
      const unit = this.getIdUnitsByText(unita);

      return { weight: peso, unit };
    }

    return { weight: 0, unit: 0 };
  },

  async isManagerByReq(req) {
    try {
      var { User } = require('../models/user');

      const idapp = req.body.idapp;
      let userId = '';
      if (req.body) userId = req.body.userId;

      const myuser = await User.getUserById(idapp, userId);
      let ismanager = false;
      if (myuser && myuser.perm) {
        ismanager = User.isManager(myuser.perm);
      }

      return ismanager;
    } catch (e) {
      return false;
    }
  },

  addslashes(str) {
    return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
  },

  removeescape(inputString) {
    // return inputString.replace(/\\/g, '').replace(/"/g, '');
    if (inputString) return inputString.replace(/\\/g, '').replace(/"/g, '');
    else return '';
  },

  escapeQuotes(stringa) {
    if (stringa) return stringa.replace(/"/g, '\\"');
    else return '';
  },

  replaceQuotesWithSingleQuotes(jsonString) {
    if (jsonString) return jsonString.replace(/"/g, "'");
    else return '';
  },

  ripulisciCampo(stringa) {
    let mystr = this.replaceQuotesWithSingleQuotes(stringa);
    return mystr;
  },

  convertToNumeroVirgola(valstr) {
    if (valstr === '' || valstr === undefined) valstr = '0';

    valstr = valstr + '';
    valstr = valstr.replace(',', '.');

    return parseFloat(valstr);
  },

  convertPriceEurToValue(inputString) {
    inputString = this.removeescape(this.addslashes(inputString));
    if (inputString === '') return '0';
    // Rimuovi il simbolo della valuta (€) e sostituisci la virgola con un punto
    return inputString.replace(/[^\d.,]/g, '').replace(',', '.');
  },

  removeLastSpaceAndACapo(miastr) {
    miastr = miastr.trimRight().replace(/[\n\r]+$/, '');
    // Rimuove eventuali '<br>' in fondo alla stringa
    // Rimuove eventuali '<div><br></div>' in fondo alla stringa
    miastr = miastr.replace(/<\/div>$/, '');
    miastr = miastr.replace(/<\/div>$/, '');
    miastr = miastr.replace(/<br>$/, '');
    miastr = miastr.replace(/<div><br><\/div>$/, '');
    miastr = miastr.replace(/<br>$/, '');
    miastr = miastr.trimRight().replace(/[\n\r]+$/, '');
    miastr = miastr.replace(/<div><br><\/div>$/, '');
    miastr = miastr.replace(/<div>$/, '');
    miastr = miastr.replace(/<br>$/, '');
    miastr = miastr.replace(/<br>$/, '');

    return miastr;
  },

  rimuoviAtPrimoCarattere(stringa) {
    if (stringa.charAt(0) === '@') {
      return stringa.slice(1);
    } else {
      return stringa;
    }
  },

  // Funzione per ottenere le coordinate di una singola città
  async getCityCoordinates(city) {
    try {
      const response = await axios.get(
        `https://nominatim.openstreetmap.org/search?format=json&q=${city.descr},${city.prov},Italia`
      );
      if (response.data.length > 0) {
        city.lat = parseFloat(response.data[0].lat);
        city.long = parseFloat(response.data[0].lon);
        // console.log(`${city.descr}: Lat ${city.lat}, Long ${city.long}`);
        return city;
      } else {
        console.error(`Coordinate non trovate per ${city.descr}, ${city.prov}`);
      }
      return null;
    } catch (error) {
      console.error(`Errore durante il recupero delle coordinate per ${city.descr}, ${city.prov}:`, error.message);
    }
  },
  /**
   * Funzione per scaricare dati GeoJSON dei comuni d'Italia e salvarli localmente.
   * @param {string} url - L'URL dal quale scaricare i dati GeoJSON.
   * @param {string} outputPath - Il percorso del file in cui salvare i dati GeoJSON.
   */
  async downloadGeoJSON(url, outputPath) {
    try {
      // Effettua una richiesta GET all'URL specificato per scaricare i dati.
      const response = await axios.get(url);
      const data = response.data;

      // Salva i dati GeoJSON in un file locale.
      await fs.writeFile(outputPath, JSON.stringify(data, null, 2));
      console.log(`Dati GeoJSON salvati in ${outputPath}`);
    } catch (error) {
      console.error('Errore durante il download o il salvataggio dei dati GeoJSON:', error);
    }
  },

  arrotondaA2Decimali(valore) {
    try {
      return parseFloat(valore).toFixed(2);
    } catch (error) {
      return valore;
    }
  },

  cleanHtmlForTelegram(htmlContent) {
    try {
      // Assicurati che l'HTML abbia un elemento body
      const dom = new JSDOM(`<body>${htmlContent}</body>`);
      const document = dom.window.document;

      // Definisci i tag permessi su Telegram
      const allowedTags = ['b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del', 'a', 'code'];

      // Seleziona tutti gli elementi all'interno del body
      const allElements = document.body.querySelectorAll('*');

      allElements.forEach((element) => {
        const tagName = element.tagName?.toLowerCase();

        if (!allowedTags.includes(tagName)) {
          // Crea un nodo di testo e sostituisci l'elemento con quel nodo di testo
          const textNode = document.createTextNode(element.textContent || '');
          element.replaceWith(textNode);
        } else {
          // Se il tag è permesso, rimuovi tutti gli attributi ad eccezione di 'href' per i link
          if (tagName === 'a') {
            const href = element.getAttribute('href');
            while (element.attributes.length > 0) {
              element.removeAttribute(element.attributes[0].name);
            }
            if (href) {
              element.setAttribute('href', href);
            }
          } else {
            while (element.attributes.length > 0) {
              element.removeAttribute(element.attributes[0].name);
            }
          }
        }
      });

      // Ritorna l'HTML pulito
      return document.body.innerHTML;
    } catch (error) {
      console.error("Errore durante la pulizia dell'HTML:", error);
      throw error; // Oppure, gestisci l'errore come preferisci
    }
  },

  getHostWithNoHttporHttps(url) {
    try {
      const parsedUrl = new URL(url);
      return parsedUrl.host;
    } catch (error) {
      console.error("Errore durante la pulizia dell'HTML:", error);
      throw error; // Oppure, gestisci l'errore come preferisci
    }
  },

  // Crea un file con all'interno il nome del dominio per ogni app:
  createFileWithDomainName() {
    const arrapps = getApps();

    const filename = server_constants.FILECONFIG_SERVER;

    for (const app of arrapps) {
      fs.writeFile(filename, this.getHostWithNoHttporHttps(app.host), function (err) {
        if (err) {
          console.log(err);
        }
      });
      if (app.host_test) {
        fs.writeFile(filename, this.getHostWithNoHttporHttps(app.host_test), function (err) {
          if (err) {
            console.log(err);
          }
        });
      }
    }
  },

  async listCollectionsBySize() {
    let output = ''; // Variabile per memorizzare l'output

    try {
      const conn = mongoose.connection;

      // Ottieni le collezioni e calcola la dimensione
      const collections = await conn.db.listCollections().toArray();

      // Calcola la dimensione per ciascuna collezione
      const collectionSizes = await Promise.all(
        collections.map(async (collection) => {
          const stats = await conn.db.collection(collection.name).stats();
          return { name: collection.name, size: stats.size };
        })
      );

      // Ordina le collezioni per dimensione
      collectionSizes.sort((a, b) => b.size - a.size); // Ordine decrescente

      output += 'Collezioni ordinate per dimensione:\n'; // Inizio dell'output
      collectionSizes.forEach((collection) => {
        const sizeInMB = (collection.size / (1024 * 1024)).toFixed(2); // Converti in MB
        output += `Collezione: ${collection.name}, Dimensione: ${sizeInMB} MB\n`; // Aggiungi all'output
      });

      return output; // Restituisci l'output alla fine
    } catch (error) {
      console.error('Errore:', error);
      return `Errore: ${error.message}`; // Restituisci l'errore come stringa
    }
  },

  getStringaConto(mov) {
    let mystr = '';
    let userfrom = '';
    let userto = '';
    let namefrom = '';
    let surnamefrom = '';
    let nameto = '';
    let surnameto = '';

    let profilefrom = null;
    let profileto = null;

    let tipocontofrom = shared_consts.AccountType.USER;
    let tipocontoto = shared_consts.AccountType.USER;

    if (mov.contocomfrom && mov.contocomfrom.name) {
      userfrom += mov.contocomfrom.name;
      tipocontofrom = shared_consts.AccountType.COMMUNITY_ACCOUNT;
    }
    if (mov.groupfrom) {
      userfrom += mov.groupfrom.groupname;
      tipocontofrom = shared_consts.AccountType.CONTO_DI_GRUPPO;
    }
    if (mov.userfrom) {
      userfrom += mov.userfrom.username;
      namefrom = mov.userfrom.name;
      surnamefrom = mov.userfrom.surname;
      profilefrom = mov.userfrom.profile;
    }

    if (mov.contocomto && mov.contocomto.name) {
      userto += mov.contocomto.name;
      tipocontoto = shared_consts.AccountType.COMMUNITY_ACCOUNT;
    }
    if (mov.groupto) {
      userto += mov.groupto.groupname;
      tipocontoto = shared_consts.AccountType.CONTO_DI_GRUPPO;
    }
    if (mov.userto) {
      userto += mov.userto.username;
      nameto = mov.userto.name;
      surnameto = mov.userto.surname;
      profileto = mov.userto.profile;
    }

    // mystr = t('movement.from') + userfrom + ' ' + t('movement.to') + userto

    return {
      userfrom: { profile: profilefrom, username: userfrom, name: namefrom, surname: surnamefrom },
      userto: { profile: profileto, username: userto, name: nameto, surname: surnameto },
      tipocontofrom,
      tipocontoto,
    };
  },

  getDirUpload() {
    return 'upload/';
  },

  getDirByTable(idapp, table) {
    let mydir = this.getdirByIdApp(idapp) + '/';

    if (table === 'catalog') {
      mydir = 'upload/cataloghi/';
    }

    return mydir;
  },

  getURLImg(idapp, table, username, img, checkifExist) {
    let dirmain = '';

    try {
      // let dir = this.getdirByIdApp(idapp) + dirmain + '/' + this.getDirUpload();
      let dir =
        this.getdirByIdApp(idapp) +
        '/' +
        this.getDirUpload() +
        shared_consts.getDirectoryImgByTable(table, username) +
        dirmain;

      img = dir + img;

      /*if (checkifExist) {
        if (!this.isFileExistsAsync(img)) {
          return '';
        }
      }*/

      if (!this.sulServer()) {
        // Finta Immagine
        // img = 'https://riso.app/upload/profile/SoniaVioletFlame/myskills/1000133092.jpg';
      }

      return img;
    } catch (e) {
      console.error('Err GetURLImg', e);
      return '';
    }
  },

  getLangByUsername(idapp, username) {
    //++Todo LANG: Estrarre il Lang dall'username (user, group)
    return 'it';
  },

  getFieldsForAnnunci() {
    let annunciFields = {
      idMyGroup: {
        type: String,
      },
      expiryDateAdv: {
        type: Date,
      },
      enableExpiringAdv: {
        type: Boolean,
      },
      // **ADDFIELD_ANNUNCI
    };

    return annunciFields;
  },

  // Funzione per pulire il nome del file
  cleanFileName(filePath) {
    // Ottieni solo il nome del file dall'intero percorso
    // const fileName = filePath.split('/').pop();

    // Rimuovi apostrofi e sostituisci gli spazi con underscore
    const cleanedName = fileName.replace(/'/g, '').replace(/\s+/g, '_');

    // Restituisci il percorso chiaro; puoi decidere di mantenere il resto del percorso
    return cleanedName;
  },

  fixUrl(myurl, idapp) {
    //if (!myurl.startsWith('http')) {
    //  return this.getHostByIdApp(idapp) + myurl;
    //}
    if (myurl.startsWith('http://127.0.0.1')) {
      //myurl = myurl.replace('http://127.0.0.1:8084/', 'https://riso.app/')
      // Se è in locale allora metti una foto finta...
      // myurl =
      //   'https://images.unsplash.com/photo-1464047736614-af63643285bf?q=80&w=2874&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D';
      //myurl = myurl.replace('http://127.0.0.1', 'http://localhost')
    }

    return myurl;
  },

  // Funzione per convertire XML in JSON
  convertXMLToJSON(xml) {
    const parser = new xml2js.Parser();

    parser.parseString(xml, (err, result) => {
      if (err) {
        console.error('Error parsing XML:', err);
      } else {
        console.log(JSON.stringify(result, null, 2)); // Stampa il risultato in formato JSON
      }
    });
  },

  async getFreeDiskSpace(path = '/') {
    try {
      const { stdout } = await execPromise(`df -k ${path} | tail -1 | awk '{print $4}'`);
      const freeSpaceKB = parseInt(stdout.trim());
      const freeSpaceGB = (freeSpaceKB / 1024 / 1024).toFixed(2);
      return `${freeSpaceGB} GB`;
    } catch (error) {
      console.error('Errore nel recupero dello spazio libero su disco:', error);
      return null;
    }
  },

  async findAllQueryIdApp(model, myfind, sort) {
    try {
      if (sort) {
        return await model.find(myfind).sort(sort).lean();
      } else {
        return await model.find(myfind).lean();
      }
    } catch (err) {
      console.error('Errore in findAllQueryIdApp:', err, model);
      return null;
    }
  },

  // Funzione per implementare il ritardo tra i tentativi
  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  },

  /**
   * Converte una data in formato italiano GG/MM/YYYY in un oggetto Date e restituisce il timestamp.
   * @param {string} dateString - La data in formato GG/MM/YYYY.
   * @returns {Object} - Un oggetto contenente la data (Date) e il timestamp (number).
   *                     Restituisce null se la data non è valida.
   */
  convertiDataItaliana(dateString) {
    // Verifica che la data sia una stringa valida
    if (!dateString || typeof dateString !== 'string') {
      console.error('Input non valido: la data deve essere una stringa.');
      return null;
    }

    // Rimuovi eventuali spazi bianchi e dividi la data in GG, MM, YYYY
    const [giorno, mese, anno] = dateString.trim().split('/').map(Number);

    // Controlla che i valori siano stati estratti correttamente
    if (isNaN(giorno) || isNaN(mese) || isNaN(anno)) {
      console.error('Formato data non valido:', dateString);
      return null;
    }

    // Crea un oggetto Date (mese parte da 0 in JavaScript)
    const dateObj = new Date(anno, mese - 1, giorno);

    // Verifica che la data sia valida
    if (isNaN(dateObj.getTime())) {
      console.error('Data non valida:', dateString);
      return null;
    }

    // Restituisci l'oggetto con la data e il timestamp
    return {
      date: dateObj,
      timestamp: dateObj.getTime(),
    };
  },

  getDateFromISOString(mydate) {
    const mydate2 = new Date(mydate);
    return mydate2;
  },

  async downloadImgIfMissing(product, forzacaricamento = false) {
    const Product = require('../models/product');

    try {
      if (this.sulServer()) {
        dirmain = '';
      } else {
        dirmain = server_constants.DIR_PUBLIC_LOCALE;
      }

      const vecchiomodo = false;

      let imagefile = product.productInfo.imagefile;

      if (product.productInfo.image_link && vecchiomodo) {
        const relativeimg = product.productInfo.image_link.split('/').pop();
        const img =
          this.getdirByIdApp(product.idapp) +
          dirmain +
          server_constants.DIR_UPLOAD +
          '/products/' +
          product.productInfo.image_link.split('/').pop();
        const savePath = path.resolve(__dirname, img); // Sostituisci con il percorso dove salvare l'immagine

        let scaricaimg = !imagefile || !(await this.isFileExistsAsync(savePath));

        if (!imagefile && (await this.isFileExistsAsync(savePath))) {
          // esiste il file, ma sul DB non è corretto
          const stats = fs.statSync(savePath); // Ottieni informazioni sul file

          if (stats.size > 0) {
            // Controlla se la dimensione del file è maggiore di zero
            // Esiste il file ed è non vuoto, ma sul DB non è corretto
            imagefile = relativeimg;
            return { imagefile, aggiornatoimg: true };
          } else {
            scaricaimg = true;
          }
        }

        if (imagefile && (await this.isFileExistsAsync(savePath))) {
          // esiste il file, ma sul DB non è corretto
          const stats = fs.statSync(savePath); // Ottieni informazioni sul file

          if (stats.size <= 0) {
            // Controlla se la dimensione del file è maggiore di zero
            scaricaimg = true;
          }
        }

        if (scaricaimg && vecchiomodo) {
          // Download image from the URL productInfo.image_link
          imagefile = relativeimg;

          const downloader = new ImageDownloader();

          const aggiornatoimg = await downloader
            .downloadImage(product.productInfo.image_link, savePath, {
              maxRetries: 1,
              initialDelay: 300,
              timeout: 15000,
            })
            .then((result) => {
              if (result) {
                // console.log('Download completato con successo!');
              } else {
                console.log('Download non riuscito.');
              }

              return result;
            });
          return { imagefile, aggiornatoimg: aggiornatoimg.ris };
        }
      }

      let fileesistente = false;
      if (imagefile && imagefile !== 'noimg.jpg') {
        // controlla se esiste il file
        const img =
          this.getdirByIdApp(product.idapp) +
          dirmain +
          server_constants.DIR_UPLOAD +
          '/products/' +
          imagefile.split('/').pop();
        const filecompleto = path.resolve(__dirname, img); // Sostituisci con il percorso dove salvare l'immagine

        // Se non esiste lo scarico !
        fileesistente = await this.isFileExistsAsync(filecompleto);
      }

      if (!vecchiomodo && (!product.productInfo.image_link || !fileesistente)) {
        let scarica_da_sito = !imagefile || imagefile === 'noimg.jpg';

        if (!scarica_da_sito && imagefile) {
          scarica_da_sito = !fileesistente; // Se non esiste lo scarico !
        }

        if ((scarica_da_sito && !product.productInfo.image_not_found) || forzacaricamento) {
          // date and time
          imagefile =
            'img_' + new Date().toISOString().replace(/T/, '').replace(/\..+/, '').replace(/:/, '').replace(/./, '');
          const img =
            this.getdirByIdApp(product.idapp) +
            dirmain +
            server_constants.DIR_UPLOAD +
            '/products/' +
            imagefile.split('/').pop();
          let savePath = path.resolve(__dirname, img); // Sostituisci con il percorso dove salvare l'immagine

          let link = 'https://www.gruppomacro.com/copertine.php?id_gm=' + product.productInfo.sku;

          const downloader = new ImageDownloader();

          let aggiornatoimg;
          try {
            aggiornatoimg = await downloader.downloadImage(link, savePath, {
              maxRetries: 3,
              initialDelay: 300,
              timeout: 15000,
              nomefileoriginale: true,
            });
          } catch (e) {
            aggiornatoimg = { ris: false };
          }
          let nontrovato = false;
          if (
            aggiornatoimg?.code === 404 ||
            (aggiornatoimg?.filepath && aggiornatoimg?.filepath?.includes('noimg.jpg'))
          ) {
            nontrovato = true;
            // non trovato quindi la prossima volta non richiederlo
            await Product.setImgNotFound(product._id);
          }

          if (aggiornatoimg?.filepath?.includes('noimg.jpg')) {
            // nascondi il prodotto se non trovo l'immagine !
            // await Product.updateOne({ _id: product._id }, { $set: { deleted: true } });

            aggiornatoimg = { ris: false, deleted: true };
          }

          if (aggiornatoimg?.filepath) {
            const filenamebase = path.basename(aggiornatoimg.filepath);
            // const img = '/upload/products/' + filenamebase;
            imagefile = filenamebase;
            if (nontrovato) {
              imagefile = '';
            }
          }

          return { imagefile, aggiornatoimg: aggiornatoimg.ris };
        } else {
          return { imagefile: null, aggiornatoimg: false };
        }
      }
    } catch (e) {
      console.error('downloadImgIfMissing', e.message);
    }

    return { imagefile: null, aggiornatoimg: false };
  },

  removeAccents(mystr) {
    if (!mystr) return mystr;

    const accentsMap = new Map([
      ['á', 'a'],
      ['à', 'a'],
      ['ã', 'a'],
      ['ä', 'a'],
      ['â', 'a'],
      ['é', 'e'],
      ['è', 'e'],
      ['ë', 'e'],
      ['ê', 'e'],
      ['í', 'i'],
      ['ì', 'i'],
      ['ï', 'i'],
      ['î', 'i'],
      ['ó', 'o'],
      ['ò', 'o'],
      ['ö', 'o'],
      ['ô', 'o'],
      ['õ', 'o'],
      ['ú', 'u'],
      ['ù', 'u'],
      ['ü', 'u'],
      ['û', 'u'],
      ['ç', 'c'],
      ['ñ', 'n'],
    ]);

    return Array.from(mystr)
      .map((char) => accentsMap.get(char) || char)
      .join('');
  },

  getDateYYYYMMDD_Today() {
    // Ottieni la data attuale nel formato YYYY-MM-DD
    const today = new Date();
    const formattedDate = today.toISOString().split('T')[0]; // Format YYYY-MM-DD
    return formattedDate;
  },

  convertFullFileNameToURL(idapp, fullfilename) {
    const mydir = this.getdirByIdApp(idapp);
    const myhost = this.getHostByIdApp(idapp);

    // ++ remove mydir from fullfilename and add myhost to generate a URL
    const url = fullfilename.replace(mydir, '').replace(/\\/g, '/'); // Replace backslashes with slashes
    const myurl = myhost + '/' + url;

    console.log('myurl', myurl);
    return myurl;
  },

  removePathDirByFileName(idapp, fullfilename) {
    const mydir = this.getdirByIdApp(idapp);
    let filename = fullfilename.replace(mydir, '').replace(/\\/g, '/'); // Sostituisce backslash con slash
    // Rimuove la barra iniziale se presente
    if (filename.startsWith('/')) {
      filename = filename.substring(1);
    }
    return filename;
  },

  isDateValid(mydate) {
    try {
      return (
        mydate instanceof Date && isFinite(mydate.getTime()) && mydate.toISOString().split('T')[0] !== '1970-01-01'
      );
    } catch {
      return false;
    }
  },

  async getSizeFile(filename) {
    try {
      if (await this.isFileExistsAsync(filename)) {
        const stats = fs.statSync ? fs.statSync(filename) : null;
        const fileSizeInBytes = stats.size;
        const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
        return fileSizeInMB.toFixed(2); // Returns size in MB with 2 decimal places
      } else {
        return 0;
      }
    } catch (e) {
      console.error('Error getting file size:', e);
      return '0.00';
    }
  },

  aggiungiSuffissoAlNomeFile(filePath, suffisso) {
    const dir = path.dirname(filePath);
    const estensione = path.extname(filePath);
    const nomeFile = path.basename(filePath, estensione);

    const nuovoNomeFile = nomeFile + suffisso + estensione;
    return path.join(dir, nuovoNomeFile);
  },

  async attendiNSecondi(numsec) {
    await new Promise((resolve) => setTimeout(resolve, numsec * 1000));
  },

  async copyDirectory(sourceDir, destinationDir) {
    try {
      await fs.copy(sourceDir, destinationDir);
      console.log('Directory copiata con successo!');
    } catch (err) {
      console.error('Errore durante la copia della directory:', err);
    }
  },

  fixFilePath(myfilepath) {
    return myfilepath.replace(/\\/g, '/');
  },

  getDateNow() {
    const mydate = new Date();
    return mydate;
  },

  areDifferentArrayString(array1, array2) {
    // confronta l'array1 con l'array2 (sono array di stringhe) e torna se sono diversi
    return !array1.every((val, index) => array2[index] === val);
  },

  areDifferentProduct(array1prod, array2prod) {
    // confronta l'array1 con l'array2  e torna se sono diversi
    // la struttura è product.productInfo.code , quindi puoi controllare se sono uguali il code
    return !array1prod.every((val1, index) => array2prod[index].productInfo.code === val1.productInfo.code);
  },

  getvalueByJsonText(valore) {
    if (valore && valore['#text']) {
      if (valore['#text']) return valore['#text'];
      else return value;
    }

    return null;
  },

  getTokenRandom() {
    return crypto.randomBytes(32).toString('hex');
  },

  async ensureDir(fullnamepath) {
    const dir = path.dirname(fullnamepath);

    try {
      await fs.stat(dir);
      // La directory esiste → tutto ok
    } catch (err) {
      if (err.code === 'ENOENT') {
        console.log('Directory non esistente, creazione:', dir);
        await fs.mkdir(dir, { recursive: true });
      } else {
        console.error('Errore imprevisto durante il controllo della directory:', err);
        throw err; // Rilancia l'errore se non è ENOENT
      }
    }
  },

  /* Converte una stringa di versione (es. "1.2.55") in un numero per confronti
   * @param {string} version - Stringa versione in formato "major.minor.patch"
   * @returns {number} Numero rappresentante la versione
   */
  versionToNumber(version) {
    const parts = version.split('.').map((num) => parseInt(num, 10));

    // Gestisce versioni con 1, 2 o 3 componenti
    const major = parts[0] || 0;
    const minor = parts[1] || 0;
    const patch = parts[2] || 0;

    // Usa padding di 3 cifre per minor e patch (supporta fino a 999)
    return major * 1000000 + minor * 1000 + patch;
  },

};
