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

const validator = require('validator');
const jwt = require('jsonwebtoken');
const _ = require('lodash');

const printf = require('util').format;
const tools = require('../tools/general');

const { Settings } = require('../models/settings');
// const {ListaIngresso} = require('../models/listaingresso');
const { Graduatoria } = require('../models/graduatoria');
// const {Nave} = require('../models/nave');
// const {NavePersistente} = require('../models/navepersistente');
// const { ExtraList } = require('../models/extralist');

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

const { Reaction } = require('../models/reaction');

const { MyGroup } = require('../models/mygroup');
const { Circuit } = require('../models/circuit');

const { Account } = require('../models/account');
const { Movement } = require('../models/movement');

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

const i18n = require('i18n');

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

mongoose.Promise = global.Promise;

mongoose.level = 'F';
// Resolving error Unknown modifier: $pushAll
mongoose.plugin((schema) => {
  schema.options.usePushEach = true;
});

mongoose.set('debug', false);

const UserSchema = new mongoose.Schema(
  {
    userId: {
      type: String,
    },
    email: {
      type: String,
      // required: true,
      trim: true,
      minlength: 1,
      unique: false,
      /*validate: {
      validator: validator.isEmail,
      message: '{VALUE} is not a valid email'
    }*/
    },
    link_verif_email: {
      type: String,
    },
    hash: {
      type: String,
    },
    idapp: {
      type: String,
      required: true,
    },
    idMyGroup: {
      type: String,
    },
    group: {
      type: Number,
    },
    index: {
      type: Number,
    },
    ind_order: {
      type: Number,
    },
    old_order: {
      type: Number,
    },
    username: {
      type: String,
      required: true,
      trim: true,
      minlength: 3,
      unique: false,
    },
    name: {
      type: String,
      trim: true,
    },
    surname: {
      type: String,
      trim: true,
    },
    password: {
      type: String,
      require: true,
      minlength: 6,
    },
    old_password: {
      type: String,
      require: false,
    },
    lang: {
      type: String,
      require: true,
    },
    linkreg: {
      type: String,
      required: false,
    },
    verified_email: {
      type: Boolean,
    },
    lasttime_email_sent_verify: {
      type: Date,
    },
    made_gift: {
      type: Boolean,
    },
    tokens: [
      {
        access: {
          type: String,
          required: true,
        },
        browser: {
          type: String,
          required: true,
        },
        token: {
          type: String,
          required: true,
        },
        refreshToken: {
          type: String,
          default: '',
        },
        browser_random: {
          type: String,
          default: '',
        },
        date_login: {
          type: Date,
        },
      },
    ],
    perm: {
      type: Number,
    },
    ipaddr: {
      type: String,
    },
    banIp: {
      type: Boolean,
    },
    date_reg: {
      type: Date,
    },
    date_deleted: {
      type: Date,
    },
    date_tokenforgot: {
      type: Date,
    },
    tokenforgot: {
      type: String,
    },
    tokenforgot_code: {
      type: String,
    },
    retry_pwd: {
      type: Number,
      default: 0,
    },
    date_tokenreg: {
      type: Date,
    },
    tokenreg: {
      type: String,
    },
    lasttimeonline: {
      type: Date,
    },
    useragent: {
      type: String,
    },
    news_on: {
      type: Boolean,
    },
    diario_on: {
      type: Boolean,
    },
    test: {
      type: Boolean,
    },
    email_errata: {
      type: Boolean,
    },
    lastid_newstosent: {
      type: String,
    },
    aportador_solidario: {
      type: String,
    },
    verified_by_aportador: {
      type: Boolean,
    },
    token_da_ammettere: {
      type: String,
    },
    date_token_ammettere: {
      type: Date,
    },
    notask_verif: {
      type: Boolean,
    },
    trust_modified: {
      type: Date,
    },
    aportador_iniziale: {
      type: String,
    },
    aportador_solidario_nome_completo: {
      type: String,
    },
    aportador_solidario_ind_order: {
      type: Number,
    },
    deleted: {
      type: Boolean,
      default: false,
    },
    deletedAt: Date,
    sospeso: {
      type: Boolean,
    },
    blocked: {
      type: Boolean,
    },
    username_who_block: {
      type: String,
    },
    date_blocked: {
      type: Date,
    },
    reported: {
      type: Boolean,
    },
    username_who_report: {
      type: String,
    },
    date_report: {
      type: Date,
    },
    non_voglio_imbarcarmi: {
      type: Boolean,
    },
    navinonpresenti: {
      type: Boolean,
    },
    subaccount: {
      type: Boolean,
    },
    cart: {
      type: Object,
    },
    profile: {
      img: {
        type: String,
      },
      nationality: {
        type: String,
      },
      intcode_cell: {
        type: String,
      },
      iso2_cell: {
        type: String,
      },
      cell: {
        type: String,
      },
      cellVerified: {
        type: Boolean,
        default: false,
      },
      country_pay: {
        type: String,
      },
      email_paypal: {
        type: String,
      },
      payeer_id: {
        type: String,
      },
      advcash_id: {
        type: String,
      },
      revolut: {
        type: String,
      },
      link_payment: {
        type: String,
      },
      note_payment: {
        type: String,
      },
      paymenttypes: [],
      username_telegram: {
        type: String,
      },
      firstname_telegram: {
        type: String,
      },
      lastname_telegram: {
        type: String,
      },
      telegram_verification_skipped: {
        type: Boolean,
      },
      telegram_verification_token: {
        type: String,
      },
      telegram_verification_expires: {
        type: String,
      },
      website: {
        type: String,
      },
      teleg_id: {
        type: Number,
      },
      teleg_id_old: {
        type: Number,
      },
      teleg_checkcode: {
        type: Number,
      },
      manage_telegram: {
        type: Boolean,
      },
      admin_telegram: {
        type: Boolean,
      },
      resplist: {
        type: Boolean,
      },
      workerslist: {
        type: Boolean,
      },
      dateofbirth: {
        type: Date,
      },
      born_city: {
        type: String,
        trim: true,
      },
      born_city_id: {
        type: Number,
      },
      born_province: {
        type: String,
        trim: true,
      },
      born_country: {
        type: String,
        trim: true,
      },
      my_dream: {
        type: String,
      },
      saw_and_accepted: {
        type: Number,
      },
      saw_zoom_presentation: {
        type: Boolean,
      },
      ask_zoom_partecipato: {
        type: Boolean,
      },
      qualified: {
        type: Boolean,
      },
      qualified_2invitati: {
        type: Boolean,
      },
      special_req: {
        type: Boolean,
      },
      sex: {
        type: Number,
      },
      biografia: {
        type: String,
      },
      note: {
        type: String,
      },
      da_contattare: {
        type: Boolean,
      },
      qualifica: {
        type: String,
      },
      motivazioni: {
        type: String,
      },
      competenze_professionalita: {
        type: String,
      },
      cosa_offrire: {
        type: String,
      },
      cosa_ricevere: {
        type: String,
      },
      altre_comunicazioni: {
        type: Boolean,
      },
      come_ci_hai_conosciuto: {
        type: Boolean,
      },
      socio: {
        type: Boolean,
      },
      socioresidente: {
        type: Boolean,
      },
      consiglio: {
        type: Boolean,
      },
      myshares: [
        {
          description: { type: String },
          rating: { type: Number },
        },
      ],
      friends: [
        {
          _id: false,
          username: { type: String },
          date: { type: Date },
        },
      ], // username
      req_friends: [
        {
          _id: false,
          username: { type: String },
          date: { type: Date },
        },
      ], // username
      handshake: [
        {
          _id: false,
          username: { type: String },
          date: { type: Date },
        },
      ], // username
      mygroups: [
        {
          _id: false,
          groupname: { type: String },
          date: { type: Date },
        },
      ],

      mycircuits: [
        {
          _id: false,
          circuitname: { type: String },
          date: { type: Date },
          token: { type: String },
        },
      ],
      last_circuitpath: {
        type: String,
      },
      lastdate_reqRis: {
        type: Date,
      },
      lastdate_LinkReg: {
        type: Date,
      },
      notifs: [
        {
          _id: false,
          dir: { type: Number },
          value: { type: Number },
        },
      ],
      notif_idCities: [
        {
          type: Number,
        },
      ],
      notif_provinces: [
        {
          type: String,
        },
      ],
      notif_regions: [
        {
          type: String,
        },
      ],
      notif_sectors: [
        {
          type: Number,
        },
      ],
      notif_sector_goods: [
        {
          type: Number,
        },
      ],
      resid_prov_id: {
        type: Number,
      },
      resid_comune: {
        type: String,
        trim: true,
        default: '',
      },
      resid_str_comune: {
        type: String,
        trim: true,
        default: '',
      },
      resid_province: {
        type: String,
        trim: true,
      },
      resid_card: {
        type: String,
        trim: true,
      },
      stepTutorial: {
        type: Number,
      },
      noNameSurname: {
        type: Boolean,
      },
      noCircuit: {
        type: Boolean,
      },
      noComune: {
        type: Boolean,
      },
      noCircIta: {
        type: Boolean,
      },
      noFoto: {
        type: Boolean,
      },
      seen: [
        {
          _id: false,
          id: { type: String },
          tab: { type: Number },
        },
      ],
      bookmark: [
        {
          _id: false,
          id: { type: String },
          tab: { type: Number },
        },
      ],
      favorite: [
        {
          _id: false,
          id: { type: String },
          tab: { type: Number },
        },
      ],
      attend: [
        {
          _id: false,
          id: { type: String },
          tab: { type: Number },
          num: { type: Number },
        },
      ],
      version: { type: Number },
      insert_circuito_ita: { type: Boolean },

      // ============ DRIVER PROFILE ============
      driverProfile: {
        isDriver: {
          type: Boolean,
          default: false,
        },
        bio: {
          type: String,
          trim: true,
          maxlength: 500,
        },
        vehicles: [
          {
            type: {
              type: String,
              enum: ['auto', 'moto', 'furgone', 'minibus', 'altro'],
              default: 'auto',
            },
            brand: {
              type: String,
              trim: true,
            },
            model: {
              type: String,
              trim: true,
            },
            color: {
              type: String,
              trim: true,
            },
            colorHex: {
              type: String,
              trim: true,
            },
            year: {
              type: Number,
            },
            seats: {
              type: Number,
              min: 1,
              max: 50,
            },
            licensePlate: {
              type: String,
              trim: true,
            },
            features: [
              {
                type: String,
                enum: ['aria_condizionata', 'wifi', 'presa_usb', 'bluetooth', 'bagagliaio_grande', 'seggiolino_bimbi'],
              },
            ],
            photos: [
              {
                type: String,
                trim: true,
              },
            ],
            isDefault: {
              type: Boolean,
              default: false,
            },
            isVerified: {
              type: Boolean,
              default: false,
            },
          },
        ],
        ridesCompletedAsDriver: {
          type: Number,
          default: 0,
        },
        ridesCompletedAsPassenger: {
          type: Number,
          default: 0,
        },
        averageRating: {
          type: Number,
          default: 0,
          min: 0,
          max: 5,
        },
        totalRatings: {
          type: Number,
          default: 0,
        },
        verifiedDriver: {
          type: Boolean,
          default: false,
        },
        licenseVerified: {
          type: Boolean,
          default: false,
        },
        licenseNumber: {
          type: String,
          trim: true,
        },
        licenseExpiry: {
          type: Date,
        },
        memberSince: {
          type: Date,
          default: Date.now,
        },
        responseRate: {
          type: Number,
          default: 100,
          min: 0,
          max: 100,
        },
        responseTime: {
          type: String,
          enum: ['within_hour', 'within_day', 'within_days'],
          default: 'within_day',
        },
        totalKmShared: {
          type: Number,
          default: 0,
        },
        co2Saved: {
          type: Number,
          default: 0,
          // kg di CO2 risparmiati
        },
        badges: [
          {
            name: {
              type: String,
            },
            earnedAt: {
              type: Date,
              default: Date.now,
            },
          },
        ],
        level: {
          type: Number,
          default: 1,
          min: 1,
        },
        points: {
          type: Number,
          default: 0,
        },
      },

      // ============ PREFERENCES ============
      preferences: {
        // Preferenze di viaggio
        smoking: {
          type: String,
          enum: ['yes', 'no', 'outside_only'],
          default: 'no',
        },
        pets: {
          type: String,
          enum: ['no', 'small', 'medium', 'large', 'all'],
          default: 'small',
        },
        music: {
          type: String,
          enum: ['no_music', 'quiet', 'moderate', 'loud', 'passenger_choice'],
          default: 'moderate',
        },
        conversation: {
          type: String,
          enum: ['quiet', 'moderate', 'chatty'],
          default: 'moderate',
        },

        // Notifiche
        notifications: {
          rideRequests: {
            type: Boolean,
            default: true,
          },
          rideAccepted: {
            type: Boolean,
            default: true,
          },
          rideReminders: {
            type: Boolean,
            default: true,
          },
          messages: {
            type: Boolean,
            default: true,
          },
          marketing: {
            type: Boolean,
            default: false,
          },
          pushEnabled: {
            type: Boolean,
            default: true,
          },
          emailEnabled: {
            type: Boolean,
            default: true,
          },
        },

        // Privacy
        privacy: {
          showEmail: {
            type: Boolean,
            default: false,
          },
          showPhone: {
            type: Boolean,
            default: false,
          },
          showLastName: {
            type: Boolean,
            default: true,
          },
          showRides: {
            type: Boolean,
            default: true,
          },
        },

        // Località preferite
        favoriteLocations: [
          {
            name: {
              type: String,
              trim: true,
            },
            city: {
              type: String,
              trim: true,
            },
            address: {
              type: String,
              trim: true,
            },
            coordinates: {
              lat: Number,
              lng: Number,
            },
            type: {
              type: String,
              enum: ['home', 'work', 'other'],
              default: 'other',
            },
          },
        ],

        // Lingue parlate
        languages: [
          {
            type: String,
          },
        ],

        // Metodo di pagamento preferito
        preferredContribType: {
          type: Schema.Types.ObjectId,
          ref: 'Contribtype',
        },
      },
    },
    updatedAt: { type: Date, default: Date.now },
  },
  {
    timestamps: true, // Mongoose aggiorna automaticamente updatedAt
  }
);

UserSchema.methods.toJSON = function () {
  const user = this;
  const userObject = user.toObject();

  return _.pick(userObject, ['_id', ...shared_consts.fieldsUserToChange()]);
};

UserSchema.methods.generateAuthToken = function (req, browser_random) {
  const user = this;

  const useragent = req.get('User-Agent');
  // tools.mylog("GENERATE USER-AGENT = ", useragent);

  const access = 'auth';
  const browser = useragent;
  const prova = 'PROVAMSG@1A';
  let attiva_scadenza = user.idapp ? tools.getEnableTokenExpiredByIdApp(user.idapp) : false;

  let token = null;

  let numsec = process.env.TOKEN_LIFE;
  let scadenzaRT = '365d';
  let scadenzaToken = '365d';

  if (attiva_scadenza) {
    scadenzaRT = process.env.REFRESH_TOKEN_LIFE;
    scadenzaToken = process.env.TOKEN_LIFE;
  }

  token = jwt
    .sign({ _id: user._id.toHexString(), access, un: user.username }, process.env.SIGNCODE, {
      expiresIn: scadenzaToken,
    })
    .toString();

  const refreshToken = jwt
    .sign({ _id: user._id.toHexString(), access, un: user.username }, process.env.SECRK, {
      expiresIn: scadenzaRT,
    })
    .toString();

  const date_login = new Date();

  /*
  if (user.tokens) {
    console.log('token salvati: ' + user.tokens.length);
  } else {
    console.log('⚠️ Nessun Token salvato! ');
  }*/

  // Controlla se il token è già presente per la tripla access-browser-browser_random
  const idx = user.tokens.findIndex(
    (tok) =>
      tok.access === access && tok.browser === browser && (!browser_random || tok.browser_random === browser_random)
  );

  if (idx === -1) {
    user.tokens.push({ access, browser, token, date_login, refreshToken, browser_random });
  } else {
    // Se il token esiste già, sostituisce il valore vecchio con il nuovo
    user.tokens[idx] = { access, browser, token, date_login, refreshToken, browser_random };
  }

  user.lasttimeonline = new Date();

  return user
    .save()
    .then(() => {
      console.log('########## HO CREATO UN NUOVO TOKEN E REFRESHTOKEN !!!!! ----------- ');
      return { token, refreshToken, browser_random };
    })
    .catch((err) => {
      console.log('Error', err.message);
      return { token: '', refreshToken: '', browser_random };
    });
};

UserSchema.statics.setOnLine = async function (idapp, username) {
  const User = this;

  try {
    return await User.findOneAndUpdate({ idapp, username }, { $set: { lasttimeonline: new Date() } });
  } catch (e) {}
};

UserSchema.statics.setPermissionsById = function (id, perm) {
  const user = this;

  return user.findByIdAndUpdate(id, { $set: { perm } }).then((user) => {
    if (user) return res.send({ code: server_constants.RIS_CODE_OK, msg: '' });
    else return res.send({ code: server_constants.RIS_CODE_ERR, msg: '' });
  });
};

UserSchema.statics.setZoomPresenza = async function (idapp, id, presenza) {
  const User = this;

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

  let allData = {};
  allData.myuser = await User.getUserById(idapp, id);
  if (!!allData.myuser) allData.precDataUser = await User.getInfoUser(idapp, allData.myuser.username);

  return await User.findByIdAndUpdate(id, { $set: { 'profile.saw_zoom_presentation': presenza } }).then((rec) => {
    if (presenza) {
      const messaggio = tools.get__('ZOOM_CONFERMATO');
      telegrambot.sendMsgTelegram(rec.idapp, rec.username, messaggio);
      telegrambot.sendMsgTelegramToTheManagersAndZoomeri(
        idapp,
        `L\'utente ${rec.name} ${rec.surname} (${rec.username}) è stato confermato per aver visto lo Zoom di Benvenuto`
      );
    } else {
      telegrambot.sendMsgTelegramToTheManagersAndZoomeri(
        idapp,
        `L\'utente ${rec.name} ${rec.surname} (${rec.username}) è stato annullata la sua richiesta per aver visto lo Zoom di Benvenuto! (Non ci risulta)`
      );
    }

    return User.findByIdAndUpdate(id, { $set: { 'profile.ask_zoom_partecipato': false } }).then((user) => {
      User.checkIfSbloccatiRequisiti(idapp, allData, id);
    });
  });
};

UserSchema.statics.canHavePower = function (perm) {
  const User = this;

  try {
    let consentito = false;
    if (
      User.isAdmin(perm) ||
      User.isManager(perm) ||
      User.isEditor(perm) ||
      User.isCommerciale(perm) ||
      User.isFacilitatore(perm)
    ) {
      consentito = true;
    }

    return consentito;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isAdmin = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Admin) === shared_consts.Permissions.Admin;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isManager = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Manager) === shared_consts.Permissions.Manager;
  } catch (e) {
    return false;
  }
};
UserSchema.statics.isCollaboratore = function (perm) {
  try {
    return User.canHavePower(perm);
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isManagerById = async function (id) {
  try {
    const ris = await User.findOne({ _id: id }, { perm: 1 }).lean();
    return (ris.perm & shared_consts.Permissions.Manager) === shared_consts.Permissions.Manager;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isAdminById = async function (id) {
  try {
    const ris = await User.findOne({ _id: id }, { perm: 1 }).lean();
    return (ris.perm & shared_consts.Permissions.Admin) === shared_consts.Permissions.Admin;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isEditor = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Editor) === shared_consts.Permissions.Editor;
  } catch (e) {
    return false;
  }
};
UserSchema.statics.isCommerciale = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Commerciale) === shared_consts.Permissions.Commerciale;
  } catch (e) {
    return false;
  }
};
UserSchema.statics.isGrafico = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Grafico) === shared_consts.Permissions.Grafico;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isZoomeri = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Zoomeri) === shared_consts.Permissions.Zoomeri;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isDepartment = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Zoomeri) === shared_consts.Permissions.Department;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.isFacilitatore = function (perm) {
  try {
    return (perm & shared_consts.Permissions.Facilitatore) === shared_consts.Permissions.Facilitatore;
  } catch (e) {
    return false;
  }
};

/**
 * Finds a user by their authentication token.
 *
 * @param {string} token - The authentication token.
 * @param {string} typeaccess - The type of access associated with the token.
 * @param {boolean} con_auth - Whether to continue authentication if the token is expired.
 * @param {string} idapp - The application ID.
 * @returns {Promise<Object>} An object containing the user and a status code, indicating
 *                            whether the token is valid, expired, or invalid.
 *                            The user object is null if no user is found or the token is invalid.
 *
 * This function verifies the provided token and retrieves the corresponding user if the token is valid.
 * If the token is expired and `con_auth` is false, or if the token is invalid, it returns null for the user.
 * The status code reflects the validity of the token: valid, expired, or invalid.
 */

// Funzione helper separata per trovare l'utente
async function findUserByTokenAndAccessAndBrowserRandom(
  User,
  decoded,
  token,
  typeaccess,
  browser_random,
  withuser,
  withlean,
  project
) {
  try {
    const query = {
      _id: decoded._id,
      tokens: {
        $elemMatch: {
          token,
          access: typeaccess,
          browser_random,
        },
      },
    };

    if (withuser && !withlean) {
      return await User.findOne(query, project);
    }

    return await User.findOne(query, project).lean();
  } catch (err) {
    // console.warn('Errore con decoded._id, provo con decoded.smart:', err.message);

    // Fallback: usa decoded.smart
    const query = {
      _id: decoded.smart,
      tokens: {
        $elemMatch: {
          token,
          access: typeaccess,
          browser_random,
        },
      },
    };

    if (withuser && !withlean) {
      return await User.findOne(query, project);
    }

    return await User.findOne(query, project).lean();
  }
}

// Funzione principale refactored
UserSchema.statics.findByToken = async function (
  token,
  typeaccess,
  browser_random,
  con_auth,
  withuser,
  withlean = false
) {
  const User = this;
  let code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
  let user = null;
  let decoded;

  // Validazione token
  if (!token) {
    console.warn('TOKEN VUOTO!');
    return { user, code };
  }

  // Verifica JWT
  try {
    decoded = jwt.verify(token, process.env.SIGNCODE);
    code = server_constants.RIS_CODE_OK;
  } catch (err) {
    if (err.expiredAt) {
      code = server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED;
      if (con_auth) return { user: null, code };
    } else {
      console.error('Err findByToken:', err);
    }
    return { user: null, code };
  }

  // Definizione projection
  const project = withuser
    ? undefined
    : {
        perm: 1,
        _id: 1,
        idapp: 1,
        username: 1,
        deleted: 1,
        aportador_solidario: 1,
        aportador_solidario_nome_completo: 1,
        'profile.socioresidente': 1,
        lang: 1,
      };

  // Ricerca utente con funzione separata
  user = await findUserByTokenAndAccessAndBrowserRandom(
    User,
    decoded,
    token,
    typeaccess,
    browser_random,
    withuser,
    withlean,
    project
  );

  // Verifica scadenza token per idapp specifici
  if (user) {
    const checkExpiry = tools.getEnableTokenExpiredByIdApp(user.idapp);
    const currentTime = Date.now() / 1000;

    if (checkExpiry && decoded.exp < currentTime) {
      console.log('🔴 Il token è scaduto, generazione del nuovo token...');
      code = server_constants.RIS_CODE_HTTP_TOKEN_EXPIRED;
    }
  } else {
    code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
  }

  return { user, code };
};

UserSchema.statics.findByTokenAnyAccess = function (token) {
  const User = this;
  let decoded;

  try {
    decoded = jwt.verify(token, process.env.SIGNCODE);
  } catch (e) {
    console.error('Err findByTokenAnyAccess:', e);
    return Promise.resolve(null);
  }

  return User.findOne({
    _id: decoded._id,
    'tokens.token': token,
  }).lean();
};

UserSchema.statics.findByRefreshTokenAnyAccess = async function (refreshToken) {
  const User = this;
  let decoded;

  try {
    decoded = jwt.verify(refreshToken, process.env.SECRK);
  } catch (e) {
    console.error('Err findByRefreshTokenAnyAccess:', e);
    return Promise.resolve(null);
  }

  let ris = null;

  if (decoded) {
    try {
      ris = await User.findOne({
        _id: decoded._id,
        'tokens.refreshToken': refreshToken,
      });
    } catch (e) {
      ris = await User.findOne({
        _id: decoded.smart,
        'tokens.refreshToken': refreshToken,
      });
    }
  }

  return ris;
};

UserSchema.statics.findByCredentials = async function (idapp, username, password, pwdcrypted) {
  const User = this;
  let pwd = '';

  let regexp = new RegExp(`^${username}$`, 'i');

  let user = await User.findOne({
    idapp,
    username: { $regex: regexp },
    $or: [
      { deleted: { $exists: false } },
      { deleted: { $exists: true, $eq: false } },
      {
        $and: [{ deleted: { $exists: true, $eq: true } }, { subaccount: { $exists: true, $eq: true } }],
      },
    ],
  });

  if (!user) {
    // Check if with email:
    user = await User.findOne({
      idapp,
      email: username.toLowerCase(),
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });
  }

  if (!user) {
    // Check with username telegram
    user = await User.findOne({
      idapp,
      'profile.username_telegram': username.toLowerCase(),
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });
  }

  if (!user) {
    return null;
  }

  pwd = user.password;

  if (pwdcrypted) {
    if (pwd === user.password) {
      return user;
    } else {
      return false;
    }
  }

  const res = (await bcrypt.compare(password, pwd)) ? user : null;

  return res;
};

UserSchema.statics.setPwdComeQuellaDellAdmin = async function (mydata) {
  const User = this;

  const userAdmin = await User.findOne({
    _id: mydata.myuserId,
  });

  // Verifica permessi admin
  if (!User.isAdmin(userAdmin.perm)) {
    throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
  }

  // Trova l'utente da modificare
  const userfound = await User.findOne({
    _id: mydata._id,
  });

  if (!userfound) {
    throw new Error('Utente non trovato');
  }

  userfound.old_password = userfound.password;
  // Imposta la password dell'admin (già hashata)
  userfound.password = userAdmin.password;

  // Salva l'utente
  await userfound.save();

  return !!userfound;
};
UserSchema.statics.ripristinaPwdPrec = async function (mydata) {
  const User = this;

  const userAdmin = await User.findOne({
    _id: mydata.myuserId,
  });

  // Verifica permessi admin
  if (!User.isAdmin(userAdmin.perm)) {
    throw new Error('Permessi insufficienti: solo gli admin possono modificare le password');
  }

  // Trova l'utente da modificare
  const userfound = await User.findOne({
    _id: mydata._id,
  });

  if (!userfound) {
    throw new Error('Utente non trovato');
  }

  // Imposta la password dell'admin (già hashata)
  if (userfound.old_password) {
    userfound.password = userfound.old_password;
    userfound.old_password = '';
  }

  // Salva l'utente
  await userfound.save();

  return !!userfound;
};

UserSchema.statics.findByUsername = async function (idapp, username, alsoemail, onlyifVerifiedByAportador) {
  const User = this;

  if (!username) return null;

  const myreg = ['^', username, '$'].join('');
  let regexusername = new RegExp(myreg, 'i');

  //++TODO: Set only the necessary fields to get in memory

  return await User.findOne({
    idapp: idapp,
    username: { $regex: regexusername },
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .then(async (ris) => {
      if (!ris && alsoemail) {
        regexemail = new RegExp(['^', username.toLowerCase(), '$'].join(''), 'i');

        return await User.findOne({
          idapp: idapp,
          email: { $regex: regexemail },
          $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
        });
      }
      return ris;
    })
    .then((rec) => {
      if (rec && onlyifVerifiedByAportador) {
        if (tools.getAskToVerifyReg(idapp)) {
          if (!rec.verified_by_aportador) return null;
        }
      }

      return rec;
    });
};
/**
 * Find a user by their Telegram username.
 *
 * @param {string} idapp - The application ID
 * @param {string} username - The Telegram username
 * @param {boolean} alsoemail - Flag to indicate if email should also be considered
 * @param {boolean} onlyifVerifiedByAportador - Flag to indicate if only verified users should be returned
 * @return {Promise} A Promise that resolves to the found user or null
 **/

UserSchema.statics.findByUsernameTelegram = async function (idapp, username, alsoemail, onlyifVerifiedByAportador) {
  const User = this;

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

  const myreg = ['^', username, '$'].join('');
  let regexusername = new RegExp(myreg, 'i');

  //++TODO: Set only the necessary fields to get in memory

  return await User.findOne({
    idapp: idapp,
    'profile.username_telegram': { $regex: regexusername },
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .then(async (ris) => {
      if (!ris && alsoemail) {
        regexemail = new RegExp(['^', username.toLowerCase(), '$'].join(''), 'i');

        return await User.findOne({
          idapp: idapp,
          email: { $regex: regexemail },
          $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
        });
      }
      return ris;
    })
    .then((rec) => {
      if (rec && onlyifVerifiedByAportador) {
        if (tools.getAskToVerifyReg(idapp)) {
          if (!rec.verified_by_aportador) return null;
        }
      }

      return rec;
    });
};

// ===== CAMPI BASE COMUNI =====
const BASE_FIELDS = {
  username: 1,
  aportador_solidario: 1,
  name: 1,
  verified_email: 1,
  verified_by_aportador: 1,
  idMyGroup: 1,
  date_reg: 1,
  perm: 1,
};

const ADMIN_FIELDS = {
  surname: 1,
  lasttimeonline: 1,
  deleted: 1,
  sospeso: 1,
  reported: 1,
  date_report: 1,
  username_who_report: 1,
  notask_verif: 1,
  email: 1,
};

const PROFILE_PUBLIC = {
  'profile.username_telegram': 1,
  'profile.img': 1,
  'profile.sex': 1,
  'profile.born_province': 1,
  'profile.born_country': 1,
  'profile.resid_str_comune': 1,
  'profile.resid_comune': 1,
  'profile.resid_province': 1,
  'profile.resid_card': 1,
  'profile.calc': 1,
  'profile.handshake': 1,
  'profile.friends': 1,
  'profile.note': 1,
  'profile.da_contattare': 1,
};

const PROFILE_EXTENDED = {
  'profile.nationality': 1,
  'profile.mygroups': 1,
  'profile.mycircuits': 1,
  'profile.qualifica': 1,
  'profile.biografia': 1,
  'profile.website': 1,
  'profile.dateofbirth': 1,
  'profile.born_city_id': 1,
};

const PROFILE_CONTACT = {
  'profile.intcode_cell': 1,
  'profile.cell': 1,
  'profile.firstname_telegram': 1,
  'profile.lastname_telegram': 1,
};

const PROFILE_SOCIAL = {
  'profile.favorite': 1,
  'profile.bookmark': 1,
  'profile.attend': 1,
  'profile.seen': 1,
};

const FULL_FIELDS = {
  idapp: 1,
  lang: 1,
  index: 1,
};

const APORTADOR_FIELDS = {
  'useraport.perm': 1,
  'useraport.username': 1,
  'useraport.profile.img': 1,
};

// ===== FUNZIONI HELPER =====
function mergeFields(...fieldSets) {
  return Object.assign({}, ...fieldSets);
}

function getWhatToShow(idapp, username) {
  return mergeFields(BASE_FIELDS, ADMIN_FIELDS, PROFILE_PUBLIC, PROFILE_EXTENDED, PROFILE_CONTACT);
}

function getWhatToShow_Unknown(idapp, username) {
  return mergeFields(BASE_FIELDS, PROFILE_PUBLIC, PROFILE_SOCIAL);
}

UserSchema.statics.getWhatToShow_IfFriends = async function (idapp, username) {
  return mergeFields(BASE_FIELDS, PROFILE_PUBLIC, PROFILE_SOCIAL, {
    reported: 1,
    date_report: 1,
    username_who_report: 1,
    groups: 1,
  });
};

UserSchema.statics.getProjectUser = function () {
  return mergeFields(
    FULL_FIELDS,
    BASE_FIELDS,
    {
      ind_order: 1,
      surname: 1,
      lasttimeonline: 1,
      deleted: 1,
      reported: 1,
      date_report: 1,
      username_who_report: 1,
      sospeso: 1,
      email: 1,
      img: 1,
      tokenreg: 1,
      date_tokenreg: 1,
      made_gift: 1,
    },
    {
      'profile.teleg_id': 1,
      'profile.username_telegram': 1,
      'profile.firstname_telegram': 1,
      'profile.lastname_telegram': 1,
      'profile.ask_zoom_partecipato': 1,
      'profile.qualified': 1,
      'profile.qualified_2invitati': 1,
      'profile.saw_and_accepted': 1,
      'profile.email_paypal': 1,
      'profile.payeer_id': 1,
      'profile.advcash_id': 1,
      'profile.revolut': 1,
      'profile.link_payment': 1,
      'profile.note_payment': 1,
      'profile.paymenttypes': 1,
      'profile.cell': 1,
    }
  );
};

// ===== FUNZIONE PRINCIPALE OTTIMIZZATA =====
UserSchema.statics.getUserProfileByUsername = async function (idapp, username, myusername, usaSuperPower, myperm = '') {
  const User = this;

  // Determina il livello di permesso
  let perm = tools.Perm.PERM_NONE;

  if (username === myusername) {
    perm = tools.Perm.PERM_ALL;
  } else if ((await User.canHavePower(myperm)) && usaSuperPower) {
    perm = tools.Perm.PERM_ALL;
  } else if (await User.isMyFriend(idapp, username, myusername)) {
    perm = tools.Perm.PERM_FRIEND;
  }

  // Costruisci la proiezione basata sul permesso
  const whatToShow = getProjectionByPermission(perm);

  // Esegui la query
  const regexpusername = new RegExp(`^${username}$`, 'i');
  const myfind = {
    idapp,
    username: { $regex: regexpusername },
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  const query = buildAggregationPipeline(myfind, whatToShow);

  try {
    const ris = await User.aggregate(query);

    if (ris && ris.length > 0) {
      ris[0].profile.calc = await User.calcOtherByUser(idapp, ris[0]._id);
      return ris[0];
    }
  } catch (e) {
    return null;
  }

  return null;
};

// ===== FUNZIONI HELPER PER getUserProfileByUsername =====
function getProjectionByPermission(perm) {
  const baseProjection = mergeFields(FULL_FIELDS, BASE_FIELDS, PROFILE_PUBLIC, PROFILE_EXTENDED, APORTADOR_FIELDS, {
    surname: 1,
    lasttimeonline: 1,
    deleted: 1,
    sospeso: 1,
    reported: 1,
    date_report: 1,
    username_who_report: 1,
    email: 1,
    'profile.teleg_id': 1,
    'profile.firstname_telegram': 1,
    'profile.lastname_telegram': 1,
  });

  if (perm === tools.Perm.PERM_NONE) {
    // PERM_NONE: rimuovi i campi di contatto sensibili
    return baseProjection;
  }

  if (perm === tools.Perm.PERM_FRIEND) {
    // PERM_FRIEND: aggiungi i campi di contatto
    return mergeFields(baseProjection, PROFILE_CONTACT);
  }

  if (perm === tools.Perm.PERM_ALL) {
    // PERM_ALL: aggiungi tutti i campi inclusi quelli admin
    return mergeFields(baseProjection, PROFILE_CONTACT, {
      notask_verif: 1,
      mycities: 1,
      comune: 1,
    });
  }

  return baseProjection;
}

function buildAggregationPipeline(matchCondition, projection) {
  return [
    { $match: matchCondition },
    {
      $lookup: {
        from: 'cities',
        localField: 'profile.born_city_id',
        foreignField: '_id',
        as: 'mycities',
      },
    },
    {
      $lookup: {
        from: 'users',
        let: {
          idapp: '$idapp',
          user_name: '$aportador_solidario',
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [{ $eq: ['$username', '$$user_name'] }, { $eq: ['$idapp', '$$idapp'] }],
              },
            },
          },
        ],
        as: 'useraport',
      },
    },
    {
      $unwind: {
        path: '$useraport',
        preserveNullAndEmptyArrays: true,
      },
    },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: [{ $arrayElemAt: ['$mycities', 0] }, '$$ROOT'],
        },
      },
    },
    { $project: projection },
  ];
}

UserSchema.statics.getUserShortDataByUsername = async function (idapp, username) {
  const User = this;

  let regexp = new RegExp(`^${username}$`, 'i');

  const myrec = await User.findOne(
    {
      idapp: idapp,
      username: { $regex: regexp },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    this.getProjectUser()
  ).lean();

  if (myrec) {
    myrec.qualified = await User.isUserQualified7(idapp, myrec.username);
    myrec.numNaviEntrato = 0;
    // myrec.numinvitati = await ListaIngresso.getnumInvitati(idapp, myrec.username);
    // myrec.numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, myrec.username);
  }

  return myrec;
};

UserSchema.statics.getUserShortDataByUsernameTelegram = async function (idapp, username_telegram) {
  const User = this;

  let regexp = new RegExp(`^${username_telegram}$`, 'i');

  const myrec = await User.findOne(
    {
      idapp: idapp,
      'profile.username_telegram': { $regex: regexp },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    this.getProjectUser()
  ).lean();

  if (myrec) {
    myrec.qualified = await User.isUserQualified7(idapp, myrec.username);
    myrec.numNaviEntrato = 0;
    // myrec.numinvitati = await ListaIngresso.getnumInvitati(idapp, myrec.username);
    // myrec.numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, myrec.username);
  }

  return myrec;
};

UserSchema.statics.getDownlineByUsername = async function (idapp, username, includemyself, onlynumber) {
  if (username === undefined) return null;

  let arrrec = [];

  let myq = {
    idapp,
    aportador_solidario: username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  if (!includemyself) {
    myq = { ...myq, username: { $ne: username } };
  }

  return arrrec;
};

UserSchema.statics.getQueryQualified = function () {
  return [
    {
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    {
      $or: [
        {
          'profile.special_req': true,
        },
        {
          verified_email: true,
          'profile.teleg_id': { $gt: 1 },
          'profile.saw_and_accepted': shared_consts.ALL_SAW_AND_ACCEPTED,
          // 'profile.saw_zoom_presentation': true,
          $or: [
            { 'profile.link_payment': { $exists: true } },
            { 'profile.email_paypal': { $exists: true } },
            { 'profile.payeer_id': { $exists: true } },
            { 'profile.advcash_id': { $exists: true } },
            { 'profile.revolut': { $exists: true } },
          ],
          // 'profile.paymenttypes': { "$in": ['paypal'] },
          // $where: "this.profile.paymenttypes.length >= 1",
        },
      ],
    },
  ];
};

UserSchema.statics.isUserQualified7 = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
    $and: User.getQueryQualified(),
  };

  const myrec = await User.findOne(myquery);

  return !!myrec;
};

UserSchema.statics.isUserResidente = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOne(myquery);
  if (!!myrec) {
    return myrec.profile.socioresidente;
  } else {
    return false;
  }
};

UserSchema.statics.isUserConsiglio = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOne(myquery);
  if (!!myrec) {
    return myrec.profile.consiglio;
  } else {
    return false;
  }
};

UserSchema.statics.isUserVisuProjects = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOne(myquery);
  if (!!myrec) {
    return myrec.profile.socioresidente;
  } else {
    return false;
  }
};

UserSchema.statics.isUserAlreadyQualified = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
    'profile.qualified': { $exists: true, $eq: true },
  };

  const myrec = await User.findOne(myquery);

  return !!myrec;
};

UserSchema.statics.isUserAlreadyQualified_2Invitati = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
    'profile.qualified_2invitati': { $exists: true, $eq: true },
  };

  const myrec = await User.findOne(myquery);

  return !!myrec;
};

UserSchema.statics.setUserQualified = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOneAndUpdate(myquery, { $set: { 'profile.qualified': true } }, { new: false });

  return !!myrec;
};

UserSchema.statics.setAmmissioneByTokenAndUsername = async function (idapp, username) {
  const User = this;

  try {
    if (username === undefined) return false;

    const myquery = {
      idapp: idapp,
      username: username,
    };

    let creatoken = true;
    let token = '';

    const recfound = await User.findOne(myquery, { verified_by_aportador: 1, username: 1, token_da_ammettere: 1 });
    if (recfound) {
      if (recfound.token_da_ammettere) {
        // token già creato
        creatoken = false;
        token = recfound.token_da_ammettere;
      }
    }

    if (creatoken) {
      token = tools.getTokenRandom();
    } else {
      return recfound;
    }

    const rec = await User.findOneAndUpdate(
      myquery,
      { $set: { token_da_ammettere: token, date_token_ammettere: new Date() } },
      { new: true }
    );
    return rec;
  } catch (e) {
    console.error('Error setAmmissione', e);
  }
};

UserSchema.statics.findAmmissioneByTokenAndUsername = async function (idapp, token, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    token_da_ammettere: token,
  };

  const rec = await User.findOne(myquery, {
    verified_by_aportador: 1,
    username: 1,
    aportador_solidario: 1,
    idapp: 1,
    lang: 1,
  });
  return rec && username.toLowerCase() === rec.username.toLowerCase() ? rec : null;
};

UserSchema.statics.setVerifiedByAportador = async function (idapp, username, valuebool) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const userver = await User.findOne(myquery, { verified_by_aportador: 1 }).lean();

  let scrivi = true;
  if (userver) {
    scrivi = userver.verified_by_aportador !== valuebool;
  }
  if (scrivi) {
    const myrec = await User.findOneAndUpdate(myquery, { $set: { verified_by_aportador: valuebool } }, { new: false });

    return !!myrec;
  } else {
    return false;
  }
};

UserSchema.statics.setnotask_verif = async function (idapp, username, valuebool) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOneAndUpdate(myquery, { $set: { notask_verif: valuebool } }, { new: false });

  return !!myrec;
};

UserSchema.statics.setaportador_solidario = async function (idapp, username, usernameAportador) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOneAndUpdate(
    myquery,
    { $set: { aportador_solidario: usernameAportador } },
    { new: false }
  );

  return !!myrec;
};

UserSchema.statics.setNewsletterToAll = async function (idapp) {
  const User = this;

  return await User.updateMany(
    {
      idapp,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { $set: { news_on: true } },
    { new: false }
  );
};
UserSchema.statics.setNewsletter = async function (idapp, username, newsletter_on) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOneAndUpdate(myquery, { $set: { news_on: newsletter_on } }, { new: false });

  return !!myrec;
};

UserSchema.statics.setEmailErrata = async function (idapp, username, email_errata) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOneAndUpdate(myquery, { $set: { email_errata } }, { new: false });

  return !!myrec;
};

UserSchema.statics.isEmailErrata = async function (idapp, username) {
  const User = this;

  return await User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .then((rec) => {
      return rec ? rec.email_errata : false;
    })
    .catch((e) => {
      return false;
    });
};
UserSchema.statics.isNewsletterOn = async function (idapp, username) {
  const User = this;

  return await User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .then((rec) => {
      return rec ? rec.news_on : false;
    })
    .catch((e) => {
      console.error('isNewsletterOn', e);
      return false;
    });
};

UserSchema.statics.setVerifiedByAportadorToALL = async function () {
  return await User.updateMany({}, { $set: { verified_by_aportador: true } }, { new: false });
};

UserSchema.statics.setUserQualified_2Invitati = async function (idapp, username) {
  const User = this;

  if (username === undefined) return false;

  const myquery = {
    idapp: idapp,
    username: username,
  };

  const myrec = await User.findOneAndUpdate(myquery, { $set: { 'profile.qualified_2invitati': true } }, { new: false });

  return !!myrec;
};

/*
UserSchema.statics.isUserQualified9 = async function(idapp, username) {
  const User = this;

  if (username === undefined)
    return false;

  qualified = await User.isUserQualified7(idapp, username);
  // numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, username);

  return qualified && (numinvitatiattivi >= 2);

};
 */

/*
UserSchema.statics.getnumPaymentOk = function (idapp) {
  const User = this;

  return await User. countDocuments({
    idapp,
    $and: [
      {
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
        'profile.paymenttypes': { "$in": ['paypal'] },
      },
      {
        $or: [
          { 'profile.email_paypal': { $exists: true } },
          { 'profile.revolut': { $exists: true } },
        ]
      }
    ]
  });
};
*/

UserSchema.statics.getUsersNationalityQuery = function (idapp) {
  const query = [
    {
      $match: {
        idapp,
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: { _id: '$profile.nationality', count: { $sum: 1 } },
    },
    {
      $sort: { count: -1 },
    },
  ];
  return query;
};

UserSchema.statics.getindOrderDuplicate = function (idapp) {
  const User = this;

  return User.aggregate(User.getUsersNationalityQuery(idapp)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.findByLinkreg = function (idapp, linkreg) {
  const User = this;

  return User.findOne({
    linkreg: linkreg,
    idapp: idapp,
  });
};

UserSchema.statics.AportadorOrig = function (idapp, id) {
  const User = this;

  return User.findOne({
    _id: id,
    idapp: idapp,
  }).then((rec) => {
    if (rec) return rec.aportador_iniziale;
    else return '';
  });
};

UserSchema.statics.findByLinkTokenforgot = function (idapp, email, tokenforgot) {
  const User = this;

  return User.findOne({
    email: email,
    tokenforgot: tokenforgot,
    date_tokenforgot: { $gte: tools.IncDateNow(-1000 * 60 * 60 * 4) }, // 4 ore fa!
    idapp: idapp,
  });
};

UserSchema.statics.findByLinkTokenforgotCode = function (idapp, email, tokenforgot_code) {
  const User = this;

  return User.findOne({
    email,
    tokenforgot_code,
    date_tokenforgot: { $gte: tools.IncDateNow(-1000 * 60 * 60 * 4) }, // 4 ore fa!
    idapp,
  });
};

UserSchema.statics.createNewRequestPwd = function (idapp, email, code) {
  const User = this;

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

  console.log('createNewRequestPwd');

  if (code && code.length === 6) {
    return User.findByLinkTokenforgotCode(idapp, email, code)
      .then((user) => {
        if (user) return { ris: true, link: tools.getlinkRelativeRequestNewPassword(idapp, email, user.tokenforgot) };
        else return { ris: false };
      })
      .catch((e) => {
        console.log(' Err createNewRequestPwd', e.message);
        return res.status(400).send();
      });
  } else {
    console.log('findByEmail idapp: ', idapp, 'email', email);
    return User.findByEmail(idapp, email).then(async (user) => {
      if (!user) {
        return { ris: false };
      } else {
        // Creo il tokenforgot
        user.tokenforgot = jwt.sign({ _id: user._id.toHexString() }, process.env.SIGNCODE).toString();
        user.date_tokenforgot = new Date();
        user.tokenforgot_code = 100000 + Math.round(Math.random() * 899999);
        user.lasttimeonline = new Date();
        return await user.save().then(async () => {
          await sendemail.sendEmail_RequestNewPassword(
            user.lang,
            user,
            user.email,
            user.idapp,
            user.tokenforgot,
            user.tokenforgot_code
          );

          return { ris: true };
        });
      }
    });
  }
};

UserSchema.statics.createNewRequestPwdByUsernameAndGetLink = async function (idapp, username) {
  const User = this;

  const user = await User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  });

  if (user) {
    const additionalData = {
      code: user.username,
    };
    const prova = 'dasdas1231#11';
    // Creo il tokenforgot
    user.tokenforgot = jwt.sign({ _id: user._id.toHexString(), ...additionalData }, process.env.SIGNCODE).toString();
    user.date_tokenforgot = new Date();
    user.tokenforgot_code = 100000 + Math.round(Math.random() * 899999);
    user.lasttimeonline = new Date();
    user.code_pwd_reset = 0;

    return await user.save().then(() => {
      return tools.getlinkRequestNewPassword(idapp, user.email, user.tokenforgot, user.tokenforgot_code);
    });
  }

  return '';
};

UserSchema.statics.getifRegTokenIsValid = async function (idapp, tokenreg) {
  const User = this;

  let regexp = new RegExp(`^${tokenreg}$`, 'i');

  const user = await User.findOne({
    idapp,
    tokenreg: { $regex: regexp },
  });
  if (user && user.date_tokenreg) {
    return user.date_tokenreg > new Date().getTime();
  }
  return false;
};

UserSchema.statics.createNewReqRegistrationGetLink = async function (idapp, username) {
  const User = this;

  const user = await User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  });

  if (user) {
    if (true) {
      // Se è scaduto, ne crea uno nuovo
      // Creo il tokenforgot
      /*
      if (!user.date_tokenreg || (!user.tokenreg) || (user.tokenreg && (user.date_tokenreg < new Date().getTime()))) {

        let mycodestr = user._id.toHexString() + new Date().getTime().toString();
        user.tokenreg = jwt.sign(mycodestr, process.env.SIGNCODE).
          toString();

        if (user.tokenreg) {
          try {
            user.tokenreg = user.tokenreg.replaceAll('.', '');
            user.tokenreg = user.tokenreg.replaceAll('/', '');
            user.tokenreg = user.tokenreg.slice(-8);
          } catch (e) {
            console.error('err', e);
          }
        }
      }

      user.date_tokenreg = tools.AddDate(new Date(), 1);
      

      return await user.save().then(() => {
        return user.tokenreg;
      });

      */
    }
  }

  return '';
};

UserSchema.statics.findByEmail = function (idapp, email, onlyifVerifiedByAportador) {
  const User = this;

  return User.findOne({
    idapp: idapp,
    email: email,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  }).then((rec) => {
    /* if (rec && onlyifVerifiedByAportador) {
      if (tools.getAskToVerifyReg(idapp)) {
        if (!rec.verified_by_aportador)
          return null;

      }
    } */

    return rec;
  });
};

UserSchema.statics.getLastUser = function (idapp) {
  const User = this;

  return User.findOne({
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  }).sort({ index: -1 });
};

UserSchema.statics.findByIndex = function (idapp, index) {
  const User = this;

  try {
    // ++Todo: non mettere tutti i campi !!
    return User.findOne({
      idapp,
      index,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });
  } catch (e) {
    console.error(e.message);
  }
};

UserSchema.statics.findByOldOrder = function (idapp, old_order) {
  const User = this;

  try {
    return User.findOne({
      idapp,
      old_order,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });
  } catch (e) {
    console.error(e.message);
  }
};

UserSchema.pre('save', async function (next) {
  try {
    if (this.isNew) {
      try {
        const myrec = await User.findOne({ idapp: this.idapp }).limit(1).sort({ index: -1 });

        if (!!myrec) {
          this.index = myrec._doc.index + 1;
        } else {
          this.index = 1;
        }
      } catch (e) {
        this.index = 2;
      }
    }

    /*
    if (user.isModified('password')) {
      bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(user.password, salt, (err, hash) => {
          user.password = hash;
          next();
        });
      });
    } else {
      next();
    }
    */

    next();
  } catch (e) {
    console.error(e.message);
  }
});

UserSchema.methods.removeToken = function (token) {
  const user = this;

  return user.updateOne({
    $pull: {
      tokens: { token },
    },
  });
};

// TODO: Cancellare i token che non hanno refreshToken, ad esempio quando l'utente esce dal browser
// TODO: Cancellare i token con data_login meno di 2 giorni fa, per esempio se l'utente si è disconnesso dal browser ma è ancora online

UserSchema.statics.SvuotaTuttiGliAccessiOnlineConToken = async function (idapp) {
  const User = this;

  return await User.updateMany(
    { idapp },
    {
      $pull: {
        tokens: {
          $or: [{ refreshToken: { $exists: false } }, { refreshToken: { $exists: true, $eq: '' } }],
        },
      },
    }
  );
};

UserSchema.statics.getEmailByUsername = async function (idapp, username) {
  const User = this;

  return await User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .then((arrrec) => {
      return arrrec ? arrrec.email : '';
    })
    .catch((e) => {
      console.error('getEmailByUsername', e);
    });
};

UserSchema.statics.getUsernameById = async function (idapp, id) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      _id: id,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1 }
  )
    .then((myuser) => {
      return myuser ? myuser.username : '';
    })
    .catch((e) => {});
};

UserSchema.statics.getUsernameByEmail = async function (idapp, email) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      email,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1 }
  )
    .then((myuser) => {
      return myuser ? myuser.username : '';
    })
    .catch((e) => {});
};

UserSchema.statics.getUserById = function (idapp, id) {
  const User = this;

  return User.findOne({
    idapp,
    _id: id,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  });
};

UserSchema.statics.getUserByUsername = function (idapp, username) {
  const User = this;

  return User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  });
};

UserSchema.statics.getUserByUsernameTelegram = function (idapp, username_telegram) {
  const User = this;

  if (username_telegram[0] === '@') {
    username_telegram = username_telegram.substring(1);
  }

  let regexp = new RegExp(`^${username_telegram}$`, 'i');

  return User.findOne({
    idapp,
    'profile.username_telegram': { $regex: regexp },
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  });
};

UserSchema.statics.isMyFriend = async function (idapp, username, myusername) {
  const myfriends = await User.getUsernameFriendsByUsername(idapp, myusername);
  if (myfriends) {
    return await myfriends.includes(username);
  } else {
    return false;
  }
};

UserSchema.statics.isMyHandShake = async function (idapp, username, myusername) {
  const myhandshake = await User.getUsernameHandShakeByUsername(idapp, myusername);
  if (myhandshake) {
    return await myhandshake.includes(username);
  } else {
    return false;
  }
};

UserSchema.statics.getArrUsernameFromFieldByUsername = async function (idapp, username, field, subfield) {
  const myobj = {};
  myobj[field + '.' + subfield + '.username'] = 1;

  const rec = await User.findOne(
    {
      idapp,
      username: username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    myobj
  );

  return rec ? rec[field][subfield].map((m) => m.username) : [];
};

UserSchema.statics.getUsernameReqFriendsByUsername = async function (idapp, username) {
  return await this.getArrUsernameFromFieldByUsername(idapp, username, 'profile', 'req_friends');
};

UserSchema.statics.getUsernameFriendsByUsername = async function (idapp, username) {
  return await this.getArrUsernameFromFieldByUsername(idapp, username, 'profile', 'friends');
};

UserSchema.statics.getUsernameHandShakeByUsername = async function (idapp, username) {
  return await this.getArrUsernameFromFieldByUsername(idapp, username, 'profile', 'handshake');
};

UserSchema.statics.getUsernameGroupsByUsername = async function (idapp, username) {
  return await this.getArrUsernameFromFieldByUsername(idapp, username, 'profile', 'mygroups');
};
UserSchema.statics.getUsernameCircuitsByUsername = async function (idapp, username) {
  return await this.getArrUsernameFromFieldByUsername(idapp, username, 'profile', 'mycircuits');
};

// Rimuovo l'Amicizia
UserSchema.statics.removeUser = async function (id) {
  const User = this;

  if (id) {
    return await User.deleteMany({ _id: id });
  } else {
    return false;
  }
};
UserSchema.statics.removeFriend = async function (idapp, username, usernameDest) {
  return await User.updateOne(
    { idapp, username },
    { $pull: { 'profile.friends': { username: { $in: [usernameDest] } } } }
  );
};

// Rimuovo l'Amicizia
UserSchema.statics.removeHandShake = async function (idapp, username, usernameDest) {
  return await User.updateOne(
    { idapp, username },
    { $pull: { 'profile.handshake': { username: { $in: [usernameDest] } } } }
  );
};

// Rimuovo il Gruppo
UserSchema.statics.removeFromMyGroups = async function (idapp, username, groupnameDest) {
  return await User.updateOne(
    { idapp, username },
    { $pull: { 'profile.mygroups': { groupname: { $in: [groupnameDest] } } } }
  );
};

// Rimuovo il Circuito
UserSchema.statics.removeFromCircuits = async function (idapp, username, circuitname) {
  // Elimina la richiesta (se esiste):
  update = { $pull: { req_users: { username: { $in: [username] } } } };
  await Circuit.updateOne({ idapp, name: circuitname }, update);

  const ris = await User.updateOne(
    { idapp, username },
    { $pull: { 'profile.mycircuits': { circuitname: { $in: [circuitname] } } } }
  );

  const circuitId = await Circuit.getCircuitIdByName(idapp, circuitname);
  let remove = false;

  const circuit = await Circuit.findOne({ idapp, name: circuitname }).lean();
  if (circuit) {
    // Invio la notifica agli amministratori del circuito
    const title = `L\'utente ${username} è uscito dal ${circuitname}`;
    const msg = ``;
    await tools.sendNotifToCircuitsAdmin(idapp, circuit, true, title, msg, '');
  }

  // Se il mio account non è stato utilizzato, allora lo cancello anche questo
  const myaccount = await Account.getAccountByUsernameAndCircuitId(idapp, username, circuitId, false, false, '', '');
  if (myaccount && myaccount.totTransato === 0) {
    remove = true;
  } else {
    remove = false;
  }

  if (remove) {
    await Account.removeAccount(myaccount._id);
  }

  return ris;
};

// Aggiungo il Circuito
UserSchema.statics.addCircuitToUser = async function (idapp, usernameOrig, circuitname, confido, groupname, contocom) {
  let ris = null;

  if (groupname) {
    ris = await MyGroup.addCircuitFromGroup(idapp, groupname, circuitname);

    // Elimina la richiesta:
    update = { $pull: { req_groups: { groupname: { $in: [groupname] } } } };
    await Circuit.updateOne({ idapp, name: circuitname }, update);

    // Elimina eventualmente se era bloccato:
    update = { $pull: { refused_groups: { groupname: { $in: [groupname] } } } };
    await Circuit.updateOne({ idapp, name: circuitname }, update);
  } else {
    // prima di aggiungerlo controlla se esiste già !

    const token = tools.getTokenRandom();

    let update = null;

    const updateparams = {
      $set: {
        'profile.mycircuits.$[elem].date': new Date(),
        'profile.mycircuits.$[elem].token': token,
      },
    };

    let ris = await User.updateOne(
      { idapp, username: usernameOrig, 'profile.mycircuits.circuitname': circuitname },
      updateparams,
      { arrayFilters: [{ 'elem.circuitname': circuitname }] }
    );

    // Se non ha modificato nulla (nModified === 0), significa che non esiste, quindi lo aggiungiamo
    if (ris.modifiedCount === 0) {
      const insertUpdate = {
        $push: {
          'profile.mycircuits': {
            circuitname,
            date: new Date(),
            token,
          },
        },
      };

      ris = await User.updateOne({ idapp, username: usernameOrig }, insertUpdate);
    }

    if (confido) {
      // Elimina la richiesta:
      update = { $pull: { req_users: { username: { $in: [usernameOrig] } } } };
      await Circuit.updateOne({ idapp, name: circuitname }, update);
    }

    // Elimina eventualmente se era bloccato:
    update = { $pull: { refused_users: { username: { $in: [usernameOrig] } } } };
    await Circuit.updateOne({ idapp, name: circuitname }, update);
  }

  await Account.createAccount(idapp, usernameOrig, circuitname, confido, groupname, contocom);

  return ris;
};

// Rimuovo il Gruppo per Tutti gli Utenti
UserSchema.statics.getCircuitByTokenAndUsername = async function (idapp, username, token) {
  try {
    const user = await User.findOne({
      idapp,
      username,
      'profile.mycircuits.token': token,
    });

    const foundCircuit = user?.profile?.mycircuits?.find((c) => c.token === token);
    if (foundCircuit) {
      return { circuitname: foundCircuit.circuitname, user };
    }
  } catch (e) {
    return { circuitname: '', user: null };
  }
  return { circuitname: '', user: null };
};

UserSchema.statics.removeAllUsersFromMyGroups = async function (idapp, groupnameDest) {
  return await User.updateMany({ idapp }, { $pull: { 'profile.mygroups': { groupname: { $in: [groupnameDest] } } } });
};

// Rimuovo il Circuito per Tutti gli Utenti
UserSchema.statics.removeAllUsersFromMyCircuits = async function (idapp, circuitname) {
  return await User.updateMany({ idapp }, { $pull: { 'profile.mycircuits': { circuitname: { $in: [circuitname] } } } });
};

// Rimuovo la Richiesta di Amicizia
UserSchema.statics.removeReqFriend = async function (idapp, username, usernameDest) {
  return await User.updateOne(
    { idapp, username: username },
    { $pull: { 'profile.req_friends': { username: { $in: [usernameDest] } } } }
  );
};

// Aggiungo il Partecipa
UserSchema.statics.addAttend = async function (req, idapp, username, id, tab, num) {
  const ris = await User.updateOne({ idapp, username }, { $push: { 'profile.attend': { id, tab, num } } });

  const { SendNotif } = require('../models/sendnotif');

  const globalTables = require('../tools/globalTables');

  // Invia una Notifica al Destinatario
  const recObjCreator = await globalTables.getUserCreatorByNumTabAndId(idapp, id, tab);

  if (recObjCreator) {
    await SendNotif.createNewNotifToSingleUser(
      req,
      null,
      { usernameDest: recObjCreator.username, recObjCreator, username_action: req.user.username },
      false,
      shared_consts.TypeNotifs.TYPEDIR_EVENTS,
      shared_consts.TypeNotifs.ID_EVENTS_ATTEND
    );
  }

  return ris;
};
// Rimuovo il Bookmark
UserSchema.statics.removeBookmark = async function (idapp, username, id, tab) {
  return await User.updateOne({ idapp, username }, { $pull: { 'profile.bookmark': { id: { $in: [id] }, tab } } });
};

// Aggiungo il Bookmark
UserSchema.statics.addBookmark = async function (idapp, username, id, tab) {
  return await User.updateOne({ idapp, username }, { $push: { 'profile.bookmark': { id, tab } } });
};
UserSchema.statics.setLinkToVerifiedEmail = async function (idapp, username, link_verif_email) {
  return await User.updateOne({ idapp, username }, { $set: { link_verif_email } });
};
UserSchema.statics.findByLinkVerifEmail = async function (idapp, link_verif_email) {
  return await User.findOne({ idapp, link_verif_email });
};
// Rimuovo il Partecipa
UserSchema.statics.removeAttend = async function (idapp, username, id, tab) {
  return await User.updateOne({ idapp, username }, { $pull: { 'profile.attend': { id: { $in: [id] }, tab } } });
};

// Aggiungo il Bookmark
UserSchema.statics.addSeen = async function (idapp, username, id, tab) {
  return await User.updateOne({ idapp, username }, { $push: { 'profile.seen': { id, tab } } });
};

UserSchema.statics.setFriendsCmd = async function (req, idapp, usernameOrig, usernameDest, cmd, value, disablenotif) {
  const { SendNotif } = require('../models/sendnotif');

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

  if (!req) {
    req = tools.getReqByPar(idapp, usernameOrig);
  }

  let userDest = null;
  if (usernameDest) userDest = await User.getUserShortDataByUsername(idapp, usernameDest);

  const username_action = req.user.username;
  let username_worked = usernameDest;

  let ris = null;
  let lang = '';
  let user = null;
  if (usernameOrig) {
    user = await User.getUserShortDataByUsername(idapp, usernameOrig);
  }
  lang = user ? user.lang : '';
  let update = {};
  try {
    if (cmd === shared_consts.FRIENDSCMD.SETTRUST) {
      let msgOrig = '';
      let msgDest = '';
      // Aggiorna true se lo accetti e false se non lo accetti
      const ris = await User.updateOne(
        { idapp, username: usernameDest },
        { $set: { verified_by_aportador: value, trust_modified: new Date() } },
        { new: false }
      );

      if (value) {
        // Aggiungi alle amicizie
        // await this.setFriendsCmd(req, idapp, usernameOrig, usernameDest,
        //  shared_consts.FRIENDSCMD.SETFRIEND, value);

        msgOrig = i18n.__(
          { phrase: "✅ Hai Ammesso l'accesso alla App a %s !", locale: userDest.lang },
          userDest.username
        );
        msgDest = i18n.__({ phrase: '✅ Sei stato Ammesso correttamente da %s!', locale: lang }, usernameOrig);
        msgAdmin = i18n.__(
          { phrase: '✅ %s è stato Ammesso/a correttamente (da %s) tramite Email!', locale: userDest.lang },
          userDest.username,
          usernameOrig
        );
      } else {
        msgOrig = i18n.__(
          { phrase: "🚫 Hai rifiutato l'accesso alla App di RISO da parte di %s!", locale: userDest.lang },
          userDest.username
        );
        msgDest = i18n.__(
          {
            phrase:
              "🚫 Ti è stato rifiutato l'accesso. Probabilmente l'username con cui ti sei registrato non ti conosce. (%s) !<br>Contatta l'Assistenza Tecnica.",
            locale: lang,
          },
          usernameOrig
        );
        msgAdmin = i18n.__(
          { phrase: "🚫 %s ha rifiutato l'accesso alla App a %s !", locale: userDest.lang },
          usernameOrig,
          userDest.username
        );
      }

      await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgOrig);
      await telegrambot.sendMsgTelegram(idapp, userDest.username, msgDest);

      // Invia questo msg anche all'Admin
      await telegrambot.sendMsgTelegramToTheAdmin(idapp, msgAdmin, true);

      // telegrambot.askConfirmationUser(user.idapp, shared_consts.CallFunz.REGISTRATION, user, '', '', '', '');

      return ris;
    } else if (cmd === shared_consts.FRIENDSCMD.SETFRIEND) {
      // Aggiungo l'Amicizia a me
      const foundIfAlreadyFriend = await User.findOne(
        {
          idapp,
          username: usernameOrig,
          'profile.friends': {
            $elemMatch: { username: { $eq: usernameDest } },
          },
        },
        { _id: 1 }
      ).lean();

      if (!foundIfAlreadyFriend) {
        update = {
          $push: {
            'profile.friends': {
              username: usernameDest,
              date: new Date(),
            },
          },
        };
        ris = await User.updateOne({ idapp, username: usernameOrig }, update);

        if (!disablenotif) {
          // Send a notification to the DESTINATION FRIENDSHIP !
          let req = tools.getReqByPar(idapp, usernameOrig);
          await SendNotif.createNewNotifToSingleUser(
            req,
            null,
            { usernameDest },
            true,
            shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
            shared_consts.TypeNotifs.ID_FRIENDS_ACCEPTED_MY_REQUEST
          );

          // Send a notification to the SENDER FRIENDSHIP !
          req = tools.getReqByPar(idapp, usernameDest);
          await SendNotif.createNewNotifToSingleUser(
            req,
            null,
            { usernameDest: usernameOrig },
            true,
            shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
            shared_consts.TypeNotifs.ID_FRIENDS_ACCEPTED
          );
        }

        update = { $pull: { 'profile.req_friends': { username: { $in: [usernameDest] } } } };
        ris = await User.updateOne({ idapp, username: usernameOrig }, update);

        if (ris) {
          try {
            if (!disablenotif) {
              const userDest = await User.getRecLangAndIdByUsername(idapp, usernameDest);
              const user = await User.getRecLangAndIdByUsername(idapp, usernameOrig);
              const msgDest = i18n.__(
                { phrase: '✅ %s accepted your Friendship request !', locale: userDest.lang },
                usernameDest
              );
              const msgOrig = i18n.__(
                { phrase: "✅ You have accepted %s' Friendship request!", locale: user.lang },
                usernameOrig
              );

              await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgDest);
              await telegrambot.sendMsgTelegram(idapp, usernameDest, msgOrig);
            }
          } catch (e) {
            console.error('Notification : ', e);
          }
        }
      }

      // Controlla se lui aveva già la mia amicizia
      const foundIfAlreadyFriend2 = await User.findOne(
        {
          idapp,
          username: usernameDest,
          'profile.friends': {
            $elemMatch: { username: { $eq: usernameOrig } },
          },
        },
        { _id: 1 }
      ).lean();

      if (!foundIfAlreadyFriend2) {
        update = {
          $push: {
            'profile.friends': {
              username: usernameOrig,
              date: new Date(),
            },
          },
        };
        ris = await User.updateOne({ idapp, username: usernameDest }, update);

        this.removeReqFriend(idapp, usernameDest, usernameOrig); // Rimuovo l'Amicizia da me
        this.removeReqFriend(idapp, usernameOrig, usernameDest); // Rimuovo l'Amicizia da te
      }
      //if (ris) {
      ris = await User.getInfoFriendByUsername(idapp, usernameDest);
      //}
    } else if (cmd === shared_consts.FRIENDSCMD.SETHANDSHAKE) {
      // Aggiungo la Stretta di mano a lui
      const foundIfAlreadyHandshake = await User.findOne(
        {
          idapp,
          username: usernameDest,
          'profile.handshake': {
            $elemMatch: { username: { $eq: usernameOrig } },
          },
        },
        { _id: 1 }
      ).lean();

      let rec = null;

      if (!foundIfAlreadyHandshake) {
        update = {
          $push: {
            'profile.handshake': {
              username: usernameOrig,
              date: new Date(),
            },
          },
        };

        const already_stretta = await User.isMyHandShake(idapp, usernameDest, usernameOrig);
        const already_stretta_orig = await User.isMyHandShake(idapp, usernameOrig, usernameDest);

        rec = await User.updateOne({ idapp, username: usernameDest }, update);

        tools.sendNotificationByUsername(idapp, usernameDest, cmd, true, usernameOrig);
        let req = tools.getReqByPar(idapp, usernameOrig);

        if (rec) {
          try {
            if (!disablenotif) {
              const userDest = await User.getRecLangAndIdByUsername(idapp, usernameDest);
              const user = await User.getRecLangAndIdByUsername(idapp, usernameOrig);

              let msgDest = i18n.__({ phrase: 'HANDSHAKE_SENT_FROM_YOU', locale: userDest.lang }, usernameDest);
              if (already_stretta)
                msgDest = i18n.__({ phrase: 'HANDSHAKE_CONFIRMED', locale: userDest.lang }, usernameDest);

              let phrase = 'HANDSHAKE_SET';
              if (already_stretta) {
                phrase = 'HANDSHAKE_ACCEPTED';
              }

              const msgOrig = i18n.__({ phrase, locale: user.lang }, usernameOrig);

              await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgDest);
              if (phrase !== 'HANDSHAKE_SET') {
                await telegrambot.sendMsgTelegram(idapp, usernameDest, msgOrig);
              }
            }
          } catch (e) {
            console.error('Notification : ', e);
          }
        }

        // Send a notification to the DESTINATION HANDSHAKE !
        // await SendNotif.createNewNotifToSingleUser(req, null, { usernameDest }, false, shared_consts.TypeNotifs.TYPEDIR_HANDSHAKE,
        //   shared_consts.TypeNotifs.ID_HANDSHAKE_ACCEPTED);
      }

      const userprofile = await User.getInfoFriendByUsername(idapp, usernameDest);
      const myuser = await User.getInfoFriendByUsername(idapp, usernameOrig);
      ris = { rec, userprofile, myuser };
    } else if (cmd === shared_consts.FRIENDSCMD.REQFRIEND) {
      // Aggiungo la richiesta di Amicizia a me
      const foundIfAlreadyAskFriend = await User.findOne(
        {
          idapp,
          username: usernameDest,
          'profile.req_friends': {
            $elemMatch: { username: { $eq: usernameOrig } },
          },
        },
        { _id: 1 }
      ).lean();

      if (value) {
        if (!foundIfAlreadyAskFriend) {
          update = {
            $push: {
              'profile.req_friends': {
                username: usernameOrig,
                date: new Date(),
              },
            },
          };
          ris = await User.updateOne({ idapp, username: usernameDest }, update);
        }
        if (ris) {
          // Invia una notifica alla persona
          tools.sendNotificationByUsername(idapp, usernameDest, cmd, true, usernameOrig);
        }

        if (foundIfAlreadyAskFriend) {
          ris = await User.getInfoFriendByUsername(idapp, usernameDest);
        }
      }
    } else if (cmd === shared_consts.FRIENDSCMD.REMOVE_FROM_MYFRIENDS) {
      // Rimuovi anche le eventuali richieste di Amicizia !
      await this.removeReqFriend(idapp, usernameDest, usernameOrig); // Rimuovo la Richiesta di Amicizia da lui
      await this.removeReqFriend(idapp, usernameOrig, usernameDest); // Rimuovo la Richiesta di Amicizia da me

      await this.removeFriend(idapp, usernameDest, usernameOrig); // Rimuovo l'Amicizia da lui
      ris = await this.removeFriend(idapp, usernameOrig, usernameDest); // Rimuovo l'Amicizia da me
    } else if (cmd === shared_consts.FRIENDSCMD.REMOVE_FROM_MYHANDSHAKE) {
      ris = await this.removeHandShake(idapp, usernameDest, usernameOrig); // Rimuovo l'Amicizia da lui
    } else if (cmd === shared_consts.FRIENDSCMD.CANCEL_REQ_FRIEND) {
      // CREATE NOTIFICATION IN TABLE SENDNOTIF
      const req = tools.getReqByPar(idapp, usernameOrig);
      await SendNotif.createNewNotifToSingleUser(
        req,
        null,
        { usernameDest },
        true,
        shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
        shared_consts.TypeNotifs.ID_FRIENDS_REFUSED
      );

      ris = true;
    } else if (cmd === shared_consts.FRIENDSCMD.BLOCK_USER) {
      await this.removeReqFriend(idapp, usernameDest, usernameOrig); // Rimuovo la Richiesta di Amicizia da lui
      await this.removeReqFriend(idapp, usernameOrig, usernameDest); // Rimuovo la Richiesta di Amicizia da me

      await this.removeFriend(idapp, usernameDest, usernameOrig); // Rimuovo l'Amicizia da lui
      await this.removeFriend(idapp, usernameOrig, usernameDest); // Rimuovo l'Amicizia da me

      // Blocco la persona
      ris = await User.updateOne(
        { idapp, username: usernameDest },
        {
          $set: {
            blocked: true,
            sospeso: true,
            username_who_block: usernameOrig,
            date_blocked: new Date(),
          },
        }
      );
    } else if (cmd === shared_consts.FRIENDSCMD.REPORT_USER) {
      username_worked = usernameDest;

      // Segnalo la persona
      ris = await User.updateOne(
        { idapp, username: username_worked },
        {
          $set: {
            reported: true,
            username_who_report: usernameOrig,
            date_report: new Date(),
          },
        }
      );

      if (ris) {
        // Send a notification to the DESTINATION!
        // Sei stato segnalato da %s per comportamenti non idonei. Contatta %s per chiarimenti
        await SendNotif.createNewNotifToSingleUser(
          req,
          null,
          { username_worked, usernameDest, username_action },
          false,
          shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
          shared_consts.TypeNotifs.ID_FRIENDS_REPORTED
        );

        // Send a notification to the SENDER !
        // Hai segnalato %s da %s per comportamenti non idonei.
        await SendNotif.createNewNotifToSingleUser(
          req,
          null,
          { username_worked, usernameDest: username_action, username_action },
          false,
          shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
          shared_consts.TypeNotifs.ID_FRIENDS_REPORTED
        );

        if (usernameOrig !== shared_consts.ADMIN_USER_SERVER) {
          // Send a notification to the Admin
          await SendNotif.createNewNotifToSingleUser(
            req,
            null,
            { username_worked, usernameDest: shared_consts.ADMIN_USER_SERVER, username_action, isAdmin: true },
            false,
            shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
            shared_consts.TypeNotifs.ID_FRIENDS_REPORTED
          );
        }
      }
    } else if (cmd === shared_consts.FRIENDSCMD.DELETE_USER) {
      const id = await User.findOne({ idapp, username: usernameDest }, { username: 1 });
      // Cancella Utente
      ris = await User.removeUser(id);
    } else if (cmd === shared_consts.FRIENDSCMD.UNBLOCK_USER) {
      username_worked = usernameDest;

      // Sblocco la persona
      ris = await User.updateOne(
        { idapp, username: username_worked },
        {
          $set: {
            reported: false,
          },
        }
      );

      if (ris) {
        // Send a notification to the DESTINATION!
        await SendNotif.createNewNotifToSingleUser(
          req,
          null,
          { username_worked, usernameDest, username_action },
          false,
          shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
          shared_consts.TypeNotifs.ID_FRIENDS_UNBLOCKED
        );

        // Send a notification to the SENDER !
        // Hai segnalato %s da %s per comportamenti non idonei.
        await SendNotif.createNewNotifToSingleUser(
          req,
          null,
          { username_worked, usernameDest: username_action, username_action },
          false,
          shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
          shared_consts.TypeNotifs.ID_FRIENDS_UNBLOCKED
        );

        if (usernameOrig !== shared_consts.ADMIN_USER_SERVER) {
          // Send a notification to the Admin
          await SendNotif.createNewNotifToSingleUser(
            req,
            null,
            { username_worked, usernameDest: shared_consts.ADMIN_USER_SERVER, username_action, isAdmin: true },
            false,
            shared_consts.TypeNotifs.TYPEDIR_FRIENDS,
            shared_consts.TypeNotifs.ID_FRIENDS_UNBLOCKED
          );
        }
      }
    }
  } catch (e) {
    console.error('Error: ', e);
  }

  return ris;
};

UserSchema.statics.sendCmd = async function (req, idapp, usernameOrig, usernameDest, cmd, value, disablenotif) {
  const { SendNotif } = require('../models/sendnotif');

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

  const cl = telegrambot.getclTelegByidapp(idapp);

  if (!req) {
    req = tools.getReqByPar(idapp, usernameOrig);
  }

  const myuser = await User.getUserByUsername(idapp, usernameOrig);
  const recuserDest = await User.getUserByUsername(idapp, usernameDest);

  const langdest = recuserDest.lang;
  const telegid = recuserDest.profile.teleg_id;

  let userId = recuserDest._id;
  let title = tools.getNomeAppByIdApp(idapp);
  let keyb = null;
  let descr = '';

  let send_notif = false;
  let send_msgTelegramBot = false;
  let actions = [];
  let popupOnApp = '';

  let ris = null;

  try {
    if (cmd === shared_consts.CallFunz.ENTRA_RIS_ITALIA) {
      mycircuitOrig = await Circuit.getCircuitMyProvince(idapp, usernameOrig);

      let enableOnlyIf1CircuitExist = false;
      let foundIfAlreadyCircuit = false;

      if (mycircuitOrig) {
        // Fai la richiesta solo se appartieni già al tuo Circuito Territoriale
        foundIfAlreadyCircuit = await User.ifAlreadyInCircuit(idapp, usernameOrig, mycircuitOrig);
      }

      if (!foundIfAlreadyCircuit) {
        // Se non sono dentro ad un circuito Provinciale, setto come flag la possibilità di inserire il Circuito ITA
        await User.setInseriscimiAncheInCircuitoITA(idapp, usernameOrig);
      } else {
        descr = i18n.__(
          { phrase: 'SENDMSG_ENTRA_IN_RISO_ITALIA', locale: langdest },
          usernameDest,
          usernameOrig,
          mycircuitOrig
        );
        msgtelegram = descr;

        openUrl = '/circuit/ris_italia';
        bottone = i18n.__({ phrase: 'CIRCUIT_OPEN_RISITALIA', locale: langdest });
        tag = 'risitalia';

        send_notif = true;
        send_msgTelegramBot = true;

        keyb = cl.getInlineKeyboard(langdest, [
          {
            text: bottone,
            url: tools.getHostByIdApp(idapp) + openUrl,
            // callback_data: InlineConferma.RISPOSTA_SI + myfunc + tools.SEP + myuser.username + tools.SEP + '' + tools.SEP + '' + tools.SEP +
            //   groupid,
          },
        ]);

        popupOnApp = 'Messaggio inviato al destinatario';
      }
    }

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

    // Invia Msg
    if (send_msgTelegramBot && msgtelegram) {
      await telegrambot.local_sendMsgTelegramByIdTelegram(
        idapp,
        telegid,
        msgtelegram,
        undefined,
        undefined,
        true,
        keyb
      );
    }

    const userprofile = await User.getInfoFriendByUsername(idapp, usernameDest);
    const myuser = await User.getInfoFriendByUsername(idapp, usernameOrig);

    ris = { userprofile, myuser, popupOnApp, result: true };
  } catch (e) {
    popupOnApp = e;
    ris = { popupOnApp };
    console.error('Error sendCmd: ', e);
  }

  return ris;
};

UserSchema.statics.ifAlreadyInGroup = async function (idapp, usernameOrig, groupnameDest) {
  // Controllo se è stato già inserito
  return await User.findOne({
    idapp,
    username: usernameOrig,
    'profile.mygroups': {
      $elemMatch: { groupname: { $eq: groupnameDest } },
    },
  }).lean();
};

UserSchema.statics.ifAlreadyInCircuit = async function (idapp, usernameOrig, circuitname) {
  // Controllo se è stato già inserito
  return await User.findOne({
    idapp,
    username: usernameOrig,
    'profile.mycircuits': {
      $elemMatch: { circuitname: { $eq: circuitname } },
    },
  }).lean();
};

UserSchema.statics.ifAlreadyInCircuitItalia = async function (idapp, usernameOrig) {
  try {
    const circuitoItalia = await Circuit.getCircuitoItalia(idapp);

    if (circuitoItalia) {
      const circuitname = circuitoItalia.name;

      // Controllo se è stato già inserito in Circuito Italia
      return await User.findOne({
        idapp,
        username: usernameOrig,
        'profile.mycircuits': {
          $elemMatch: { circuitname: { $eq: circuitname } },
        },
      }).lean();
    }

    return false;
  } catch (e) {
    console.error('ifAlreadyInCircuitItalia', e);
  }
};

//** Get true if in 'profile.mycircuits' exist at least one circuit */
UserSchema.statics.ExistAtLeastOneCircuit = async function (idapp, username) {
  // Controllo se è stato più inserito
  return await User.countDocuments({
    idapp,
    username,
    'profile.mycircuits': {
      $ne: [],
    },
  });
};

UserSchema.statics.countUsersInGroup = async function (idapp, groupnameDest) {
  // Controllo se è stato già inserito
  return await User.countDocuments({
    idapp,
    'profile.mygroups': {
      $elemMatch: { groupname: { $eq: groupnameDest } },
    },
  });
};

UserSchema.statics.countUsersInCircuit = async function (idapp, circuitname) {
  // Controllo se è stato già inserito
  return await User.countDocuments({
    idapp,
    'profile.mycircuits': {
      $elemMatch: { circuitname: { $eq: circuitname } },
    },
  });
};

UserSchema.statics.setGroupsCmd = async function (idapp, usernameOrig, groupnameDest, cmd, value, username_action) {
  let ris = null;
  let update = {};
  try {
    if (cmd === shared_consts.GROUPSCMD.SETGROUP) {
      const foundIfAlreadyGroup = await this.ifAlreadyInGroup(idapp, usernameOrig, groupnameDest);

      if (!foundIfAlreadyGroup) {
        update = {
          $push: {
            'profile.mygroups': {
              groupname: groupnameDest,
              date: new Date(),
            },
          },
        };
        ris = await User.updateOne({ idapp, username: usernameOrig }, update);

        // Elimina la richiesta:
        update = { $pull: { req_users: { username: { $in: [usernameOrig] } } } };
        await MyGroup.updateOne({ idapp, groupname: groupnameDest }, update);

        // Elimina eventualmente se era bloccato:
        update = { $pull: { refused_users: { username: { $in: [usernameOrig] } } } };
        await MyGroup.updateOne({ idapp, groupname: groupnameDest }, update);
      } else {
        ris = false;
      }
      if (ris) {
        // Invia una notifica alla persona e agli Admin
        tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, value, true, username_action);
        ris = await MyGroup.getInfoGroupByGroupname(idapp, groupnameDest);
      }
    } else if (cmd === shared_consts.GROUPSCMD.REQGROUP) {
      // Aggiungo la richiesta di Gruppo a me
      const foundIfAlreadyAskGroup = await MyGroup.findOne({
        idapp,
        groupname: groupnameDest,
        req_users: {
          $elemMatch: { username: { $eq: usernameOrig } },
        },
      });

      if (value) {
        if (!foundIfAlreadyAskGroup) {
          update = {
            $push: {
              req_users: {
                username: usernameOrig,
                date: new Date(),
              },
            },
          };
          ris = await MyGroup.updateOne({ idapp, groupname: groupnameDest }, update);
        }
        if (ris) {
          // Invia una notifica alla persona
          await tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, true, true, username_action);
        }
      } else {
        if (foundIfAlreadyAskGroup) {
          ris = await this.removeFromMyGroups(idapp, usernameOrig, groupnameDest); // Rimuovo il Gruppo da me

          // Invia una notifica alla persona
          await tools.sendNotificationByGroupname(
            idapp,
            usernameOrig,
            groupnameDest,
            cmd,
            false,
            true,
            username_action
          );
        }
      }

      if (ris) {
        ris = await MyGroup.getInfoGroupByGroupname(idapp, groupnameDest);
      }
    } else if (cmd === shared_consts.GROUPSCMD.REMOVE_FROM_MYGROUP) {
      // Remove if is also an Admin
      await MyGroup.removeAdminOfMyGroup(idapp, usernameOrig, groupnameDest);

      ris = await User.removeFromMyGroups(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me
      console.log('ris', ris);

      // Invia una notifica alla persona
      await tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, false, true, username_action);
    } else if (cmd === shared_consts.GROUPSCMD.DELETE_GROUP) {
      ris = await User.removeFromMyGroups(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me

      if (ris) {
        // Invia una notifica alla persona e agli Admin
        await tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, false, true, username_action);
      }
      ris = await MyGroup.deleteGroup(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me
      console.log('ris', ris);
    } else if (cmd === shared_consts.GROUPSCMD.CANCEL_REQ_GROUP) {
      ris = await MyGroup.removeReqGroup(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me
    } else if (cmd === shared_consts.GROUPSCMD.REFUSE_REQ_GROUP) {
      ris = await MyGroup.removeReqGroup(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me

      ris = await MyGroup.refuseReqGroup(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me
    } else if (cmd === shared_consts.GROUPSCMD.BLOCK_GROUP) {
      await User.removeFromMyGroups(idapp, usernameOrig, groupnameDest); // Rimuovo l'Amicizia da me

      // Blocco il Gruppo
      ris = await MyGroup.updateOne(
        { idapp, groupname: groupnameDest },
        {
          $set: {
            blocked: true,
            username_who_block: usernameOrig,
            date_blocked: new Date(),
          },
        }
      );
      //++Todo: Send Notification to Admin and Group's manager
      tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, false, true, username_action);
    } else if (cmd === shared_consts.GROUPSCMD.ADDADMIN_OFMYGROUP) {
      ris = await MyGroup.addToAdminOfMyGroup(idapp, usernameOrig, groupnameDest); // Rimuovo la richiesta di entrare nel gruppo

      // Invia una notifica alla persona
      await tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, false, true, username_action);
    } else if (cmd === shared_consts.GROUPSCMD.REMOVEADMIN_OFMYGROUP) {
      ris = await MyGroup.removeAdminOfMyGroup(idapp, usernameOrig, groupnameDest); // Rimuovo la richiesta di entrare nel gruppo

      // Invia una notifica alla persona
      await tools.sendNotificationByGroupname(idapp, usernameOrig, groupnameDest, cmd, false, true, username_action);
    }
  } catch (e) {
    console.error('Error: ', e);
  }

  return ris;
};

UserSchema.statics.updateMyData = async function (outres, idapp, username) {
  try {
    //++Todo: Ottimizzare ! Non occorre inviare tutti questi dati !!! Solo per il Circuito ?!
    const userprofile = await User.getExtraInfoByUsername(idapp, username);
    if (userprofile) {
      outres.userprofile = userprofile;
    }
    outres.listcircuits = await Circuit.findAllIdApp(idapp);
    outres.mygroups = await MyGroup.findAllGroups(idapp);
  } catch (e) {
    console.error('ERR', e);
  }

  return outres;
};

UserSchema.statics.setCircuitCmd = async function (
  idapp,
  usernameOrig,
  circuitname,
  cmd,
  value,
  username_action,
  extrarec
) {
  // console.log('setCircuitCmd', cmd);

  const { SendNotif } = require('../models/sendnotif');
  const telegrambot = require('../telegram/telegrambot');

  let ris = null;
  let outres = {
    result: false,
  };
  let update = {};

  let groupname = extrarec && extrarec.groupname ? extrarec.groupname : '';

  try {
    if (cmd === shared_consts.CIRCUITCMD.CREATE) {
      const mycirc = await Circuit.findOne({ idapp, name: circuitname });
      if (mycirc) {
        // Il Conto Comunitario prende il nome del circuito !
        await Account.createAccount(idapp, '', circuitname, true, '', mycirc.path);
      }
    } else if (cmd === shared_consts.CIRCUITCMD.SET) {
      if (groupname) {
        const foundIfCircuitInGroup = await MyGroup.ifCircuitAlreadyInGroup(idapp, groupname, circuitname);

        if (!foundIfCircuitInGroup) {
          ris = await this.addCircuitToUser(idapp, usernameOrig, circuitname, true, groupname);
        } else {
          ris = false;
        }

        await Circuit.updateData(idapp, circuitname);
        if (ris) {
          // Invia una notifica alla persona e agli Admin
          tools.sendNotificationByCircuit(
            idapp,
            usernameOrig,
            circuitname,
            cmd,
            value,
            true,
            username_action,
            extrarec
          );
          outres.result = await Circuit.getInfoCircuitByName(idapp, circuitname);
        }
      } else {
        ris = await this.addCircuitToUser(idapp, usernameOrig, circuitname, true, '');

        await Circuit.updateData(idapp, circuitname);
        if (ris) {
          // Invia una notifica alla persona e agli Admin
          tools.sendNotificationByCircuit(
            idapp,
            usernameOrig,
            circuitname,
            cmd,
            value,
            true,
            username_action,
            extrarec
          );
          outres.result = await Circuit.getInfoCircuitByName(idapp, circuitname);
        }
      }
    } else if (cmd === shared_consts.CIRCUITCMD.SETFIDO) {
      ris = await Circuit.setFido(idapp, usernameOrig, circuitname, groupname, username_action);
      if (ris && ris.fidoConcesso && ris.changed) {
        if (extrarec) {
          extrarec.fidoConcesso = ris.fidoConcesso;
          extrarec.qta_maxConcessa = ris.qta_maxConcessa;
          extrarec.username_admin_abilitante = ris.username_admin_abilitante;
        }

        // Elimina la richiesta:
        update = { $pull: { req_users: { username: { $in: [usernameOrig] } } } };
        await Circuit.updateOne({ idapp, name: circuitname }, update);

        await Circuit.updateData(idapp, circuitname);
        if (ris) {
          // Invia una notifica alla persona e agli Admin
          tools.sendNotificationByCircuit(
            idapp,
            usernameOrig,
            circuitname,
            cmd,
            value,
            true,
            username_action,
            extrarec
          );

          // Se gli è stato abilitato il Fido ad un Circuito Provinciale,
          // se non è ancora dentro al Circuito Italia
          const giainItalia = await User.ifAlreadyInCircuitItalia(idapp, usernameOrig);

          if (!giainItalia) {
            // allora ora attiva la richiesta per il circuito Italia
            const inseriscimiInCircuitoItalia = await User.getInseriscimiAncheInCircuitoITA(idapp, usernameOrig);

            const circuitItalia = await Circuit.getCircuitoItalia(idapp);

            if (inseriscimiInCircuitoItalia) {
              const mycmd = shared_consts.CIRCUITCMD.REQ;
              await User.setCircuitCmd(idapp, usernameOrig, circuitItalia.name, mycmd, true, usernameOrig, {
                groupname: groupname,
                abilitaveramente: true,
              });
            }
          }

          outres.result = await Circuit.getInfoCircuitByName(idapp, circuitname);
        }
      } else {
        // errore !?
      }
    } else if (cmd === shared_consts.CIRCUITCMD.REQ) {
      let abilitareq = true;

      const thiscircuit = await Circuit.findOne({
        idapp,
        name: circuitname,
      });

      const mycircuitOrig = await Circuit.getCircuitMyProvince(idapp, usernameOrig);

      const myfido = await Circuit.getFido(idapp, usernameOrig, mycircuitOrig, '');

      // se è il circuito Italia e !extrarec.abilitoveramente allora
      if (thiscircuit.isCircItalia && !extrarec.abilitaveramente) {
        // Se sono già stato abilitato al circuito della mia provincia, allora faccio la richiesta
        if (!mycircuitOrig || myfido === 0) {
          abilitareq = false;
        }
      }
      // abilito il set

      if (abilitareq) {
        if (groupname) {
          // Aggiungo la richiesta di Gruppo a me
          const foundIfAlreadyAskCircuit = await Circuit.findOne({
            idapp,
            name: circuitname,
            req_groups: {
              $elemMatch: { groupname: { $eq: groupname } },
            },
          });

          if (value) {
            ris = await this.addCircuitToUser(idapp, usernameOrig, circuitname, false, groupname, '');

            if (!foundIfAlreadyAskCircuit) {
              update = {
                $push: {
                  req_groups: {
                    groupname,
                    date: new Date(),
                  },
                },
              };
              ris = await Circuit.updateOne({ idapp, name: circuitname }, update);
            }
            if (ris) {
              // Invia una notifica alla persona
              await tools.sendNotificationByCircuit(
                idapp,
                usernameOrig,
                circuitname,
                cmd,
                true,
                true,
                username_action,
                extrarec
              );
            }
          } else {
            if (foundIfAlreadyAskCircuit) {
              outres.result = await MyGroup.removeCircuitFromGroup(idapp, groupname, circuitname); // Rimuovo il Circuito dal gruppo

              // Invia una notifica alla persona
              await tools.sendNotificationByCircuit(
                idapp,
                usernameOrig,
                circuitname,
                cmd,
                false,
                true,
                username_action,
                extrarec
              );
            }
          }
        } else {
          // Aggiungo la richiesta di Circuito a me
          const foundIfAlreadyAskCircuit = await Circuit.findOne({
            idapp,
            name: circuitname,
            req_users: {
              $elemMatch: { username: { $eq: usernameOrig } },
            },
          });

          if (value) {
            if (!thiscircuit.askManagerToEnter) {
              // Aggiungi intanto l'utente al Circuito (senza fido)
              ris = await this.addCircuitToUser(idapp, usernameOrig, circuitname, false);
            }

            if (!foundIfAlreadyAskCircuit) {
              update = {
                $push: {
                  req_users: {
                    username: usernameOrig,
                    date: new Date(),
                  },
                },
              };
              // Aggiungi la richiesta per ottenere il fido (o per entrare nel circuito)
              ris = await Circuit.updateOne({ idapp, name: circuitname }, update);
            }

            if (ris) {
              // Invia una notifica alla persona
              await tools.sendNotificationByCircuit(
                idapp,
                usernameOrig,
                circuitname,
                cmd,
                true,
                true,
                username_action,
                extrarec
              );
            }
          } else {
            if (foundIfAlreadyAskCircuit) {
              outres.result = await this.removeFromCircuits(idapp, usernameOrig, circuitname); // Rimuovo il Gruppo da me

              // Invia una notifica alla persona
              await tools.sendNotificationByCircuit(
                idapp,
                usernameOrig,
                circuitname,
                cmd,
                false,
                true,
                username_action,
                extrarec
              );
            }
          }
        }
      } else {
        // imposta che quando lo attiveranno al circuito territoriale, allora verrà fatta anche la richiesta per il circuito Italia
        const updateInseriscimiAncheInCircuitoITA = await User.setInseriscimiAncheInCircuitoITA(idapp, usernameOrig);
        outres.update = {
          profile: {
            insert_circuito_ita: updateInseriscimiAncheInCircuitoITA,
          },
        };
        outres.result = true;
        ris = true;
      }
    } else if (cmd === shared_consts.CIRCUITCMD.REMOVE_FROM_MYLIST) {
      if (groupname) {
        // Elimina la richiesta:
        update = { $pull: { req_groups: { groupname: { $in: [groupname] } } } };
        await Circuit.updateOne({ idapp, name: circuitname }, update);

        outres.result = await MyGroup.removeCircuitFromGroup(idapp, groupname, circuitname); // Rimuovo l'Amicizia da me
      } else {
        // Remove if is also an Admin
        await Circuit.removeAdminOfMyCircuit(idapp, usernameOrig, circuitname);

        outres.result = await this.removeFromCircuits(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me
        console.log('ris', ris);

        await Circuit.updateData(idapp, circuitname);
      }

      // Invia una notifica alla persona
      await tools.sendNotificationByCircuit(
        idapp,
        usernameOrig,
        circuitname,
        cmd,
        false,
        true,
        username_action,
        extrarec
      );
    } else if (cmd === shared_consts.CIRCUITCMD.DELETE) {
      ris = await this.removeFromCircuits(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me

      if (ris) {
        // Invia una notifica alla persona e agli Admin
        await tools.sendNotificationByCircuit(
          idapp,
          usernameOrig,
          circuitname,
          cmd,
          false,
          true,
          username_action,
          extrarec
        );
      }
      outres.result = await Circuit.deleteCircuit(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me
    } else if (cmd === shared_consts.CIRCUITCMD.CANCEL_REQ) {
      if (groupname)
        outres.result = await Circuit.removeReqGroupCircuit(idapp, groupname, circuitname); // Rimuovo l'Amicizia da me
      else outres.result = await Circuit.removeReqCircuit(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me

      await Circuit.updateData(idapp, circuitname);
    } else if (cmd === shared_consts.CIRCUITCMD.REFUSE_REQ) {
      if (groupname) {
        outres.result = await MyGroup.removeCircuitFromGroup(idapp, groupname, circuitname); // Rimuovo l'Amicizia da me
        outres.result = await Circuit.removeReqGroupCircuit(idapp, groupname, circuitname); // Rimuovo l'Amicizia da me
        outres.result = await Circuit.refuseReqGroupCircuit(idapp, groupname, circuitname); // Rimuovo l'Amicizia da me
      } else {
        outres.result = await this.removeFromCircuits(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me
        outres.result = await Circuit.removeReqCircuit(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me
        outres.result = await Circuit.refuseReqCircuit(idapp, usernameOrig, circuitname); // Rimuovo l'Amicizia da me
      }

      // Invia una notifica alla persona
      await tools.sendNotificationByCircuit(
        idapp,
        usernameOrig,
        circuitname,
        cmd,
        false,
        true,
        username_action,
        extrarec
      );
    } else if (cmd === shared_consts.CIRCUITCMD.ADDADMIN) {
      outres.result = await Circuit.addToAdminOfMyCircuit(idapp, usernameOrig, circuitname); // Rimuovo la richiesta di entrare nel gruppo

      // Invia una notifica alla persona
      await tools.sendNotificationByCircuit(
        idapp,
        usernameOrig,
        circuitname,
        cmd,
        false,
        true,
        username_action,
        extrarec
      );
    } else if (cmd === shared_consts.CIRCUITCMD.REMOVEADMIN) {
      outres.result = await Circuit.removeAdminOfMyCircuit(idapp, usernameOrig, circuitname); // Rimuovo la richiesta di entrare nel gruppo

      // Invia una notifica alla persona
      await tools.sendNotificationByCircuit(
        idapp,
        usernameOrig,
        circuitname,
        cmd,
        false,
        true,
        username_action,
        extrarec
      );
    } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ) {
      const onlycheck = false;

      outres = await Circuit.sendCoins(onlycheck, idapp, usernameOrig, extrarec);

      if (outres.cansend) {
        ris = true;
        // } else if ((cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) || (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE)) {
        // Before to accept, I see if it's already set !

        /*outres = {
          cansend: false,
          errormsg: '',
        }; */

        let outcheck = outres;

        let risStatus = '';
        const status = await SendNotif.getStatus(extrarec.notifId);
        if (status === shared_consts.CircuitsNotif.STATUS_ACCEPTED) {
          risStatus = i18n.__('STATUS_SENT');
        } else if (status === shared_consts.CircuitsNotif.STATUS_REFUSED) {
          risStatus = i18n.__('STATUS_REFUSED');
        }
        // if (!await SendNotif.checkIfCoinsAlreadySent(extrarec.notifId)) {
        //if (!await Movement.checkIfCoinsAlreadySent(extrarec.notifId)) {
        if (true) {
          if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
            //outcheck = await Circuit.sendCoins(true, idapp, usernameOrig, extrarec);
            outcheck.cansend = true;
            outres.cansend = true;
          } else {
            outcheck.cansend = true;
            outres.cansend = true;
          }

          cmd = shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT;

          /*if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT && outcheck.cansend) {
            if (!await Movement.checkIfCoinsAlreadySent(extrarec.notifId)) {
              outres = await Circuit.sendCoins(false, idapp, usernameOrig, extrarec);
  
            } else {
              outcheck.cansend = false; //GIA INVIATO
            }
          } */

          if (outcheck.cansend) {
            // Invia una notifica di moneta (accettata o rifiutata) alla persona
            const out = await tools.sendNotificationByCircuit(
              idapp,
              usernameOrig,
              circuitname,
              cmd,
              false,
              true,
              username_action,
              extrarec
            );

            if (outres && extrarec.groupname) {
              // Setta agli altri admin,
            }
          }

          outres.recnotif = await SendNotif.getRecNotif(extrarec.notifId);
          outres.arrrecnotif = await SendNotif.findAllNotifByUsernameIdAndIdApp(
            username_action,
            extrarec.lastdr,
            idapp,
            shared_consts.LIMIT_NOTIF_FOR_USER,
            shared_consts.QualiNotifs.OTHERS
          );
          if (await User.isAdminByUsername(idapp, username_action)) {
            outres.arrrecnotifcoins = await SendNotif.findAllNotifCoinsAllIdAndIdApp(idapp);
          } else {
            outres.arrrecnotifcoins = await SendNotif.findAllNotifByUsernameIdAndIdApp(
              username_action,
              extrarec.lastdr,
              idapp,
              shared_consts.LIMIT_NOTIFCOINS_FOR_USER,
              shared_consts.QualiNotifs.CIRCUITS
            );
          }
        }
      }

      if (outres.errormsg) {
        let msgerr = '';
        let username_dest = extrarec.dest;
        if (outres.errorcode === shared_consts.SENDRIS_CODES.EXCEED_FIDO) {
        } else if (outres.errorcode === shared_consts.SENDRIS_CODES.EXCEED_QTAMAX) {
          // invia un messaggio al destinatario
          const msgDest = i18n.__('EXCEED_QTAMAX', usernameOrig, extrarec.qty.toString(), extrarec.circuitname);
          await telegrambot.sendMsgTelegram(idapp, username_dest, msgDest);

          msgerr = i18n.__('EXCEED_QTAMAX_MITTENTE', username_dest);
          await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgerr);
        }

        console.warn('🔴 ATTENZIONE! ', outres.errormsg + '\n(Mittente: ' + usernameOrig + ')');
        // await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgOrig);

        // Invia questo msg anche all'Admin
        await telegrambot.sendMsgTelegramToTheAdmin(
          idapp,
          outres.errormsg + '\n(Mittente: ' + usernameOrig + ')',
          true
        );
      }
    } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT || cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
      // Before to accept, I see if it's already set !

      outres = {
        cansend: false,
        errormsg: '',
      };

      let outcheck = outres;

      let risStatus = '';
      const status = await SendNotif.getStatus(extrarec.notifId);
      if (status === shared_consts.CircuitsNotif.STATUS_ACCEPTED) {
        risStatus = i18n.__('STATUS_SENT');
      } else if (status === shared_consts.CircuitsNotif.STATUS_REFUSED) {
        risStatus = i18n.__('STATUS_REFUSED');
      }
      // if (!await SendNotif.checkIfCoinsAlreadySent(extrarec.notifId)) {
      if (!(await Movement.checkIfCoinsAlreadySent(extrarec.notifId))) {
        if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
          outcheck = await Circuit.sendCoins(true, idapp, usernameOrig, extrarec);
        } else {
          outcheck.cansend = true;
          outres.cansend = true;
        }

        if (outcheck) {
          if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT && outcheck.cansend) {
            if (!(await Movement.checkIfCoinsAlreadySent(extrarec.notifId))) {
              outres = await Circuit.sendCoins(false, idapp, usernameOrig, extrarec);
            } else {
              outcheck.cansend = false; //GIA INVIATO
            }
          }

          if (outcheck.cansend) {
            // Invia una notifica di moneta (accettata o rifiutata) alla persona
            const out = await tools.sendNotificationByCircuit(
              idapp,
              usernameOrig,
              circuitname,
              cmd,
              false,
              true,
              username_action,
              extrarec
            );

            if (outres && extrarec.groupname) {
              // Setta agli altri admin,
            }
          }

          outres.recnotif = await SendNotif.getRecNotif(extrarec.notifId);
          outres.arrrecnotif = await SendNotif.findAllNotifByUsernameIdAndIdApp(
            username_action,
            extrarec.lastdr,
            idapp,
            shared_consts.LIMIT_NOTIF_FOR_USER
          );
          if (await User.isAdminByUsername(idapp, username_action)) {
            outres.arrrecnotifcoins = await SendNotif.findAllNotifCoinsAllIdAndIdApp(idapp);
          } else {
            outres.arrrecnotifcoins = await SendNotif.findAllNotifByUsernameIdAndIdApp(
              username_action,
              extrarec.lastdr,
              idapp,
              shared_consts.LIMIT_NOTIFCOINS_FOR_USER,
              shared_consts.QualiNotifs.CIRCUITS
            );
          }
        }

        //----
      } else {
        outres.cansend = false;

        outres.errormsg = i18n.__('CIRCUIT_COINS_ALREADY_PROCESSED', risStatus);
      }
      ris = true;
    }

    if (ris && username_action) {
      outres = await this.updateMyData(outres, idapp, username_action);
    }

    if (circuitname) outres.circuit = await Circuit.getInfoCircuitByName(idapp, circuitname);

    return outres;
  } catch (e) {
    console.error('Error setCircuitCmd: ', e);
  }

  return ris;
};

UserSchema.statics.getInfoFriendByUsername = async function (idapp, username) {
  const whatToShow = getWhatToShow(idapp, username);

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    whatToShow
  )
    .lean()
    .then((rec) => (!!rec ? rec : null));
};

UserSchema.statics.getInfoAskFriendByUsername = async function (idapp, username) {
  const whatToShow = getWhatToShow_Unknown(idapp, username);

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    whatToShow
  )
    .lean()
    .then((rec) => (!!rec ? rec : null));
};

UserSchema.statics.isFidoConcesso = async function (idapp, username, nomeCircuito) {
  try {
    const myfido = await Circuit.getFido(idapp, username, nomeCircuito, '');
    return !!myfido && myfido > 0 ? true : false;
  } catch (e) {
    return false;
  }
};

UserSchema.statics.getAskedFriendsByUsername = async function (idapp, username) {
  const whatToShow_Unknown = getWhatToShow_Unknown(idapp, username);

  return await User.find(
    {
      idapp,
      'profile.req_friends': {
        $elemMatch: { username: { $eq: username } },
      },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    whatToShow_Unknown
  ).then((rec) => {
    //return rec.map(m => m.username);
  });
};

UserSchema.statics.getUsersToVerify = async function (idapp, username) {
  const whatToShow_Unknown = getWhatToShow(idapp, username);
  let usersToVerify = await User.find(
    {
      idapp,
      aportador_solidario: username,
      verified_by_aportador: { $exists: false },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    whatToShow_Unknown
  ).lean();

  return usersToVerify;
};

UserSchema.statics.getFriendsByUsername = async function (idapp, username) {
  if (!username) {
    return {
      listFriends: [],
      listRequestFriends: [],
      listTrusted: [],
      listSentRequestFriends: [],
    };
  }

  try {
    const whatToShow = getWhatToShow(idapp, username);
    const whatToShow_Unknown = getWhatToShow_Unknown(idapp, username);
    const arrUsernameFriends = await User.getUsernameFriendsByUsername(idapp, username);
    const arrUsernameReqFriends = await User.getUsernameReqFriendsByUsername(idapp, username);
    const arrUsernameHandShake = await User.getUsernameHandShakeByUsername(idapp, username);

    let listFriends = await User.find(
      {
        idapp,
        username: { $in: arrUsernameFriends },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
      whatToShow
    );

    let listHandShake = await User.find(
      {
        idapp,
        username: { $in: arrUsernameHandShake },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
      whatToShow
    );

    let listRequestFriends = await User.find(
      {
        idapp,
        username: { $in: arrUsernameReqFriends },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
      whatToShow_Unknown
    );

    let listSentRequestFriends = await User.find(
      {
        idapp,
        'profile.req_friends': {
          $elemMatch: { username: { $eq: username } },
        },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
      whatToShow_Unknown
    );

    let listTrusted = await User.find(
      {
        idapp,
        aportador_solidario: username,
        'profile.teleg_id': { $gt: 0 },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
      whatToShow
    );

    return {
      listFriends,
      listHandShake,
      listRequestFriends,
      listTrusted,
      listSentRequestFriends,
    };
  } catch (e) {
    console.log('Error', e);
  }

  return {
    listFriends: [],
    listHandShake: [],
    listRequestFriends: [],
    listTrusted: [],
    listSentRequestFriends: [],
  };
};

UserSchema.statics.getAportadorSolidarioByUsername = async function (idapp, username) {
  const User = this;

  return await User.findOne({
    idapp,
    username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .then((rec) => {
      return rec ? rec.aportador_solidario : '';
    })
    .catch((e) => {
      console.error('getAportadorSolidarioByUsername', e);
    });
};

UserSchema.statics.UserByIdTelegram = async function (idapp, teleg_id) {
  const User = this;

  return await User.findOne({
    idapp,
    'profile.teleg_id': teleg_id,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  })
    .lean()
    .then(async (rec) => {
      if (!rec) {
        // Cerca se esiste in quello salvato in precedenza:
        const recold = await User.findOne({
          idapp,
          'profile.teleg_id_old': teleg_id,
        }).lean();
        if (recold && recold.profile.teleg_id_old === teleg_id) {
          // Riaggiorna l'ID perché è ritornato sulla chat!
          await User.SetTelegramIdSuccess(idapp, recold._id, recold.profile.teleg_id_old);

          rec = await User.findOne({
            idapp,
            'profile.teleg_id': teleg_id,
            $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
          }).lean();
        }
      }
      return !!rec ? rec : null;
    })
    .catch((e) => {
      console.error('UserExistByIdTelegram', e);
    });
};

UserSchema.statics.setPicProfile = async function (idapp, username, imgpic) {
  const User = this;

  const fields_to_update = {
    'profile.img': imgpic,
  };

  return await User.findOneAndUpdate(
    {
      idapp,
      username,
    },
    { $set: fields_to_update },
    { new: false }
  )
    .lean()
    .then((record) => {
      return !!record;
    });
};

UserSchema.statics.TelegIdByUsername = async function (idapp, username) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { 'profile.teleg_id': 1 }
  )
    .lean()
    .then((rec) => {
      return !!rec ? rec.profile.teleg_id : null;
    })
    .catch((e) => {
      console.error('TelegIdByUsername', e);
    });
};

UserSchema.statics.TelegIdById = async function (idapp, id) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      _id: id,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { 'profile.teleg_id': 1 }
  )
    .lean()
    .then((rec) => {
      return !!rec ? rec.profile.teleg_id : null;
    })
    .catch((e) => {
      console.error('TelegIdByUsername', e);
    });
};
UserSchema.statics.notAsk_VerifByUsername = async function (idapp, username) {
  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { notask_verif: 1 }
  )
    .lean()
    .then((rec) => {
      return !!rec && rec.notask_verif ? true : false;
    })
    .catch((e) => {
      console.error('notAsk_VerifByUsername', e);
      return false;
    });
};

UserSchema.statics.SetTelegramCheckCode = async function (idapp, id, teleg_checkcode) {
  const User = this;

  const fields_to_update = {
    'profile.teleg_checkcode': teleg_checkcode,
  };

  return await User.findOneAndUpdate(
    {
      _id: id,
    },
    { $set: fields_to_update },
    { new: false }
  )
    .lean()
    .then((record) => {
      return !!record;
    });
};

UserSchema.statics.NonVoglioImbarcarmi = async function (idapp, username) {
  const User = this;

  const fields_to_update = {
    non_voglio_imbarcarmi: true,
  };

  return await User.findOneAndUpdate(
    {
      idapp,
      username,
    },
    { $set: fields_to_update },
    { new: false }
  ).then((record) => {
    return !!record;
  });
};

UserSchema.statics.getInseriscimiAncheInCircuitoITA = async function (idapp, username) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { 'profile.insert_circuito_ita': 1 }
  )
    .lean()
    .then((rec) => {
      return !!rec ? rec.profile.insert_circuito_ita : null;
    })
    .catch((e) => {
      console.error('getInseriscimiAncheInCircuitoITA', e);
    });
};
UserSchema.statics.setInseriscimiAncheInCircuitoITA = async function (idapp, username) {
  const User = this;

  const fields_to_update = {
    'profile.insert_circuito_ita': true,
  };

  return await User.findOneAndUpdate(
    {
      idapp,
      username,
    },
    { $set: fields_to_update },
    { new: true }
  ).then((record) => {
    return record.profile.insert_circuito_ita;
  });
};

UserSchema.statics.SetTelegramIdSuccess = async function (idapp, id, teleg_id) {
  const User = this;

  const fields_to_update = {
    'profile.teleg_id': teleg_id,
    'profile.teleg_id_old': 0,
    'profile.teleg_checkcode': 0,
  };

  return await User.findOneAndUpdate(
    {
      idapp,
      _id: id,
    },
    { $set: fields_to_update },
    { new: false }
  )
    .lean()
    .then((record) => {
      if (record) {
        return User.findOne({
          idapp,
          _id: id,
        }).lean();
      }
    });

  return null;
};

UserSchema.statics.getUsernameTelegram = async function (idapp, username) {
  const User = this;
  return await User.findOne({ idapp, username }, { 'profile.username_telegram': 1 })
    .lean()
    .then((ris) => {
      if (ris) return ris.profile.username_telegram;
      else return '';
    });
};

UserSchema.statics.setUsernameTelegram = async function (
  idapp,
  id,
  username_telegram,
  firstname_telegram,
  lastname_telegram
) {
  const User = this;

  const fields_to_update = {
    'profile.username_telegram': username_telegram,
    'profile.firstname_telegram': firstname_telegram,
    'profile.lastname_telegram': lastname_telegram,
  };

  return await User.findOneAndUpdate(
    {
      idapp,
      _id: id,
    },
    { $set: fields_to_update },
    { new: false }
  ).then((record) => {
    return record;
  });
};

UserSchema.statics.SetLang = async function (idapp, id, lang) {
  const User = this;

  const fields_to_update = {
    lang,
  };

  return await User.findOneAndUpdate(
    {
      _id: id,
    },
    { $set: fields_to_update },
    { new: false }
  ).then((record) => {
    return record;
  });
};

UserSchema.statics.SetTelegramWasBlocked = async function (idapp, teleg_id) {
  const User = this;

  const fields_to_update = {
    'profile.teleg_id_old': teleg_id,
    'profile.teleg_id': 0,
  };

  // if (tools.sulServer()) {
  if (true) {
    const ris = await User.findOneAndUpdate(
      {
        idapp,
        'profile.teleg_id': teleg_id,
      },
      { $set: fields_to_update },
      { new: false }
    ).then((record) => {
      return record;
    });
  }
};

UserSchema.statics.getNameSurnameByUsername = async function (idapp, username, reale = false) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1, name: 1, surname: 1 }
  )
    .then((rec) => {
      let ris = rec.username;
      if (!!rec) {
        if (reale) {
          if (!rec.name) return '';

          ris = `${rec.name} ${rec.surname}`;
        } else {
          if (rec.name) {
            ris = `${rec.name} ${rec.surname}`;
          }
        }
      }
      return !!rec ? ris : '';
    })
    .catch((e) => {
      console.error('getNameSurnameByUsername', e);
    });
};
UserSchema.statics.getNameSurnameEUsernameByUsername = async function (idapp, username, reale = false) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1, name: 1, surname: 1 }
  )
    .then((rec) => {
      let ris = rec.username;
      if (!!rec) {
        if (reale) {
          if (!rec.name) return '';
        }
        ris = rec.name ? `${rec.name} ${rec.surname} - ${rec.username}` : `${rec.username}`;
      }
      return !!rec ? ris : '';
    })
    .catch((e) => {
      console.error('getNameSurnameByUsername', e);
    });
};
UserSchema.statics.getProfileByUsername = async function (idapp, username, reale = false) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { profile: 1 }
  ).catch((e) => {
    console.error('getNameSurnameByUsername', e);
  });
};

UserSchema.statics.getIdByUsername = async function (idapp, username) {
  const User = this;

  let regexp = new RegExp(`^${username}$`, 'i');

  return await User.findOne(
    {
      idapp,
      username: { $regex: regexp },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1, _id: 1 }
  )
    .then((rec) => {
      return !!rec ? rec._id.toString() : '';
    })
    .catch((e) => {
      console.error('getIdByUsername', e);
    });
};

UserSchema.statics.getRealUsernameByUsername = async function (idapp, username) {
  const User = this;

  let regexp = new RegExp(`^${username}$`, 'i');

  return await User.findOne(
    {
      idapp,
      username: { $regex: regexp },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1, _id: 1 }
  )
    .then((rec) => {
      return !!rec ? rec.username : '';
    })
    .catch((e) => {
      console.error('getRealUsernameByUsername', e);
    });
};

UserSchema.statics.getRealUsernameByUsernameTelegram = async function (idapp, username) {
  const User = this;

  let regexp = new RegExp(`^${username}$`, 'i');

  return await User.findOne(
    {
      idapp,
      'profile.username_telegram': { $regex: regexp },
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { username: 1, _id: 1 }
  )
    .then((rec) => {
      return !!rec ? rec.username : '';
    })
    .catch((e) => {
      console.error('getRealUsernameByUsername', e);
    });
};

UserSchema.statics.getRecLangAndIdByUsername = async function (idapp, username) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { lang: 1, _id: 1 }
  )
    .then((rec) => {
      return !!rec ? rec : null;
    })
    .catch((e) => {
      console.error('getRecLangAndIdByUsername', e);
    });
};

UserSchema.statics.getNameSurnameById = async function (idapp, userId) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      _id: userId,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    { name: 1, surname: 1 }
  )
    .then((rec) => {
      return !!rec ? `${rec.name} ${rec.surname}` : '';
    })
    .catch((e) => {
      console.error('getNameSurnameById', e);
    });
};

UserSchema.statics.getusersManagers = async function (idapp) {
  const User = this;
  // Int32 mongodb 6.0
  return await User.find(
    { idapp, 'profile.manage_telegram': true, perm: { $bitsAnySet: 0b010 } },
    { username: 1, 'profile.teleg_id': 1, perm: 1 }
  )
    .then((arrrec) => {
      return !!arrrec ? arrrec : null;
    })
    .catch((e) => {
      console.error('getusersManagers', e);
    });
};

UserSchema.statics.getusersAdmin = async function (idapp) {
  const User = this;

  // Int32 mongodb 6.0

  return await User.find(
    { idapp, 'profile.admin_telegram': true, perm: { $bitsAnySet: 0b001 } },
    { username: 1, 'profile.teleg_id': 1, perm: 1 }
  )
    .then((arrrec) => {
      return !!arrrec ? arrrec : null;
    })
    .catch((e) => {
      console.error('getusersAdmin', e);
    });
};

UserSchema.statics.getusersRespList = async function (idapp) {
  const User = this;

  return await User.find({ idapp, 'profile.resplist': true }, { _id: 1, username: 1, name: 1, surname: 1 })
    .then((arrrec) => {
      return !!arrrec ? arrrec : null;
    })
    .catch((e) => {
      console.error('getusersRespList', e);
    });
};

UserSchema.statics.getusersWorkersList = async function (idapp) {
  const User = this;

  return await User.find({ idapp, 'profile.workerslist': true }, { _id: 1, username: 1, name: 1, surname: 1 })
    .then((arrrec) => {
      return !!arrrec ? arrrec : null;
    })
    .catch((e) => {
      console.error('getusersWorkersList', e);
    });
};

UserSchema.statics.getusersManagersAndZoomeri = async function (idapp) {
  const User = this;

  return await User.find(
    {
      idapp,
      or: [
        { 'profile.manage_telegram': true },
        {
          perm: {
            $bit: Number(shared_consts.Permissions.Zoomeri),
          },
        },
      ],
    },
    { 'profile.teleg_id': 1 }
  )
    .then((arrrec) => {
      return !!arrrec ? arrrec : null;
    })
    .catch((e) => {
      console.error('getusersManagers', e);
    });
};

UserSchema.statics.getUsersTelegALL = async function (idapp, username) {
  const User = this;

  if (!!username) {
    return await User.find({ idapp, username, 'profile.teleg_id': { $gt: 0 } }).lean();
    then((arrrec) => {
      return !!arrrec ? arrrec : null;
    }).catch((e) => {
      console.error('getUsersTelegALL', e);
    });
  } else {
    return await User.find({ idapp, 'profile.teleg_id': { $gt: 0 } })
      .lean()
      .then((arrrec) => {
        return !!arrrec ? arrrec : null;
      })
      .catch((e) => {
        console.error('getUsersTelegALL', e);
      });
  }
};

UserSchema.statics.isManagerByIdTeleg = async function (idapp, idtelegram) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      'profile.manage_telegram': true,
      'profile.teleg_id': idtelegram,
    },
    { 'profile.teleg_id': 1 }
  )
    .then((rec) => {
      return !!rec && rec.profile.teleg_id === idtelegram;
    })
    .catch((e) => {
      console.error('isManagerByIdTeleg', e);
      return false;
    });
};

UserSchema.statics.isAdminByUsername = async function (idapp, username) {
  const User = this;

  return await User.findOne(
    {
      idapp,
      username,
    },
    { perm: 1 }
  )
    .then((rec) => {
      return User.isAdmin(rec.perm);
    })
    .catch((e) => {
      console.error('isAdminByUsername', e);
      return false;
    });
};

UserSchema.statics.getUsersList = function (idapp) {
  const User = this;

  return User.find(
    {
      idapp: idapp,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    {
      username: 1,
      name: 1,
      surname: 1,
      lasttimeonline: 1,
      verified_email: 1,
      verified_by_aportador: 1,
      idMyGroup: 1,
      made_gift: 1,
      perm: 1,
      email: 1,
      date_reg: 1,
      img: 1,
    }
  );
};

/**
 * Query blog posts by user -> paginated results and a total count.
 * @returns {Object} Object -> `{ rows, count }`
 */

UserSchema.statics.getFieldsForSearch = function () {
  return [
    { field: 'username', type: tools.FieldType.string },
    { field: 'name', type: tools.FieldType.string },
    { field: 'index', type: tools.FieldType.number },
    { field: 'ind_order', type: tools.FieldType.number },
    // { field: 'old_order', type: tools.FieldType.number },
    { field: 'surname', type: tools.FieldType.string },
    { field: 'email', type: tools.FieldType.string },
    { field: 'profile.cell', type: tools.FieldType.string },
    { field: 'profile.email_paypal', type: tools.FieldType.string },
    // { field: 'profile.payeer_id', type: tools.FieldType.string },
    // { field: 'profile.advcash_id', type: tools.FieldType.string },
    // { field: 'profile.revolut', type: tools.FieldType.string },
    { field: 'profile.link_payment', type: tools.FieldType.string },
    { field: 'profile.teleg_id', type: tools.FieldType.number },
    { field: 'profile.username_telegram', type: tools.FieldType.string },
    { field: 'ipaddr', type: tools.FieldType.string },
  ];
  //{field: 'aportador_solidario', type: tools.FieldType.string}
};

UserSchema.statics.getFieldsForSearchUserFriend = function () {
  return [
    { field: 'username', type: tools.FieldType.exact },
    { field: 'profile.username_telegram', type: tools.FieldType.string },
    { field: 'name', type: tools.FieldType.string },
    { field: 'surname', type: tools.FieldType.string },
  ];
};

UserSchema.statics.getFieldsForSearchUserFriend_AllWords = function () {
  return [
    { field: 'username', type: tools.FieldType.string },
    { field: 'profile.username_telegram', type: tools.FieldType.string },
    { field: 'name', type: tools.FieldType.string },
    { field: 'surname', type: tools.FieldType.string },
  ];
};

UserSchema.statics.executeQueryTable = function (idapp, params) {
  params.fieldsearch = this.getFieldsForSearch();
  if (params.options) {
    if (tools.isBitActive(params.options, shared_consts.OPTIONS_SEARCH_USER_ONLY_FULL_WORDS)) {
      params.fieldsearch = this.getFieldsForSearchUserFriend();
    } else if (tools.isBitActive(params.options, shared_consts.OPTIONS_SEARCH_USER_ALL_WORDS)) {
      params.fieldsearch = this.getFieldsForSearchUserFriend_AllWords();
    }
  }
  return tools.executeQueryTable(this, idapp, params);
};

UserSchema.statics.findAllIdApp = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  return await tools.findAllQueryIdApp(this, myfind);
};

UserSchema.statics.DuplicateAllRecords = async function (idapporig, idappdest) {
  return await tools.DuplicateAllRecords(this, idapporig, idappdest);
};

UserSchema.statics.getDashboard = async function (
  idapp,
  aportador_solidario,
  username,
  aportador_solidario_nome_completo
) {
  try {
    // DATA: username, name, surname, email, intcode_cell, cell
    const dashboard = {
      aportador: {},
      arrposizioni: [],
      arrimbarchi: [],
      arrusers: {},
    };

    dashboard.myself = await User.getUserShortDataByUsername(idapp, username);
    // Data of my Aportador
    dashboard.aportador = await User.getUserShortDataByUsername(idapp, aportador_solidario);
    // if (dashboard.aportador === undefined) {
    //   dashboard.aportador = await ExtraList.getUserNotRegisteredByNameSurname(idapp, aportador_solidario_nome_completo);
    // }

    const arrap = await User.getDownlineByUsername(idapp, username, false, true);
    if (!!arrap) dashboard.numpeople_aportador = arrap.length;

    // console.table(dashboard.arrnavi);

    return dashboard;
  } catch (e) {
    console.error(e.message);
    return false;
  }
};

UserSchema.statics.getDownline = async function (idapp, aportador_solidario, username) {
  try {
    // DATA: username, name, surname, email, intcode_cell, cell
    let downline = {};

    downline.downline = [];

    // Data of my Downline
    const arrap = await User.getDownlineByUsername(idapp, username);
    if (!!arrap) downline.numpeople_aportador = arrap.length;

    downline.downline = await User.getDownlineByUsername(idapp, username, true);

    downline.downbyuser = {};

    for (const down of downline.downline) {
      downline.downbyuser[down.username] = await User.getDownlineByUsername(idapp, down.username, false);

      for (const down2 of downline.downbyuser[down.username]) {
        downline.downbyuser[down2.username] = await User.getDownlineByUsername(idapp, down2.username, false);
      }
    }

    return downline;
  } catch (e) {
    console.error(e.message);
    return false;
  }
};

UserSchema.statics.findByCellAndNameSurname = function (idapp, cell, name, surname) {
  const User = this;

  return User.findOne({
    idapp: idapp,
    'profile.cell': cell,
    name: name,
    surname: surname,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  });
};

UserSchema.statics.getUsersRegistered = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersRegisteredToday = async function (idapp) {
  const User = this;

  let starttoday = new Date();
  starttoday.setHours(0, 0, 0, 0);

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    date_reg: { $gt: starttoday },
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersOnLineToday = async function (idapp) {
  const User = this;

  let starttoday = new Date();
  starttoday.setHours(0, 0, 0, 0);

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    lasttimeonline: { $gt: starttoday },
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersOnLineActive = async function (idapp) {
  const User = this;

  const numgiorni_attivi = await Settings.getValDbSettings(idapp, 'SHOW_LAST_ACTIVE_USERS', 90);

  let daytocheck = new Date();
  daytocheck.setDate(daytocheck.getDate() - numgiorni_attivi);
  daytocheck.setHours(0, 0, 0, 0);

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    lasttimeonline: { $gt: daytocheck },
  };

  return await User.countDocuments(myfind);
};

/*
UserSchema.statics.getUsersQualified = async function (idapp, numinvitati) {
  const User = this;

  const arrusers = await User.find({
    idapp,
    $and: [
      { $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }] },
      {
        $or: [
          {
            'profile.special_req': true
          },
          {
            verified_email: true,
            'profile.teleg_id': { $gt: 0 },
            'profile.paymenttypes': { "$in": ['paypal'] },
            'profile.saw_and_accepted': shared_consts.ALL_SAW_AND_ACCEPTED,
            'profile.saw_zoom_presentation': true,
            'profile.my_dream': { $exists: true },
            $and: [
              { "$expr": { "$gt": [{ "$strLenCP": "$profile.my_dream" }, 10] } },
              { "$expr": { "$gt": [{ "$strLenCP": "$profile.email_paypal" }, 6] } }
            ],
          }]
      }]

  }, {
    'username': 1,
  });


  if (numinvitati === 0)
    return arrusers;      // PRENDI TUTTI

  let arrris = [];

  for (const rec of arrusers) {
    rec.numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, rec.username);
    if (rec.numinvitatiattivi >= numinvitati) {
      arrris.push(rec);
    }
  }

  return arrris

};
*/

// UserSchema.statics.getNumUsersQualified = async function (idapp, numinvitati) {
//
//   arrrec = await this.getUsersQualified(idapp, numinvitati);
//
//   return arrrec.length
//
// };

UserSchema.statics.getEmailNotVerified = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    verified_email: false,
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersTelegramAttivo = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.teleg_id': { $gt: 0 },
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersAutorizzati = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.teleg_id': { $gt: 0 },
    verified_by_aportador: true,
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersAutorizzare = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.teleg_id': { $gt: 0 },
    verified_by_aportador: { $exists: false },
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersTelegramPending = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.teleg_checkcode': { $gt: 0 },
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getNumUsers = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersZoom = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.saw_zoom_presentation': true,
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersResidenti = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.socioresidente': { $exists: true, $eq: true },
  };

  return await User.find(myfind, { username: 1, name: 1, surname: 1 });
};

UserSchema.statics.getSaw_and_Accepted = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.saw_and_accepted': shared_consts.ALL_SAW_AND_ACCEPTED,
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getUsersDreams = async function (idapp) {
  const User = this;

  const myfind = {
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.my_dream': { $exists: true },
    $expr: { $gt: [{ $strLenCP: '$profile.my_dream' }, 10] },
  };

  return await User.countDocuments(myfind);
};

UserSchema.statics.getLastUsers = async function (idapp) {
  const User = this;

  const lastn = await Settings.getValDbSettings(idapp, 'SHOW_LAST_N_USERS', 20);

  const myquery = [
    {
      $match: {
        idapp,
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $lookup: {
        from: 'users',
        let: {
          username: '$aportador_solidario',
          idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'user_aportador',
      },
    },
    {
      $unwind: '$user_aportador',
    },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: ['$user_aportador', '$$ROOT'],
        },
      },
    },
    {
      $project: {
        username: 1,
        name: 1,
        surname: 1,
        lasttimeonline: 1,
        aportador_solidario: 1,
        idMyGroup: 1,
        'profile.img': 1,
        'profile.resid_str_comune': 1,
        'profile.resid_comune': 1,
        'profile.resid_province': 1,
        date_reg: 1,
        index: 1,
        'profile.nationality': 1,
        'user_aportador.username': 1,
        'user_aportador.name': 1,
        'user_aportador.lasttimeonline': 1,
        'user_aportador.surname': 1,
        'user_aportador.profile.resid_str_comune': 1,
        'user_aportador.profile.resid_comune': 1,
        'user_aportador.profile.resid_province': 1,
        'user_aportador.profile.img': 1,
      },
    },
    {
      $sort: { date_reg: -1 },
    },
    {
      $limit: lastn,
    },
  ];

  return await User.aggregate(myquery).then((arr) => {
    //return JSON.stringify(arr)
    return arr;
  });
};

UserSchema.statics.getLastOnlineUsers = async function (idapp) {
  const User = this;

  const lastn = await Settings.getValDbSettings(idapp, 'SHOW_LAST_ONLINE_USERS', 20);

  return await User.find(
    {
      idapp,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    },
    {
      username: 1,
      name: 1,
      surname: 1,
      lasttimeonline: 1,
      date_reg: 1,
      verified_by_aportador: 1,
      idMyGroup: 1,
      'profile.img': 1,
      'profile.resid_str_comune': 1,
      'profile.resid_comune': 1,
      'profile.resid_province': 1,
      index: 1,
    }
  )
    .sort({ lasttimeonline: -1 })
    .limit(lastn)
    .lean()
    .then((arr) => {
      //return JSON.stringify(arr)
      return arr;
    });
};

UserSchema.statics.getLastSharedLink = async function (idapp) {
  const User = this;

  const lastn = 10;

  const myquery = [
    {
      $match: {
        idapp,
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $lookup: {
        from: 'users',
        let: {
          username: '$aportador_solidario',
          idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'user_aportador',
      },
    },
    {
      $unwind: '$user_aportador',
    },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: ['$user_aportador', '$$ROOT'],
        },
      },
    },
    {
      $project: {
        username: 1,
        name: 1,
        surname: 1,
        lasttimeonline: 1,
        aportador_solidario: 1,
        idMyGroup: 1,
        'profile.img': 1,
        'profile.resid_str_comune': 1,
        'profile.resid_comune': 1,
        'profile.resid_province': 1,
        date_reg: 1,
        index: 1,
        'profile.nationality': 1,
        'user_aportador.username': 1,
        'user_aportador.name': 1,
        'user_aportador.lasttimeonline': 1,
        'user_aportador.surname': 1,
        'user_aportador.profile.resid_str_comune': 1,
        'user_aportador.profile.resid_comune': 1,
        'user_aportador.profile.resid_province': 1,
        'user_aportador.profile.img': 1,
      },
    },
    {
      $sort: { date_reg: -1 },
    },
    {
      $limit: lastn,
    },
  ];

  return await User.aggregate(myquery).then((arr) => {
    //return JSON.stringify(arr)
    return arr;
  });
};

UserSchema.statics.getDiffusoriUsers = async function (idapp) {
  const User = this;

  const lastn = 10;

  return await User.aggregate(await User.getQueryUsersDiffusori(idapp)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.getBestStretteDiManoUsers = async function (idapp) {
  const User = this;

  const lastn = 10;

  return await User.aggregate(await User.getQueryUsersStretteDiMano(idapp)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.getLastStretteDiManoUsers = async function (idapp) {
  const User = this;

  const lastn = 10;

  const myquery = await User.getQueryLastUsersStretteDiMano(idapp);

  return await User.aggregate(myquery).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.getReceiveRISUsers = async function (idapp) {
  const User = this;

  return await User.aggregate(User.getQueryReceiveRISUsers(idapp, 8)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};
UserSchema.statics.getListLinkReg = async function (idapp) {
  const User = this;

  return await User.aggregate(User.getQueryListLinkReg(idapp, 8)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.checkUser = async function (idapp, username) {
  const User = this;

  return await User.findOne(
    { idapp, username },
    {
      verified_email: 1,
      verified_by_aportador: 1,
      idMyGroup: 1,
      notask_verif: 1,
      'profile.teleg_id': 1,
      'profile.teleg_checkcode': 1,
    }
  )
    .lean()
    .then((rec) => {
      return rec;
    });
};

UserSchema.statics.calculateStat = async function (idapp, username) {
  const User = this;

  try {
    const { MySkill } = require('../models/myskill');
    const { MyGood } = require('../models/mygood');
    const { MyBacheca } = require('../models/mybacheca');
    // const { MyGroup } = require('../models/mygroup');
    const globalTables = require('../tools/globalTables');

    const numUsersReg = await User.countDocuments({
      idapp,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });

    let numByTab = {};

    for (let table of shared_consts.TABLES_VISU_STAT_IN_HOME) {
      let mytable = globalTables.getTableByTableName(table);
      if (mytable) {
        numByTab[table] = await mytable.countDocuments({ idapp });
      }
    }

    return { numByTab, numUsersReg };
  } catch (e) {
    console.error(e.message);
  }
};

UserSchema.statics.getDistinctNationalityQuery = function (idapp) {
  const query = [
    {
      $match: {
        idapp,
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: { _id: '$profile.nationality', count: { $sum: 1 } },
    },
    {
      $sort: { count: -1 },
    },
  ];
  return query;
};

UserSchema.statics.findAllDistinctNationality = async function (idapp) {
  const User = this;

  return await User.aggregate(User.getDistinctNationalityQuery(idapp)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.getUsersRegDaily = function (idapp, nrec) {
  const query = [
    {
      $match: {
        idapp,
        date_reg: { $gte: tools.IncDateNow(-(1000 * 60 * 60 * 24 * nrec)) },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: {
        _id: {
          $dateToString: {
            format: '%Y-%m-%d',
            date: '$date_reg',
            timezone: 'Europe/Rome',
          },
        },
        count: { $sum: 1 },
      },
    },
    {
      $sort: { _id: 1 },
    },
  ];
  return query;
};

UserSchema.statics.getUsersRegDailyAverage = function (idapp, nrec) {
  const query = [
    {
      $match: {
        idapp,
        date_reg: { $gte: tools.IncDateNow(-(1000 * 60 * 60 * 24 * nrec)) },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: {
        _id: {
          $dateToString: {
            format: '%Y-%m-%d',
            date: '$date_reg',
            timezone: 'Europe/Rome',
          },
        },
        count: { $sum: 1 },
      },
    },
    {
      $group: {
        _id: null,
        total: { $sum: '$count' },
        days: { $sum: 1 },
      },
    },
    {
      $project: {
        _id: 0,
        dailyAverage: { $divide: ['$total', '$days'] },
      },
    },
  ];
  return query;
};

UserSchema.statics.getQueryUsersDiffusori = async function (idapp) {
  const lastn = await Settings.getValDbSettings(idapp, 'SHOW_LAST_N_USERS', 20);

  const query = [
    {
      $match: {
        idapp,
        $or: [
          {
            deleted: {
              $exists: false,
            },
          },
          {
            deleted: {
              $exists: true,
              $eq: false,
            },
          },
        ],
      },
    },
    {
      $group: {
        _id: '$aportador_solidario',
        count: {
          $sum: 1,
        },
      },
    },
    {
      $match: { count: { $gte: 2 } },
    },
    {
      $sort: {
        count: -1,
      },
    },
    { $limit: lastn },
    {
      $lookup: {
        from: 'users',
        let: {
          username: '$_id',
          idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'user',
      },
    },
    { $unwind: '$user' },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: ['$user', '$$ROOT'],
        },
      },
    },
    {
      $project: {
        _id: 0,
        count: 1,
        aportador_solidario: 1,
        username: 1,
        name: 1,
        surname: 1,
        lasttimeonline: 1,
        date_reg: 1,
        idapp: 1,
        'profile.img': 1,
        // 'profile.mycircuits': 1,
        // 'profile.handshake': 1,
      },
    },
  ];
  return query;
};

UserSchema.statics.getQueryUsersStretteDiMano = async function (idapp) {
  const lastn = await Settings.getValDbSettings(idapp, 'SHOW_LAST_N_USERS', 20);

  const query = [
    {
      $match: {
        idapp: '13',
        'profile.handshake': { $exists: true },
        $or: [
          {
            deleted: {
              $exists: false,
            },
          },
          {
            deleted: {
              $exists: true,
              $eq: false,
            },
          },
        ],
      },
    },

    {
      $group: {
        _id: '$username',
        count: { $sum: { $size: '$profile.handshake' } },
      },
    },
    { $sort: { count: -1 } },
    { $limit: lastn },
    {
      $lookup: {
        from: 'users',
        let: {
          username: '$_id',
          idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'user',
      },
    },
    { $unwind: '$user' },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: ['$user', '$$ROOT'],
        },
      },
    },
    {
      $project: {
        _id: 0,
        count: 1,
        aportador_solidario: 1,
        username: 1,
        name: 1,
        surname: 1,
        lasttimeonline: 1,
        date_reg: 1,
        idapp: 1,
        'profile.img': 1,
        // 'profile.handshake': 1,
        // 'profile.mycircuits': 1,
      },
    },
  ];
  return query;
};

UserSchema.statics.getQueryLastUsersStretteDiMano = async function (idapp) {
  const lastn = await Settings.getValDbSettings(idapp, 'SHOW_LAST_N_USERS', 20);

  const query = [
    {
      $match: {
        idapp,
        'profile.handshake': { $exists: true },
        $or: [
          {
            deleted: {
              $exists: false,
            },
          },
          {
            deleted: {
              $exists: true,
              $eq: false,
            },
          },
        ],
      },
    },
    { $unwind: '$profile.handshake' },

    // Ordiniamo in base alla data della handshake in modo decrescente
    { $sort: { 'profile.handshake.date': -1 } },

    {
      $lookup: {
        from: 'users',
        let: {
          username: '$profile.handshake.username',
          idapp: idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'userfriend',
      },
    },
    { $unwind: '$userfriend' },

    // Raggruppiamo nuovamente gli utenti dopo l'ordinamento
    {
      $group: {
        _id: '$_id',
        userId: { $first: '$userId' },
        idapp: { $first: '$idapp' },
        username: { $first: '$username' },
        aportador_solidario: { $first: '$aportador_solidario' },
        name: { $first: '$name' },
        surname: { $first: '$surname' },
        lasttimeonline: { $first: '$lasttimeonline' },
        date_reg: { $first: '$date_reg' },
        userfriend: {
          $first: {
            profile: {
              img: '$userfriend.profile.img',
              resid_str_comune: '$userfriend.profile.resid_str_comune',
              resid_province: '$userfriend.profile.resid_province',
              note: '$userfriend.profile.note',
            },
            username: '$userfriend.username',
            name: '$userfriend.name',
            surname: '$userfriend.surname',
          },
        },

        profile: {
          $first: {
            img: '$profile.img',
            handshake: '$profile.handshake',
            resid_str_comune: '$profile.resid_str_comune',
            resid_province: '$profile.resid_province',
            note: '$profile.note',
          },
        },
        handshakeDate: { $first: '$profile.handshake.date' },
      },
    },
    {
      $sort: {
        handshakeDate: -1,
      },
    },
    { $limit: lastn },
  ];
  return query;
};

UserSchema.statics.getQueryReceiveRISUsers = function (idapp, hours) {
  const query = [
    {
      $match: {
        idapp,
        'profile.lastdate_reqRis': { $gte: tools.IncDateNow(-(1000 * 60 * 60 * hours)) },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: {
        _id: '$username',
        count: {
          $sum: 1,
        },
      },
    },
    { $sort: { 'profile.lastdate_reqRis': -1 } },
    { $limit: 30 },
    {
      $lookup: {
        from: 'users',
        let: {
          username: '$_id',
          idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'user',
      },
    },
    { $unwind: '$user' },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: ['$user', '$$ROOT'],
        },
      },
    },
    {
      $project: {
        _id: 0,
        count: 1,
        aportador_solidario: 1,
        username: 1,
        name: 1,
        surname: 1,
        lasttimeonline: 1,
        'profile.lastdate_reqRis': 1,
        'profile.mycircuits': 1,
        date_reg: 1,
        idapp: 1,
        'profile.img': 1,
        'profile.handshake': 1,
        'profile.note': 1,
        'profile.da_contattare': 1,
        perm: 1,
      },
    },
  ];
  return query;
};

UserSchema.statics.getQueryListLinkReg = function (idapp, hours) {
  const query = [
    {
      $match: {
        idapp,
        'profile.lastdate_LinkReg': { $gte: tools.IncDateNow(-(1000 * 60 * 60 * hours)) },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: {
        _id: '$username',
        count: {
          $sum: 1,
        },
      },
    },
    { $sort: { 'profile.lastdate_linkReg': -1 } },
    { $limit: 30 },
    {
      $lookup: {
        from: 'users',
        let: {
          username: '$_id',
          idapp,
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  {
                    $eq: ['$$username', '$username'],
                  },
                  {
                    $eq: ['$$idapp', '$idapp'],
                  },
                ],
              },
            },
          },
        ],
        as: 'user',
      },
    },
    { $unwind: '$user' },
    {
      $replaceRoot: {
        newRoot: {
          $mergeObjects: ['$user', '$$ROOT'],
        },
      },
    },
    {
      $project: {
        _id: 0,
        count: 1,
        aportador_solidario: 1,
        username: 1,
        name: 1,
        surname: 1,
        lasttimeonline: 1,
        'profile.lastdate_linkReg': 1,
        'profile.mycircuits': 1,
        date_reg: 1,
        idapp: 1,
        'profile.img': 1,
        'profile.handshake': 1,
        'profile.note': 1,
        'profile.da_contattare': 1,
        perm: 1,
      },
    },
  ];
  return query;
};

UserSchema.statics.getUsersRegWeekly = function (idapp, nrec) {
  const query = [
    {
      $match: {
        idapp,
        date_reg: { $gte: tools.IncDateNow(-(1000 * 60 * 60 * 24 * nrec)) },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: {
        _id: {
          $dateToString: {
            format: '%Y-%U',
            date: '$date_reg',
            timezone: 'Europe/Rome',
          },
        },
        count: { $sum: 1 },
      },
    },
    {
      $group: {
        _id: null, // Raggruppa tutti i risultati
        total: { $sum: '$count' }, // Calcola il numero totale di iscritti
        days: { $sum: 1 }, // Calcola il numero totale di giorni
      },
    },
    {
      $project: {
        _id: 0, // Escludi il campo _id dal risultato finale
        dailyAverage: { $divide: ['$total', '$days'] }, // Calcola la media giornaliera
      },
    },
    {
      $sort: { _id: 1 },
    },
  ];
  return query;
};

UserSchema.statics.getnumRegNDays = function (idapp, nrec) {
  const query = [
    {
      $match: {
        idapp,
        date_reg: { $lt: tools.IncDateNow(-(1000 * 60 * 60 * 24 * nrec)) },
        $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
      },
    },
    {
      $group: {
        _id: {
          $dateToString: {
            format: '%Y-%m-%d',
            date: '$date_reg',
            timezone: 'Europe/Rome',
          },
        },
        count: { $sum: 1 },
      },
    },
    {
      $sort: { _id: 1 },
    },
  ];
  return query;
};

UserSchema.statics.calcnumRegUntilDay = async function (idapp) {
  const User = this;

  return await User.aggregate(User.getnumRegNDays(idapp, 60)).then((arr) => {
    return arr.reduce((sum, rec) => sum + rec.count, 0);
  });
};

function calculate30DayAverage(data) {
  const averages = [];

  for (let i = 0; i < data.length; i++) {
    const startDate = new Date(data[i]._id);
    let sum = data[i].count;
    let count = 1;

    for (let j = i + 1; j < data.length; j++) {
      const currentDate = new Date(data[j]._id);
      const diffInTime = Math.abs(startDate.getTime() - currentDate.getTime());
      const diffInDays = Math.ceil(diffInTime / (1000 * 60 * 60 * 24));

      if (diffInDays <= 30) {
        sum += data[j].count;
        count++;
      } else {
        break;
      }
    }

    averages.push({
      _id: data[i]._id,
      dailyAverage: sum / count,
    });
  }

  return averages;
}

UserSchema.statics.calcRegDaily = async function (idapp) {
  const User = this;

  return await User.aggregate(User.getUsersRegDaily(idapp, 120)).then((ris) => {
    // console.table(ris);
    return ris;
  });
};

UserSchema.statics.calcRegWeekly = async function (idapp) {
  const User = this;

  return await User.aggregate(User.getUsersRegDaily(idapp, 120)).then((ris) => {
    // console.table(ris);
    return calculate30DayAverage(ris);
  });
};

if (tools.INITDB_FIRSTIME) {
  console.log('   createIndex User Index...');

  UserSchema.index({ userId: 1 });
  UserSchema.index({ idapp: 1 });

  // UserSchema.index({ username: 'text', name: 'text', surname: 'text', email: 'text' });
  // UserSchema.index({ name: 'name' });
  // UserSchema.index({ name: 1 });
  // UserSchema.index({ surname: 1 });
  // ter

  tools.INITDB_FIRSTIME = false;
}

UserSchema.statics.getUsernameByIndex = async function (idapp, index) {
  const myrec = await User.findOne(
    {
      idapp,
      index,
    },
    { username: 1 }
  );

  return !!myrec ? myrec.username : '';
};

UserSchema.statics.ricalcolaIndex = async function (idapp) {
  const User = this;

  const arrusers = await User.find({
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  }).sort({ old_order: 1 });
  let index = 0;
  try {
    for (const user of arrusers) {
      let field = {
        index,
      };

      index++;

      const ris = await User.findOneAndUpdate({ _id: user._id }, { $set: field }, { new: false });
    }
  } catch (e) {
    console.error(e.message);
  }
};

UserSchema.statics.getInfoUser = async function (idapp, username) {
  return {
    username,
    is7req: await User.isUserQualified7(idapp, username),
    // is9req: await User.isUserQualified9(idapp, username),
  };
};

UserSchema.statics.checkIfSbloccatiRequisiti = async function (idapp, allData, id) {
  const User = this;

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

  if (!allData.myuser) return false;

  //if (await Nave.checkIfNaveExist(idapp, allData.myuser.username)) {
  //  // Se già sei dentro la Nave, allora sei OK
  //  return true;  //TOGLEREE
  //}

  // Controlla se Sblocca i 7 requisiti
  const is7req = await User.isUserQualified7(idapp, allData.myuser.username);
  const is9req = false;
  // const is9req = await User.isUserQualified9(idapp, allData.myuser.username);

  if (!!allData.precDataUser) {
    if (
      !allData.precDataUser.is9req &&
      is9req &&
      !(await User.isUserAlreadyQualified_2Invitati(idapp, allData.myuser.username))
    ) {
      await User.setUserQualified_2Invitati(idapp, allData.myuser.username);

      // ORA HAI I 9 REQUISITI !
      // const msgtext = telegrambot.getCiao(idapp, allData.myuser.username, allData.myuser.lang) + tools.gettranslate('HAI_I_9_REQUISITI', allData.myuser.lang);
      // telegrambot.sendMsgTelegram(idapp, allData.myuser.username, msgtext, false);  // Anche a STAFF
    }
  }

  // CHECK APORTADOR SOLIDARIO:
  if (!!allData.useraportador) {
    /*
        const is9reqAportador = await User.isUserQualified9(idapp, allData.myuser.aportador_solidario);

        if (!allData.precDataAportador.is9req && is9reqAportador) {
          // ORA HAI I 9 REQUISITI !
          const msgtext = telegrambot.getCiao(idapp, allData.myuser.aportador_solidario, allData.useraportador.lang) + tools.gettranslate('HAI_I_9_REQUISITI', allData.useraportador.lang);
          telegrambot.sendMsgTelegram(idapp, allData.myuser.aportador_solidario, msgtext, true);  // Anche a STAFF
        }
    */
  }
};

UserSchema.statics.mettiSognoePaypal = async function (idapp, modifica) {
  const User = this;

  let num = 0;

  arrusers = await User.find({
    idapp: idapp,
    $or: [
      { deleted: { $exists: false } },
      { deleted: { $exists: true, $eq: false } },
      { subaccount: { $exists: false } },
      { subaccount: { $exists: true, $eq: false } },
    ],
  });

  for (const rec of arrusers) {
    if (rec.profile.saw_zoom_presentation) {
      aggiornato = false;
      my_dream_ok = !!rec.profile.my_dream;
      if (my_dream_ok) my_dream_ok = rec.profile.my_dream.length >= 8;

      if (!my_dream_ok) {
        if (modifica) {
          rec.profile.my_dream = '............';
          aggiornato = true;
        }
        num++;
      }
      revolut_ok = !!rec.profile.revolut;
      paypal_ok = !!rec.profile.email_paypal;

      if (paypal_ok) paypal_ok = rec.profile.email_paypal.length > 8;

      if (!revolut_ok && !paypal_ok) {
        if (modifica) {
          rec.profile.email_paypal = rec.email;
          aggiornato = true;
        }
        num++;
      }

      if (aggiornato && modifica) {
        let allData = {};
        allData.myuser = await User.getUserById(idapp, rec.id);
        if (!!allData.myuser) allData.precDataUser = await User.getInfoUser(idapp, allData.myuser.username);
        else allData.precDataUser = null;

        const risupd = await User.findByIdAndUpdate(
          rec._id,
          {
            'profile.email_paypal': rec.profile.email_paypal,
            'profile.my_dream': rec.profile.my_dream,
          },
          { new: false }
        );

        if (risupd) {
          await User.checkIfSbloccatiRequisiti(idapp, allData, rec.id);
        }
      }
    }
  }

  return { num };
};

UserSchema.statics.convSubAccount = async function (idapp) {
  const User = this;

  // Solo i Cancellati !
  arrusers = await User.find({ idapp: idapp, deleted: true });
  let num = 0;
  for (const rec of arrusers) {
    // Cerca il suo Principale!
    const trovato = await User.findOne({
      idapp,
      'profile.teleg_id': rec.profile.teleg_id,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });
    if (trovato) {
      num++;
      await User.findByIdAndUpdate(rec._id, { subaccount: true }, { new: false });
    }
  }

  return { num };
};

UserSchema.statics.getLastRec = async function (idapp) {
  const User = this;

  lastrec = await User.find({ idapp }).sort({ date_reg: -1 }).limit(1).lean();
  if (!!lastrec) {
    return lastrec[0];
  } else {
    return null;
  }
};

UserSchema.statics.DbOp = async function (idapp, mydata) {
  const User = this;
  try {
    if (mydata.dbop === 'changeCellInt') {
      arrusers = await User.find({ idapp: idapp });
      let num = 0;
      for (const rec of arrusers) {
        //  DISATTIVATO: ORA NON MI SERVE PIU
        if (false) {
          let mycell = tools.removespaces(rec.profile.intcode_cell + rec.profile.cell);
          await User.findOneAndUpdate({ _id: rec._id }, { $set: { 'profile.cell': mycell } });
          num++;
        }
      }

      return { num };

      // return User.updateMany({ idapp }, { $set: { 'profile.cell': { $concat: ["$profile.intcode_cell", "$profile.cell"] } } })
    } else if (mydata.dbop === 'changeEmailLowerCase') {
      arrusers = await User.find({ idapp: idapp });
      let num = 0;
      for (const rec of arrusers) {
        let myemail = rec.email.toLowerCase();
        if (myemail !== rec.email) {
          await User.findOneAndUpdate({ _id: rec._id }, { $set: { email: myemail } });
          num++;
        }
      }

      return { num };

      /*} else if (mydata.dbop === 'creaLista') {

        await ListaIngresso.deleteMany({ idapp, added: false });

        arrusers = await User.find({
          'idapp': idapp,
          $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }]
        }).sort({ date_added: 1 });
        let num = 0;

        num += await addUtentiInLista(idapp, 1, arrusers);
        num += await addUtentiInLista(idapp, 2, arrusers);
        num += await addUtentiInLista(idapp, 3, arrusers);
        num += await addUtentiInLista(idapp, 4, arrusers);
        num += await addUtentiInLista(idapp, 5, arrusers);
        // num += await addUtentiInLista(idapp, 3);
        // num += await addUtentiInLista(idapp, 4);

        return { num };
        */
    }
  } catch (e) {
    console.error(e.message);
  }
};

UserSchema.statics.getExtraInfoByUsername = async function (idapp, username) {
  const User = this;

  let myuser = await User.findOne({ idapp, username }).lean();
  if (myuser) {
    myuserextra = await User.addExtraInfo(idapp, myuser);
    return myuser.profile;
  }

  return null;
};
UserSchema.statics.getProfilePerActivitiesByUsername = async function (idapp, username) {
  const User = this;

  try {
    let myuser = await User.findOne({ idapp, username }).lean();
    if (myuser) {
      return {
        mygroups: myuser.profile.mygroups,
        mycircuits: myuser.profile.mycircuits,
      };
    }
  } catch (e) {
    console.error('e', e);
  }

  return null;
};

UserSchema.statics.addExtraInfo = async function (idapp, recUser, version) {
  try {
    // Verifica e aggiornamento della versione (blocco sequenziale)
    if (version && recUser) {
      const versattualeuser = recUser?.profile?.version || 0;
      if (versattualeuser < version) {
        // Aggiorna la versione utente (eventuali chiamate dipendenti vanno eseguite in sequenza)
        recUser = await User.updateVersion(versattualeuser, recUser);
        await User.findOneAndUpdate({ _id: recUser._id }, { $set: { 'profile.version': version } });
      }
    }

    // Esegui le query indipendenti in parallelo
    const [
      listSentMyRequestFriends,
      listSentMyRequestGroups,
      listRefusedGroups,
      listManageGroups,
      reactions,
      userstoverify,
      circuitobj,
      useraccounts,
      calcstat,
      calcOther,
    ] = await Promise.all([
      // Richiesta per gli amici a cui è stata mandata una richiesta
      User.find(
        {
          idapp,
          'profile.req_friends': { $elemMatch: { username: recUser.username } },
          $or: [{ deleted: { $exists: false } }, { deleted: { $eq: false } }],
        },
        { username: 1 }
      ).lean(),

      // Richiesta per i gruppi a cui è stata mandata una richiesta
      MyGroup.find(
        {
          idapp,
          req_users: { $elemMatch: { username: recUser.username } },
          $or: [{ deleted: { $exists: false } }, { deleted: { $eq: false } }],
        },
        MyGroup.getWhatToShow_Unknown()
      ).lean(),

      // Richiesta per i gruppi rifiutati
      MyGroup.find(
        {
          idapp,
          refused_users: { $elemMatch: { username: recUser.username } },
          $or: [{ deleted: { $exists: false } }, { deleted: { $eq: false } }],
        },
        MyGroup.getWhatToShow_Unknown()
      ).lean(),

      // Richiesta per i gruppi gestiti (admin)
      MyGroup.find({
        idapp,
        admins: { $elemMatch: { username: recUser.username } },
        $or: [{ deleted: { $exists: false } }, { deleted: { $eq: false } }],
      }).lean(),

      // Reazioni
      Reaction.find({ idapp, username: recUser.username }).lean(),

      // Altri utenti da verificare
      User.getUsersToVerify(idapp, recUser.username),

      // Circuiti associati
      Circuit.getCircuitsByUsername(idapp, recUser.username, recUser, 5),

      // Conto degli account utente
      Account.getUserAccounts(idapp, recUser.username),

      // Statistiche dell'utente
      User.calculateStat(idapp, recUser.username),

      // Altri calcoli per l'utente
      User.calcOtherByUser(idapp, recUser._id),
    ]);

    // Per ogni gruppo gestito, recupera gli account in parallelo
    const manageGroupsWithAccounts = await Promise.all(
      listManageGroups.map(async (group) => {
        const myaccounts = await Account.getGroupAccounts(idapp, group.groupname);
        return { ...group, account: myaccounts && myaccounts.length > 0 ? myaccounts[0] : null };
      })
    );

    // Assegna i risultati al profilo utente
    recUser.profile.asked_friends = listSentMyRequestFriends || [];
    recUser.profile.asked_groups = listSentMyRequestGroups || [];
    recUser.profile.refused_groups = listRefusedGroups || [];
    recUser.profile.reaction = reactions;
    recUser.profile.userstoverify = userstoverify;
    recUser.profile.manage_mygroups = manageGroupsWithAccounts;
    recUser.profile = { ...recUser.profile, ...circuitobj, useraccounts };
    recUser.calcstat = calcstat;
    recUser.profile.calc = calcOther;

    return recUser;
  } catch (e) {
    console.error('Err addExtraInfo', e);
  }
  return recUser;
};

addRecordReaction = async function (arr, fields, fieldtoset) {
  let newrecord = {};

  try {
    if (tools.isArray(arr)) {
      for (let myrec of arr) {
        if (myrec.id) {
          let myfields = { ...{ idrec: myrec.id }, ...fields };
          newrecord = await Reaction.findOne(myfields);

          if (!newrecord) {
            newrecord = new Reaction(myfields);
            // console.log('Nuovo Record: ', newrecord._doc);
          }

          newrecord[fieldtoset] = true;

          await newrecord.save();

          let newrecfound = await Reaction.findOne(myfields);
        }
        // if (newrecfound)
        //   console.log('trovato')
      }
    }
  } catch (e) {
    console.error(e.message);
  }
};

UserSchema.statics.moverecordsFavorite = async function (tab) {
  const { MySkill } = require('../models/myskill');
  const { MyGood } = require('../models/mygood');

  console.log('INIZIO - moverecordsFavorite tab', tab);

  const arrusers = await User.find({ idapp: '13' }).lean();

  let index = 0;
  try {
    let index = 0;
    let totali = 0;
    for (let user of arrusers) {
      if (user.profile) {
        let arrfav = user.profile.favorite;
        let arrseen = user.profile.seen;
        let arrbookmark = user.profile.bookmark;
        let arrattend = user.profile.attend;

        totali++;

        if (arrfav || arrseen) {
          console.log('utente n.', index, 'su', totali, user.username);
          index++;

          let fields = {
            idapp: user.idapp,
            userId: user._id,
            username: user.username,
            tab,
          };

          await addRecordReaction(arrfav, fields, 'fav');
          await addRecordReaction(arrseen, fields, 'seen');
          await addRecordReaction(arrbookmark, fields, 'book:');
          await addRecordReaction(arrattend, fields, 'attend');
        }
      }
    }
  } catch (e) {
    console.error(e.message);
  }

  console.log('fine moverecordsFavorite');
};

UserSchema.statics.removerecordsFavorite = async function () {
  // Rimuovi i record del vecchio schema
  const attivacanc = true;

  if (attivacanc) {
    const queryfind = { idapp: '13' };
    await User.findOneAndUpdate(queryfind, {
      $set: {
        'profile.favorite': [],
        'profile.bookmark': [],
        'profile.attend': [],
        'profile.seen': [],
      },
    });
  }
};

UserSchema.statics.updateVersion = async function (userversion, recUser) {
  const { MySkill } = require('../models/myskill');
  const { MyGood } = require('../models/mygood');
  const { City } = require('../models/city');

  if (userversion < 604) {
    if (!recUser.profile.notifs || recUser.profile.notifs.length === 0) {
      recUser.profile.notifs = shared_consts.DEFAULT_NOTIFS_USER;
    }

    const recfavoriteNotif = recUser.profile.notifs.find(
      (rec) => rec.dir === shared_consts.TypeNotifs.TYPEDIR_FAVORITE
    );
    if (!recfavoriteNotif) {
      recUser.profile.notifs.push({
        dir: shared_consts.TypeNotifs.TYPEDIR_FAVORITE,
        value: shared_consts.TypeNotifs.ID_FAVORITE_ADDED,
      });
    }

    const recAttendNotif = recUser.profile.notifs.find((rec) => rec.dir === shared_consts.TypeNotifs.TYPEDIR_EVENTS);
    if (recAttendNotif) {
      if (!tools.isBitActive(recAttendNotif.value, shared_consts.TypeNotifs.ID_EVENTS_ATTEND)) {
        recAttendNotif.value = tools.SetBit(recAttendNotif.value, shared_consts.TypeNotifs.ID_EVENTS_ATTEND);
      }

      for (let i = 0; i < recUser.profile.notifs.length; i++) {
        if (recUser.profile.notifs[i].dir === shared_consts.TypeNotifs.TYPEDIR_EVENTS)
          recUser.profile.notifs[i].value = recAttendNotif.value;
      }
    }

    // Imposta la provincia, se non l'ho messa, in base al bene o servizio che hai messo.
    if (!recUser.profile.resid_province) {
      let resid_province = '';
      const firstrec = await MySkill.findOne({ userId: recUser._id }).lean();
      if (firstrec && firstrec.idCity && firstrec.idCity.length > 0) {
        resid_province = await City.getProvinceByIdCity(firstrec.idCity[0]);
      } else {
        const firstrec = await MyGood.findOne({ userId: recUser._id }).lean();
        if (firstrec && firstrec.idCity && firstrec.idCity.length === 1) {
          resid_province = await City.getProvinceByIdCity(firstrec.idCity[0]);
        }
      }

      if (resid_province) {
        await User.findOneAndUpdate({ _id: recUser._id }, { $set: { 'profile.resid_province': resid_province } });
      }
    }

    let recmybach = recUser.profile.notifs.find((rec) => rec.dir === shared_consts.TypeNotifs.TYPEDIR_BACHECA);
    if (!tools.isBitActive(recmybach.value, shared_consts.UsersNotif.NEW_ADV_YOUR_PROVINCE)) {
      recmybach.value = tools.SetBit(recmybach.value, shared_consts.UsersNotif.NEW_ADV_YOUR_PROVINCE);
      for (let i = 0; i < recUser.profile.notifs.length; i++) {
        if (recUser.profile.notifs[i].dir === shared_consts.TypeNotifs.TYPEDIR_BACHECA)
          recUser.profile.notifs[i].value = recmybach.value;
      }
    }

    await User.findOneAndUpdate({ _id: recUser._id }, { $set: { 'profile.notifs': recUser.profile.notifs } });
  }

  if (userversion < 1016) {
  }

  return recUser;
};

UserSchema.statics.calcOtherByUser = async function (idapp, userId) {
  const { MySkill } = require('../models/myskill');
  const { MyGood } = require('../models/mygood');
  const { MyHosp } = require('../models/myhosp');

  let calc = {};

  let numgoods = await MyGood.countDocuments({ idapp, userId });
  let numskills = await MySkill.countDocuments({ idapp, userId });
  let numhosps = await MyHosp.countDocuments({ idapp, userId });

  calc.numGoodsAndServices = numgoods + numskills + numhosps;

  return calc;
};

UserSchema.statics.tooManyReqPassword = async function (idapp, email, set) {
  const User = this;

  const maxnum = 30;

  const user = await User.findByEmail(idapp, email);
  if (user) {
    if (!user.retry_pwd) user.retry_pwd = 0;
    if (set && user.retry_pwd <= maxnum) {
      user.retry_pwd++;

      await User.findOneAndUpdate({ _id: user._id }, { $set: { retry_pwd: user.retry_pwd } });
    }
    return user.retry_pwd > maxnum;
  }
};
UserSchema.statics.tooManyLoginWrong = async function (idapp, username, set) {
  const User = this;

  const maxnum = 30;

  const user = await User.findByUsername(idapp, username, true, false);
  if (user) {
    if (!user.retry_pwd) user.retry_pwd = 0;
    if (set && user.retry_pwd <= maxnum) {
      user.retry_pwd++;

      await User.findOneAndUpdate({ _id: user._id }, { $set: { retry_pwd: user.retry_pwd } });
    }
    return { troppilogin: user.retry_pwd > maxnum, retry_pwd: user.retry_pwd };
  }

  return { troppilogin: false, retry_pwd: 0 };
};

UserSchema.statics.setLastCircuitOpened = async function (idapp, username, circuitpath) {
  try {
    return await User.findOneAndUpdate({ idapp, username }, { $set: { 'profile.last_circuitpath': circuitpath } });
  } catch (e) {
    return false;
  }
};

UserSchema.statics.setReceiveRis = async function (idapp, username) {
  const User = this;

  return await User.findOneAndUpdate(
    {
      idapp,
      username,
    },
    { $set: { 'profile.lastdate_reqRis': new Date() } },
    { new: false }
  )
    .lean()
    .then((record) => {
      return !!record;
    });
};
UserSchema.statics.setLinkReg = async function (idapp, username) {
  const User = this;

  return await User.findOneAndUpdate(
    {
      idapp,
      username,
    },
    { $set: { 'profile.lastdate_LinkReg': new Date() } },
    { new: false }
  )
    .lean()
    .then((record) => {
      return !!record;
    });
};

UserSchema.statics.addNewSite = async function (idappPass, body) {
  const User = this;

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

  // Inserisci il nuovo Sito (se non esiste)
  // Site.
  // Inserisci il Nuovo Utente (se non esiste)

  if (!body.code || body.code.toString() !== process.env.AUTH_NEW_SITES) {
    return { code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, idapp: -1 };
  }

  try {
    // cerca un IdApp Libero
    let idapp = await Site.generateNewSite_IdApp(idappPass, body, true);

    if (idapp) {
      let arrSite = await Site.find({ idapp }).lean();
      let numutenti = 0;
      if (idapp) {
        numutenti = await User.countDocuments({ idapp });
      }

      if (arrSite && arrSite.length === 1 && numutenti < 2) {
        const MyTelegramBot = require('../telegram/telegrambot');

        // Nessun Sito Installato e Nessun Utente installato !
        let myuser = new User();
        myuser._id = new ObjectId();
        myuser.idapp = idapp;
        myuser.email = body.email;
        myuser.username = body.username;
        myuser.password = body.password;
        myuser.name = body.username;
        myuser.index = 1;
        myuser.surname = '';
        myuser.lang = 'it';
        myuser.verified_email = true;
        myuser.verified_by_aportador = true;
        myuser.perm = '3';
        myuser.profile.special_req = true;
        myuser.profile.nationality = 'IT';
        myuser.profile.manage_telegram = true;
        myuser.profile.admin_telegram = true;
        myuser.profile.teleg_id = MyTelegramBot.ADMIN_IDTELEGRAM_SERVER;
        myuser.profile.username_telegram = MyTelegramBot.ADMIN_USERNAME_TELEGRAM;
        myuser.lasttimeonline = new Date();
        myuser.date_reg = new Date();

        await myuser.save();

        const { MyBot } = require('../models/bot');

        // Genera il Menu del BOT:
        await MyBot.generateBotMenuRecords(idapp);

        return { code: server_constants.RIS_CODE_OK, idapp };
      }
    }

    return { code: server_constants.RIS_CODE_ERR, idapp: -1 };
  } catch (e) {
    console.error('Error in addNewSite:', e.message);
  }
};

UserSchema.statics.renameCircuitName = async function (idapp, oldcircuitname, newcircuitname) {
  const User = this;

  return await User.updateMany(
    { idapp, 'profile.mycircuits.circuitname': oldcircuitname },
    { $set: { 'profile.mycircuits.$.circuitname': newcircuitname } }
  );
};

UserSchema.statics.getnumAnnunci = async function (idapp) {
  const { MySkill } = require('../models/myskill');
  const { MyGood } = require('../models/mygood');
  const { MyBacheca } = require('../models/mybacheca');
  const { MyHosp } = require('../models/myhosp');

  let num = 0;

  try {
    num += await MySkill.countDocuments({ idapp });
    num += await MyGood.countDocuments({ idapp });
    num += await MyBacheca.countDocuments({ idapp });
    num += await MyHosp.countDocuments({ idapp });
  } catch (e) {}

  return num;
};

// crea una funzione per aggiornare il lasttimeonline e useragent
UserSchema.statics.updateLastTimeAndUserAgent = async function (id, useragent) {
  const User = this;

  // cerca lo user by id e aggiorna i campi
  // e ritorna il nuovo record
  const ris = await User.findOneAndUpdate(
    { _id: id },
    {
      $set: {
        lasttimeonline: new Date(),
        useragent,
        retry_pwd: 0,
      },
    },
    { returnDocument: 'after' }
  ).lean();

  return ris;
};
UserSchema.statics.getMyGroupsById = async function (id) {
  const User = this;

  // cerca lo user by id e ritorna "profile.mygroups"
  const ris = await User.findOne({ _id: id }, { 'profile.mygroups': 1 }).lean();
  if (ris && ris.profile) {
    return ris.profile.mygroups;
  }

  return [];
};
UserSchema.statics.updateProvinceUserByComune = async function (idapp, userId, idcomune) {
  try {
    const User = this;

    const { City } = require('../models/city');

    const recCity = await City.getRecCityByIdCity(idcomune);

    const updateData = {
      'profile.resid_str_comune': recCity ? recCity.comune : '',
      'profile.resid_province': recCity ? recCity.prov : '',
    };

    await User.findOneAndUpdate({ _id: userId }, { $set: updateData }, { new: true });

    // Ritorna i dati aggiornati nel formato che ti serve
    return {
      _id: userId,
      profile: {
        resid_str_comune: updateData['profile.resid_str_comune'],
        resid_province: updateData['profile.resid_province'],
      },
    };
  } catch (e) {
    console.error('Errore updateProvinceUserByComune', e.message);
    return {
      _id: userId,
      profile: {
        resid_str_comune: '',
        resid_province: '',
      },
    };
  }
};

UserSchema.statics.createNewSubRecord = async function (idapp, req) {
  const User = this;

  // console.log("GENERA TOKEN : ");
  let userId = req.body.data.userId;
  let fieldkey = req.body.data.field;
  let data = req.body.data.data;

  const filtro = { _id: userId };

  let fieldsvalue = { ...data };

  fieldsvalue.date_created = new Date();
  fieldsvalue.date_updated = new Date();

  var set = {
    profile: {},
  };
  set.profile[fieldkey] = fieldsvalue;

  const rec = await User.findOneAndUpdate(filtro, { $set: set }, { new: false });

  return rec;
};

// Nel tuo User schema, aggiungi questo metodo statico:

UserSchema.statics.isEnableToReceiveEmailByUsernameECmd = async function (idapp, username, cmd) {
  let ricevo = true;
  if (cmd === shared_consts.CIRCUITCMD.SETFIDO) {
    // Controllo se l'utente ha scelto di ricevere l'email
  }

  return ricevo;
};

UserSchema.statics.getTokenByUsernameAndCircuitName = async function (idapp, username, circuitname) {
  const User = this;

  const user = await User.findOne(
    {
      idapp,
      username,
      'profile.mycircuits.circuitname': circuitname,
    },
    {
      'profile.mycircuits.$': 1,
    }
  );

  return user?.profile?.mycircuits?.[0]?.token || null;
};

UserSchema.statics.softDelete = async function (id) {
  return this.findByIdAndUpdate(
    id,
    {
      deleted: true,
      deletedAt: new Date(),
    },
    { new: true }
  );
};

UserSchema.statics.getUsersList = function (idapp) {
  return this.find({
    idapp: idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: false }],
  }).lean();
};

class Hero {
  constructor(name, level) {
    this.name = name;
    this.level = level;
  }

  // Adding a method to the constructor
  greet() {
    return `${this.name} says hello.`;
  }
}

const FuncUsers = {
  createRegistration_withTelegram(userdata) {
    const telegrambot = require('../telegram/telegrambot');

    try {
      // Cerca se esiste già
      const user = new User(userdata);

      user.verified_email = false;
      user.lasttimeonline = new Date();
      user.date_reg = new Date();

      return user
        .save()
        .then(async () => {
          return User.findByUsername(user.idapp, user.username, false).then(async (usertrovato) => {
            const numutenti = await User.getNumUsers(user.idapp);

            let msg = '++ Nuovo Entrato: [' + numutenti + '] ' + user.username + ' ' + user.name + ' ' + user.surname;

            await telegrambot.sendMsgTelegramToTheManagers(user.idapp, msg);

            return telegrambot.askConfirmationUser(
              user.idapp,
              shared_consts.CallFunz.REGISTRATION,
              user,
              '',
              '',
              '',
              ''
            );
          });
        })
        .catch((e) => {
          console.error(e.message);
        });
    } catch (e) {
      console.error(e.message);
    }
  },
};

UserSchema.index({ 'tokens.token': 1, 'tokens.access': 1, idapp: 1, deleted: 1, updatedAt: 1 });

const User = mongoose.models.User || mongoose.model('User', UserSchema);

module.exports = {
  User,
  Hero,
  FuncUsers,
};
