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

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

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

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

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

mongoose.Promise = global.Promise;

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

mongoose.set('debug', process.env.DEBUG);

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'
    }*/
  },
  idapp: {
    type: String,
    required: true,
  },
  index: {
    type: Number
  },
  ind_order: {
    type: Number
  },
  old_order: {
    type: Number
  },
  username: {
    type: String,
    required: true,
    trim: true,
    minlength: 6,
    unique: false,
  },
  name: {
    type: String,
    trim: true,
  },
  surname: {
    type: String,
    trim: true,
  },
  password: {
    type: String,
    require: true,
    minlength: 6,
  },
  lang: {
    type: String,
    require: true,
  },
  linkreg: {
    type: String,
    required: false
  },
  verified_email: {
    type: Boolean,
  },
  made_gift: {
    type: Boolean,
  },
  tokens: [{
    access: {
      type: String,
      required: true
    },
    browser: {
      type: String,
      required: true
    },
    token: {
      type: String,
      required: true
    },
    date_login: {
      type: Date
    },
  }],
  perm: {
    type: Number
  },
  ipaddr: {
    type: String,
  },
  date_reg: {
    type: Date,
  },
  date_tokenforgot: {
    type: Date
  },
  tokenforgot: {
    type: String,
  },
  lasttimeonline: {
    type: Date
  },
  news_on: {
    type: Boolean
  },
  aportador_solidario: {  // da cancellare
    type: String,
  },
  aportador_iniziale: {
    type: String,
  },
  aportador_solidario_nome_completo: {
    type: String,
  },
  aportador_solidario_ind_order: {
    type: Number,
  },
  note: {
    type: String,
  },
  deleted: {
    type: Boolean
  },
  sospeso: {
    type: Boolean
  },
  profile: {
    img: {
      type: String
    },
    nationality: {
      type: String
    },
    intcode_cell: {
      type: String
    },
    iso2_cell: {
      type: String
    },
    cell: {
      type: String
    },
    country_pay: {
      type: String
    },
    email_paypal: {
      type: String
    },
    paymenttypes: [],
    username_telegram: {
      type: String
    },
    teleg_id: {
      type: Number
    },
    teleg_id_old: {
      type: Number
    },
    teleg_checkcode: {
      type: Number
    },
    manage_telegram: {
      type: Boolean
    },
    dateofbirth: {
      type: Date,
    },
    my_dream: {
      type: String,
    },
    saw_and_accepted: {
      type: Number,
    },
    saw_zoom_presentation: {
      type: Boolean
    },
    special_req: {
      type: Boolean
    },
    vuole_ritessersi: {
      type: Boolean
    },
    sex: {
      type: Number,
    },
    chisei: {
      type: String
    },
    iltuoimpegno: {
      type: String
    },
    come_aiutare: {
      type: String
    },
  },

});

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

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

UserSchema.methods.generateAuthToken = function (req) {
  // console.log("GENERA TOKEN : ");
  const user = this;

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

  const access = 'auth';
  const browser = useragent;
  const token = jwt.sign({ _id: user._id.toHexString(), access }, process.env.SIGNCODE).toString();
  const date_login = new Date();

  // CANCELLA IL PRECEDENTE !
  user.tokens = user.tokens.filter(function (tok) {
    return (tok.access !== access) || ((tok.access === access) && (tok.browser !== browser));
  });
  user.tokens.push({ access, browser, token, date_login });

  user.lasttimeonline = new Date();

  return user.save()
    .then(() => {
      // console.log("TOKEN CREATO IN LOGIN : " + token);
      return token;
    })
    .catch(err => {
      console.log("Error", err.message);
    });
};

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.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.isTraduttrici = function (perm) {
  try {
    return ((perm & shared_consts.Permissions.Traduttrici) === shared_consts.Permissions.Traduttrici);
  } catch (e) {
    return false
  }
};

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

UserSchema.statics.findByToken = function (token, typeaccess) {
  const User = this;
  let decoded;

  try {
    decoded = jwt.verify(token, process.env.SIGNCODE);
  } catch (e) {
    return Promise.resolve(null);
  }

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

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

  try {
    decoded = jwt.verify(token, process.env.SIGNCODE);
  } catch (e) {
    return Promise.resolve(null);
  }

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

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

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

    pwd = user.password;

    return new Promise((resolve, reject) => {
      // Use bcrypt.compare to compare password and user.password
      // console.log("pwd1 " + password);
      // console.log("pwd2 " + pwd);
      bcrypt.compare(password, pwd, (err, res) => {
        if (res) {
          resolve(user);
        } else {
          return resolve(null);
        }
      });
    });
  });
};


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

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

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

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

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

  const myrec = await User.findOne({
    'idapp': idapp,
    'username': username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  }, {
    lang: 1,
    ind_order: 1,
    username: 1,
    aportador_solidario: 1,
    name: 1,
    surname: 1,
    deleted: 1,
    sospeso: 1,
    verified_email: 1,
    'profile.teleg_id': 1,
    'profile.saw_zoom_presentation': 1,
    'profile.saw_and_accepted': 1,
    'profile.email_paypal': 1,
    'profile.my_dream': 1,
    'profile.paymenttypes': 1,
    'profile.cell': 1,
    made_gift: 1,
    email: 1,
    date_reg: 1,
    img: 1
  }).then((ris) => {
    if (!!ris) {
      // console.log('ris', ris);
      if (!!ris._doc)
        return ris._doc;
      else
        return null;
    }
  });

  if (myrec) {
    myrec.qualified = await User.isUserQualified7(idapp, myrec.username);
    myrec.numNaviEntrato = await Nave.getnumNaviByUsername(idapp, myrec.username);
    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) {

  if (username === undefined)
    return null;

  const arrrec = await ListaIngresso.getInvitati(idapp, username,
    {
      index: 1,
      lang: 1,
      invitante_username: 1,
      ind_order: 1,
      username: 1,
      name: 1,
      surname: 1,
      verified_email: 1,
      'profile.teleg_id': 1,
      'profile.saw_zoom_presentation': 1,
      'profile.saw_and_accepted': 1,
      'profile.email_paypal': 1,
      'profile.my_dream': 1,
      'profile.paymenttypes': 1,
      'profile.cell': 1,
      made_gift: 1,
      email: 1,
      date_reg: 1,
      img: 1
    }
  );

  // Ottieni gli invitati che ancora non hanno un'imbarco
  const arrinv = await User.find({
    idapp,
    aportador_solidario: username,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }]
  });

  for (const inv of arrinv) {
    if (!arrrec.find((rec) => rec.username === inv.username))
      arrrec.push(inv);
  }


  if (!!arrrec) {
    for (const rec of arrrec) {
      rec.qualified = await User.isUserQualified7(idapp, rec.username);
      rec.numNaviEntrato = await Nave.getnumNaviByUsername(idapp, rec.username);
      rec.numinvitati = await ListaIngresso.getnumInvitati(idapp, rec.username);
      rec.numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, rec.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,
          'profile.my_dream': { $exists: true },
          'profile.email_paypal': { $exists: true },
          'profile.paymenttypes': { "$in": ['paypal'] },
          $and: [
            { "$expr": { "$gt": [{ "$strLenCP": "$profile.my_dream" }, 10] } },
            { "$expr": { "$gt": [{ "$strLenCP": "$profile.email_paypal" }, 6] } }
          ],
          // $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.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 User.count({
    idapp,
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    'profile.paymenttypes': { "$in": ['paypal'] },
    $where: "this.profile.paymenttypes.length >= 1",
    'profile.email_paypal': { $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 JSON.stringify(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.findByEmail = function (idapp, email) {
  const User = this;

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

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

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

UserSchema.statics.findByIndOrder = function (idapp, ind_order) {
  const User = this;

  try {
    // ++Todo: non mettere tutti i campi !!
    return User.findOne({
      idapp,
      ind_order,
      $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 }
    }
  });
};

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.getUserById = function (idapp, id) {
  const User = this;

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

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 } }],
  })
    .then((rec) => {
      return (!!rec) ? rec._doc : null;
    }).catch((e) => {
      console.error('UserExistByIdTelegram', e);
    });
};

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

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

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 })
    .then((rec) => {
      return (!!rec) ? rec.profile.teleg_id : null;
    }).catch((e) => {
      console.error('TelegIdByUsername', e);
    });
};

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 }).then((record) => {
    return !!record;
  });

};

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 }).then((record) => {
    return record;
  });

};

UserSchema.statics.getLangByIndOrder = async function (idapp, ind_order) {
  const User = this;

  return await User.findOne({ idapp, ind_order }, { lang: 1 })
    .then((rec) => {
      return (!!rec) ? rec.lang : '';
    }).catch((e) => {
      return 'it';
    });
};


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,
  };

  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) {
  const User = this;

  return await User.findOne({
    idapp, username,
    $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('getNameSurnameByUsername', e);
    });
};

UserSchema.statics.getSmallRecByIndOrder = async function (idapp, ind_order) {

  try {
    const rec = await ListaIngresso.getarray(idapp,
      {
        idapp,
        ind_order,
        deleted: false,
      },
      {
        idapp: 1,
        ind_order: 1,
        username: 1,
        name: 1,
        surname: 1
      });

    if (!!rec)
      return rec[0];

    return null;

  } catch (e) {
    console.error('getSmallRecByIndOrder', e);
  }

};


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

  return await User.find({ idapp, 'profile.manage_telegram': true }, { 'profile.teleg_id': 1, perm: 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 } })
      .then((arrrec) => {
        return (!!arrrec) ? arrrec : null;
      }).catch((e) => {
        console.error('getUsersTelegALL', e);
      });
  } else {
    return await User.find({ idapp, 'profile.teleg_id': { $gt: 0 } })
      .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('getusersManagers', e);
      return false
    });
};

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

  return await User.findOne({
    idapp,
    username: 'paoloar77',
    '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('getusersManagers', 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,
      verified_email: 1,
      made_gift: 1,
      perm: 1,
      email: 1,
      date_reg: 1,
      img: 1
    }
  )
};


UserSchema.statics.getUsersListByParams = function (params) {
  const User = this;

  myclParamQuery = new queryclass.CParamsQuery(params);

  const filterMatchBefore = `${ myclParamQuery.filter }`;

  return User.find(
    { $match: filterMatchBefore },
    { 'idapp': idapp },
    {
      username: 1,
      name: 1,
      surname: 1,
      verified_email: 1,
      made_gift: 1,
      perm: 1,
      email: 1,
      date_reg: 1,
      img: 1,
      lasttimeonline: 1,
      news_on: 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.teleg_id', type: tools.FieldType.number },
    { field: 'profile.username_telegram', type: tools.FieldType.string },
    { field: 'aportador_solidario', type: tools.FieldType.string }]
};

UserSchema.statics.executeQueryTable = function (idapp, params) {
  params.fieldsearch = this.getFieldsForSearch();
  return tools.executeQueryTable(this, idapp, params);
};

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

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

  return User.find(myfind, (err, arrrec) => {
    return arrrec
  });
};

UserSchema.statics.DuplicateAllRecords = async function (idapporig, idappdest) {

  return 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: {},
      downline: [],
      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);
    // }

    dashboard.downline = [];

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

    dashboard.downline = await User.getDownlineByUsername(idapp, username);
    // dashboard.downnotreg = await ExtraList.getDownlineNotRegisteredByNameSurname(idapp, dashboard.myself.name + ' ' + dashboard.myself.surname);

    dashboard.downbyuser = {};

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

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

    dashboard.arrimbarchi = await ListaIngresso.findAllByUsername(idapp, username);

    // dashboard.arrprofili = await Nave.getArrProfiliByIndOrder(idapp, dashboard.myself.ind_order);

    dashboard.arrposizioni = await Nave.getArrPosizioniByUsername(idapp, username);

    const arrrec = await ListaIngresso.getProssimiInLista(idapp, true);

    for (let myimbarco of dashboard.arrimbarchi) {
      if (!!myimbarco.invitante_username)
        dashboard.arrusers[myimbarco.invitante_username] = await User.getUserShortDataByUsername(idapp, myimbarco.invitante_username);
      myimbarco._doc.posiz = await ListaIngresso.getPosizioneInLista(idapp, arrrec, myimbarco.ind_order, myimbarco.num_tess);
    }
    dashboard.navi_partenza = await NavePersistente.getListaNavi(idapp);

    dashboard.lastnave = await NavePersistente.getLastNave(idapp);

    for (let mypos of dashboard.arrposizioni) {
      mypos._doc.rec = await Nave.getNaveByRigaCol(idapp, mypos.riga, mypos.col);
      mypos._doc.nave_partenza = await NavePersistente.findByRigaColByDonatore(idapp, mypos.riga, mypos.col, 0);
    }

    //for (let indriga = 0; indriga < 10; indriga++) {
    //  dashboard.navi_partenza.push(await Nave.getPrimaNaveByRiga(idapp, indriga));
    //}


    const arrnew = [];

    for (let mypos of dashboard.arrposizioni) {
      // Controlla se è presente la Nave con il num_tess pari

      let trovato = false;

      if (mypos.num_tess % 2 !== 0) {
        for (let myrec of dashboard.arrposizioni) {
          if (myrec.num_tess === mypos.num_tess + 1 && (myrec.ind_order === mypos.ind_order)) {
            // La Nave di Ritorno (numtess = 2) esiste nella lista !
            trovato = true
          }
        }
      } else {
        trovato = true;
      }
      if (!trovato) {
        const mymediatore = mypos._doc.rec.mediatore.arrdonatori[7];
        if (!!mymediatore) {
          const myrec = {
            riga: mymediatore.riga,
            col: mymediatore.col,
            name: mypos._doc.rec.mediatore.recmediatore.name,
            surname: mypos._doc.rec.mediatore.recmediatore.surname,
            username: mypos._doc.rec.mediatore.recmediatore.username,
            num_tess: mypos._doc.rec.mediatore.recmediatore.num_tess + 1,
            rec: await Nave.getNaveByRigaCol(idapp, mymediatore.riga, mymediatore.col),
            nave_partenza: {},
          };

          myrec.nave_partenza = await NavePersistente.findByRigaColByDonatore(idapp, myrec.riga, myrec.col, 0);

         arrnew.push(myrec);
        }
      }
    }

    dashboard.arrposizioni = [...dashboard.arrposizioni, ...arrnew];

    // console.table(dashboard.arrnavi);

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

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

  // Check if somewhere there is my username
  return User.find({ idapp, aportador_solidario_ind_order }, async (err, arrrec) => {
    if (arrrec) {
      for (const myuser of arrrec) {
        if (!myuser.aportador_solidario || myuser.aportador_solidario === tools.APORTADOR_NONE) {
          myuser.aportador_solidario = username;
          await myuser.save()
        }
      }
    }
  });


};

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.count(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.visuUtentiNonInNavi = async function (idapp) {
  const User = this;

  const arrusers = await User.find({
    idapp,
    $and: User.getQueryQualified()
  }, {
    name: 1,
    surname: 1,
    username: 1,
    ind_order: 1,
    deleted: 1,
    sospeso: 1,
  });


  let num = 0;
  let innave = 0;
  let noninnave = 0;
  let mystr = 'visuUtentiNonInNavi: ' + tools.ACAPO;
  let reg = 0;
  let num0inv = 0;
  let num1inv = 0;
  let num2inv = 0;
  let numnoinlista = 0;
  let numeliminati = 0;
  let numsospesi = 0;
  let strnavidoppie = '';
  let esiste = false;

  for (const user of arrusers) {
    esiste = true;
    if (!!user.deleted) {
      if (user.deleted) {
        numeliminati++;
        esiste = false;
      }
    }
    if (esiste) {

      let visualizza = false;

      // Controlla se ho un doppione nelle Navi !
      let mienavi = await Nave.find({ idapp, ind_order: user.ind_order }, { num_tess: 1 });

      let strnavidoppie = [];
      if (!!mienavi) {
        strnavidoppie = mienavi.reduce((acc, currentValue, index, array) => {
          if (array.indexOf(currentValue.num_tess) > -1 && !acc.includes(currentValue.num_tess))
            acc.push(currentValue.num_tess);
          return acc;
        }, []);
      }

      if (strnavidoppie.length > 1) {
        visualizza = true;
      }

      user.numinvitati = await ListaIngresso.getnumInvitati(idapp, user.username);
      reg++;
      let mianave = await Nave.findOne({ idapp, ind_order: user.ind_order });
      let mialistaingresso = await ListaIngresso.findOne({ idapp, ind_order: user.ind_order });
      let trovato = false;

      if (!mianave)
        visualizza = true;

      if (visualizza) {
        mystr += user.username + ' ' + user.name + ' ' + user.surname + ' [' + user.ind_order + '] [inv=' + user.numinvitati + ']'
        trovato = true;
      }

      if (strnavidoppie.length > 1) {
        mystr += ' NAVI DUPLICATE! ' + strnavidoppie.join(',');
      }
      if (!mianave)
        noninnave++;
      else
        innave++;


      if (user.sospeso) {
        numsospesi++;
      }

      if (!mialistaingresso) {
        mystr += ' NO IN LISTA INGRESSO!';
        trovato = true;
        numnoinlista++;
      }

      if (trovato)
        mystr += tools.ACAPO;

      if (user.numinvitati === 0) {
        num0inv++;
      }
      if (user.numinvitati === 1) {
        num1inv++;
      }
      if (user.numinvitati >= 2) {
        num2inv++;
      }
    }
  }


  mystrstart = 'Registrati: ' + reg + tools.ACAPO;
  mystrstart += '0 Invitati: ' + num0inv + tools.ACAPO;
  mystrstart += '1 Invitato: ' + num1inv + tools.ACAPO;
  mystrstart += '2 o più Invitati: ' + num2inv + tools.ACAPO;
  mystrstart += 'Presente in Nave: ' + innave + tools.ACAPO;
  mystrstart += 'Non in Nave: ' + noninnave + tools.ACAPO;
  mystrstart += 'Non in Lista Imbarco: ' + numnoinlista + tools.ACAPO;
  mystrstart += 'Usciti (Nascosti): ' + numeliminati + tools.ACAPO;
  mystrstart += 'Sospesi: ' + numsospesi + tools.ACAPO;

  mystrstart += tools.ACAPO;

  mystr = mystrstart + mystr;

  return { num, mystr };
};

// 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.count(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.count(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.count(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.count(myfind);
};

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.count(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.count(myfind);
};

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

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

  return await User.find(
    {
      idapp,
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }]
    },
    {
      username: 1,
      name: 1,
      surname: 1,
      date_reg: 1,
      ind_order: 1,
      'profile.nationality': 1,
    }).sort({ date_reg: -1 }).limit(lastn).then((arr) => {
    //return JSON.stringify(arr)
    return arr
  });

};

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

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

};

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

  return calcstat = {
    numinvitati: await ListaIngresso.getnumInvitati(idapp, username),
    numinvitati_attivi: await ListaIngresso.getnumInvitatiAttivi(idapp, username),
  };

};

UserSchema.statics.getDistinctNationalityQuery = function (idapp) {
  const query = [
    {
      $match: { idapp }
    },
    {
      $group: { _id: "$profile.nationality", count: { $sum: 1 } }
    },
    {
      $sort: { count: -1 }
    }
  ];
  return query
};


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

  return User.aggregate(User.getDistinctNationalityQuery(idapp))
    .then(ris => {
      // console.table(ris);
      return JSON.stringify(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" } }, count: { $sum: 1 } }
    },
    {
      $sort: { _id: 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" } }, count: { $sum: 1 } }
    },
    {
      $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" } }, count: { $sum: 1 } }
    },
    {
      $sort: { _id: 1 }
    }
  ];
  return query
};

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

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

};

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

  return User.aggregate(User.getUsersRegDaily(idapp, 30))
    .then(ris => {
      // console.table(ris);
      return JSON.stringify(ris);
    });
};

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

  return User.aggregate(User.getUsersRegWeekly(idapp, 20 * 7))
    .then(ris => {
      // console.table(ris);
      return JSON.stringify(ris.slice(0, -1));
    });
};


if (tools.INITDB_FIRSTIME) {
  console.log('   createIndex User Index...');
  // UserSchema.index({ username: 'text', name: 'text', surname: 'text', email: 'text' });
  // UserSchema.index({ name: 'name' });
  // UserSchema.index({ name: 1 });
  // UserSchema.index({ surname: 1 });
}

async function addUtentiInLista(idapp, mode, arrusers) {

  let num = 0;
  for (const rec of arrusers) {
    let ok = false;
    let qualified = await User.isUserQualified7(idapp, rec.username);
    let numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, rec.username);
    let numinvitati = await ListaIngresso.getnumInvitati(idapp, rec.username);

    if (rec.profile.special_req) {
      numinvitatiattivi = 2;
    }

    if (mode === 1) {
      // 9 punti qualificati
      ok = qualified && (numinvitatiattivi >= 2);
    } else if (mode === 2) {
      // 8 punti qualificati ( 1 Invitato)
      ok = qualified && (numinvitati === 2);
    } else if (mode === 3) {
      ok = qualified && (numinvitatiattivi === 1);
    } else if (mode === 4) {
      ok = qualified && (numinvitati === 1);
    } else if (mode === 5) {
      ok = qualified;
    }

    if (ok) {
      ris = await ListaIngresso.addUserInListaIngresso(idapp, rec.username, rec.aportador_solidario, rec.lang, false, true);
      if (!!ris)
        num++;
    }
  }
  return num;

}

UserSchema.statics.deveRitessersi = async function (idapp, ind_order) {

  const myrec = await User.findOne({
    'idapp': idapp,
    'ind_order': ind_order,
    "profile.vuole_ritessersi": true
  });

  return (!!myrec)

};

UserSchema.statics.getUsernameByIndOrder = async function (idapp, ind_order) {

  const myrec = await User.findOne({
    'idapp': idapp,
    'ind_order': ind_order,
  }, { username: 1 });

  return (!!myrec) ? myrec.username : ''

};

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 }).sort({ ind_order: 1 });
  let index = 1;
  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.changeInvitante = async function (idapp, username, invitante_username, ind_order_ingr) {
  const User = this;

  const rec_ind_order_prima = await ListaIngresso.findOne({ idapp, username });

  let modif_aportador = false;
  // cambia aportador_solidario solo se è la prima nave!

  // Oppure se ancora non sono in Lista!
  if (!rec_ind_order_prima) {
    modif_aportador = true;
  } else {
    if (rec_ind_order_prima.ind_order === ind_order_ingr) {
      modif_aportador = true;
    }
  }

  if (modif_aportador) {
    await User.findOneAndUpdate({ idapp, username }, { $set: { aportador_solidario: invitante_username } });
  }

  // **
  // ** Cambia invitante_username e ind_order di LISTAINGRESSO
  // **
  const rec_ingr = await ListaIngresso.findOne({ idapp, username: username });
  if (!!rec_ingr) {
    await ListaIngresso.findByIdAndUpdate(rec_ingr._id, { $set: { invitante_username, ind_order: ind_order_ingr } });
  }


};


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 await 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);

  }

};


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


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

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


module.exports = { User, Hero };


