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

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

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

const { Nave } = require('./nave');

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 ListaIngressoSchema = new mongoose.Schema({
  idapp: {
    type: String,
    required: true,
  },
  username: {
    type: String,
  },
  ind_order: {
    type: Number,
  },
  index: {
    type: Number,
  },
  invitante_username: {
    type: String,
  },
  date_added: {
    type: Date,
  },
  added: {          // Added into Programmation (Nave)
    type: Boolean,
    default: false,
  },
  num_tess: {
    type: Number,
  },
  note: {
    type: String,
  },
  deleted: {
    type: Boolean,
    default: false,
  }

});

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

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

  next();
});


// ListaIngressoSchema.methods.toJSON = function () {
//   const ListaIngresso = this;
//   const userObject = ListaIngresso.toObject();
//
//   return _.pick(userObject, ['_id', ...shared_consts.fieldsUserToChange()]);
// };
//
ListaIngressoSchema.statics.findByUsername = function (idapp, username) {
  const ListaIngresso = this;

  return ListaIngresso.findOne({
    'idapp': idapp,
    'username': username,
  });
};

ListaIngressoSchema.statics.getTotInLista = async function (idapp) {
  const ListaIngresso = this;

  const myfind = { idapp, added: false, deleted: false };

  return await ListaIngresso.count(myfind);
};

ListaIngressoSchema.statics.findByIndOrder = function (idapp, ind_order) {
  const ListaIngresso = this;

  try {
    return ListaIngresso.findOne({
      'idapp': idapp,
      'ind_order': ind_order,
    });
  } catch (e) {

  }
};

ListaIngressoSchema.statics.findByAllRecByUsername = function (idapp, username) {
  const ListaIngresso = this;

  try {
    return ListaIngresso.find({
      idapp,
      username,
      deleted: false
    });
  } catch (e) {

  }
};

ListaIngressoSchema.statics.findByUsernameETess = async function (idapp, username, num_tess) {
  const ListaIngresso = this;

  try {
    return await ListaIngresso.findOne({
      idapp,
      username,
      num_tess,
    });
  } catch (e) {

  }
};

ListaIngressoSchema.statics.findAllByUsername = function (idapp, username) {
  const ListaIngresso = this;

  try {
    return ListaIngresso.find({
      'idapp': idapp,
      username,
      added: false,  // NON QUELLI GIA INSERITI !
      $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
    });
  } catch (e) {
    console.error(e.message);
  }
};

ListaIngressoSchema.statics.deleteUserInListaIngresso = async function (idapp, username) {
  const ListaIngresso = this;

  return ListaIngresso.updateMany({
    idapp,
    username,
  }, { $set: { deleted: true } }, { new: false })

};

ListaIngressoSchema.statics.addUserInListaIngresso = async function (idapp, username, invitante_username, lang, addednowreal, nuovo, dateins, note, added) {
  const ListaIngresso = this;

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

  try {
    let listaingresso = null;
    if (!nuovo) {
      listaingresso = await ListaIngresso.findByUsernameETess(idapp, username, 1);
    }
    if (!listaingresso) {
      listaingresso = new ListaIngresso({
        _id: new ObjectID(),
        idapp,
        username,
        num_tess: 1,
        date_added: new Date(),
        invitante_username,
      });

      if (!!note) {
        listaingresso.note = note;
      }
      if (!!added) {
        listaingresso.added = added;
      }

      if (!!dateins) {
        try {
          listaingresso.date_added = dateins;
        } catch (e) {
          listaingresso.date_added = new Date();
        }
      }

      if (listaingresso) {
        const ris = await listaingresso.save();
        if (addednowreal) {
          if (!!ris) {
            const nome = await User.getNameSurnameByUsername(idapp, username);
            msgtext = '🔵 ' + nome + ' (' + username + ') ' + tools.gettranslate('ADDED_TOLISTAINGRESSO', lang);
            await telegrambot.sendMsgTelegram(idapp, username, msgtext, true);  // Anche a STAFF
          }
        }
        return ris;
      }
    }

    const giapresente = await
      Nave.findOne({ idapp, ind_order, num_tess: 1 });
    if (!!giapresente) {
      // Era stato già aggiunto!
      let fields_to_update = { added: true };
      await
        ListaIngresso.findOneAndUpdate({ _id: listaingresso._id }, { $set: fields_to_update }, { new: false });

      return false;
    }

    return null;

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


function getQueryProj(myfilter, myobjField, myfilter2, mygroup) {
  const query = [
    { $match: myfilter },
    {
      $lookup: {
        from: "users",
        as: "user",
        let: { username: '$username' },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  { $eq: ['$username', '$$username'] },
                  { $eq: ['$idapp', myfilter.idapp] },
                ]
              }
            }
          }
        ]
      }
    },
    {
      $replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ["$user", 0] }, "$$ROOT"] } }
    },
    { $match: myfilter2 },
  ];

  if (!!mygroup) {
    query.push(
      { $group: mygroup }
    );
  }

  if (!!myobjField) {
    query.push(
      { $project: myobjField }
    );
  }

  return query;
}

ListaIngressoSchema.statics.getPosizioneInLista = async function (idapp, arrrec, ind_order, num_tess) {


  let posiz = 0;
  let totposiz = arrrec.length;
  let numNaviEntrato = 0;

  for (let ind = 0; ind < arrrec.length; ind++) {
    if (arrrec[ind].ind_order === ind_order && arrrec[ind].num_tess === num_tess) {
      posiz = ind;
      numNaviEntrato = arrrec[ind].numNaviEntrato;
      break;
    }
  }

  return { posiz: posiz + 1, totposiz, num_tess, numNaviEntrato }

};

ListaIngressoSchema.statics.showListaOrd = async function (idapp, solonuovi) {
  const ListaIngresso = this;

  arrrec = await ListaIngresso.getProssimiInLista(idapp, solonuovi);

  let mystr = '';
  let conta = 1;
  for (const rec of arrrec) {
    mystr += conta + ' - ' + ' [' + rec.numinvitatiattivi + '] - [' + rec.numinvitati + '] (' + rec.ind_order + ') ' + rec.username + ' ' + rec.name + ' ' + rec.surname;
    mystr += ' inv = ' + rec.invitante_username;
    mystr += ' num_tess = ' + rec.num_tess;
    if (rec.added)
      mystr += ' (ADDED ++)';

    mystr += '\n';
    conta++;
  }

  return mystr;
};


ListaIngressoSchema.statics.getFieldsForSearch = function () {
  return [
    { field: 'ind_order', type: tools.FieldType.number },
    { field: 'username', type: tools.FieldType.string },
    { field: 'invitante_username', type: tools.FieldType.string },
  ]
};

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

ListaIngressoSchema.statics.findAllIdApp = function (idapp) {
  const ListaIngresso = this;

  const myfind = { idapp };

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

function sortRec(rec) {
  return rec.sort(function (reca, recb) {
    return Math.round((recb.numinvitatiattivi + recb.numinvitati) / recb.num_tess) - Math.round((reca.numinvitatiattivi + reca.numinvitati) / reca.num_tess);
  });
}

ListaIngressoSchema.statics.getProssimiInLista = async function (idapp, solonuovi) {
  const ListaIngresso = this;

  let myquery = '';

  let myobjField = {
    idapp: 1,
    ind_order: 1,
    name: 1,
    surname: 1,
    username: 1,
    invitante_username: 1,
    email: 1,
    lang: 1,
    num_tess: 1,
    added: 1,
    deleted: 1,
    sospeso: 1,
  };

  let myfilter2 = {
    surname: { $exists: true },
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  if (solonuovi) {
    myquery = getQueryProj({
      'idapp': idapp,
      ind_order: { $gte: 0 },
      added: false,
      deleted: false,
    }, myobjField, myfilter2);
  } else {
    myquery = getQueryProj({
      'idapp': idapp,
      ind_order: { $gte: 0 },
      deleted: false,
    }, myobjField, myfilter2);
  }

  // await ListaIngresso.aggiornaIndex();

  arrrec = await ListaIngresso.aggregate(myquery).sort({ date_added: 1 })
    .then(async (arrlista) => {

      const { User } = require('../models/user');

      const arrindex = {};

      for (const rec of arrlista) {

        // ++Todo: TOGLIERE!
        if (false) {
          rec.numNaviEntrato = await Nave.getnumNaviByUsername(idapp, rec.username);

          rec.numinvitati = await ListaIngresso.getnumInvitati(idapp, rec.username);
          rec.numinvitatiattivi = await ListaIngresso.getnumInvitatiAttivi(idapp, rec.username);
        }

        const arrListaIngrUser = arrindex[rec.username];
        if (!arrListaIngrUser) {
          arrindex[rec.username] = 1 + rec.numNaviEntrato;
        } else {
          arrindex[rec.username] += 1; // incrementa
        }

        // if (rec.num_tess > 2) {
        rec.numinvitati = rec.numinvitati - (arrindex[rec.username] - 1) * 2;
        rec.numinvitatiattivi = rec.numinvitatiattivi - (arrindex[rec.username] - 1) * 2;
        // }
        if (rec.numinvitati < 0) {
          rec.numinvitati = 0;
        }
        if (rec.numinvitatiattivi < 0) {
          rec.numinvitatiattivi = 0;
        }

        if (rec.numinvitatiattivi > 2)
          rec.numinvitatiattivi = 2;
      }

      if (arrlista) {
        arrlista = sortRec(arrlista);
        // arrlista.reverse();
      }

      return arrlista;
    }).catch((e) => {
      console.error(e.message);
      return null
    });

  return arrrec;
};

ListaIngressoSchema.statics.creaTessituraeConv = async function (idapp) {
  const ListaIngresso = this;

  const { User } = require('./user');

  try {
    if (true) {
      // Elimina TUTTI i Cancellati (ma non Sospesi)
      await User.remove({
        idapp,
        $and: [
          { deleted: { $exists: true, $eq: true } },  // cancellati
          { $or: [{ sospeso: { $exists: false } }, { sospeso: { $exists: true, $eq: false } }] },  // ma non sospeso
        ]
      });


      // Comprende anche i sospesi
      const arrusers = await User.find({
        idapp,
      });

      // Ricalcola il Campo Index
      await User.ricalcolaIndex(idapp);

      // Ricrea i record ListaIngresso (num_tess = 1), per ogni User
      for (const user of arrusers) {
        if (user.ind_order >= 0) {
          let ingr = new ListaIngresso();
          ingr._id = new ObjectID();
          ingr.idapp = idapp;
          ingr.ind_order = user.ind_order;
          ingr.username = user.username;
          ingr.invitante_username = user.aportador_solidario;
          ingr.num_tess = 1;
          // ingr.date_added = user.date_reg;
          ingr.added = false;
          const listaingr = await ListaIngresso.find({ idapp, ind_order: ingr.ind_order, num_tess: 1 });

          const trovatanave = await Nave.findOne({ idapp, ind_order: ingr.ind_order, num_tess: 1 })
            .then((mynave) => {
              if (!!mynave) {
                ingr.date_added = mynave.created;
                ingr.added = true;
              }
              return !!mynave;
            });

          if (listaingr.length === 0) {
            if (trovatanave)
              await ingr.save();
            else {
              // non ha ancora i 7 requisiti
            }

          } else {

            const listaingr2 = await ListaIngresso.find({ idapp, ind_order: ingr.ind_order });

            for (const ingr2 of listaingr2) {

              const ris = await ListaIngresso.findByIdAndUpdate(ingr2._id, {
                $set: {
                  username: ingr.username,
                  invitante_username: ingr.invitante_username
                }
              }, { new: false });
            }

          }

          // cancella in user i campi che non uso più
          await User.findByIdAndUpdate(user.id, {
            $set: {
              old_order: user.ind_order,
              ind_order: -1,
              // aportador_solidario: ''
            }
          })
        }
      }
    }

    // Comprende anche i sospesi
    const arrIdTelegramUsers = await User.find({ idapp, 'profile.teleg_id': { $gt: 1 } },
      { _id: 1, username: 1, aportador_solidario: 1, old_order: 1, lang: 1, date_reg: 1, 'profile.teleg_id': 1 })
      .sort({
        'profile.teleg_id': 1,
        old_order: 1
      });


    // Crea altri ListaIngresso per gli utenti con i sottoaccount:
    /*
        const arrIdTelegramUsers = [];
        const arrusersAlreadyExist = [];
        for (const user of arrusers) {
          if (!!user.profile.teleg_id) {
            const trovati = arrusersAlreadyExist.find((rec) => rec.teleg_id === user.profile.teleg_id);
            if (trovati) {
              // era già presente, quindi lo aggiungo !
              const trovatoidteleg = arrIdTelegramUsers.find((rec) => rec.teleg_id === user.profile.teleg_id);
              if (!trovatoidteleg)
                arrIdTelegramUsers.push({ teleg_id: user.profile.teleg_id, username: user.username });
            } else {
              arrusersAlreadyExist.push({ teleg_id: user.profile.teleg_id, username: user.username });
            }
          }
        }
    */

    let index = 0;

    let teleg_id = 0;
    let username = '';
    let invitante_username = '';
    let invitante_degli_invitati = '';

    // Questi sono Utenti che hanno associato Telegram a più Account !
    for (const myuser of arrIdTelegramUsers) {

      // Crea 1 record listaingresso per ogni record aggiuntivo ! escludendo il primo!
      if (teleg_id !== myuser.profile.teleg_id) {
        index = 0;
        teleg_id = myuser.profile.teleg_id;
        username = myuser.username;
        invitante_username = myuser.aportador_solidario;
        invitante_degli_invitati = myuser.username;
      }

      if (index > 0) {
        // Ind_order to find !
        const ind_order = myuser.old_order;
        //const findnave = await Nave.find({ idapp, ind_order: myuser.old_order, num_tess: 1 });

        if (ind_order >= 0) {
          const findlistaingresso = await ListaIngresso.findOne({ idapp, ind_order, num_tess: 1 });

          if (!!findlistaingresso) {
            await ListaIngresso.findByIdAndUpdate(findlistaingresso._id, {
              $set: {
                username,
                invitante_username,
                note: findlistaingresso.username,
              }
            }, { new: false });

            await Nave.updateMany({idapp, ind_order}, {
              $set: {
                note: findlistaingresso.username,
              }
            }, { new: false });


            // Crea Record ListaIngresso
            // const newrecingr = await ListaIngresso.addUserInListaIngresso(idapp, myuser.username, invitante_username, myuser.lang, false, true, myuser.date_reg, note, added);

            // Assegna l'Invitante agli Invitati !  trova tutti gli invitati che hanno questo username
            const arrinvitati = await User.find({ idapp, aportador_solidario: myuser.username }, { username: 1 });
            for (const invitato of arrinvitati) {
              await User.changeInvitante(idapp, invitato.username, invitante_degli_invitati, ind_order)
            }

            // poi imposta come cancellato questo record
            await User.findByIdAndUpdate(myuser._id, { $set: { deleted: true } });
          }
        }

      }
      index++;
    }

    return index;

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

};

ListaIngressoSchema.statics.getListaTessByUsername = function (idapp, username) {
  const ListaIngresso = this;

  return ListaIngresso.find({
    idapp,
    username,
    // added: true,
    deleted: false,
  });


};

ListaIngressoSchema.statics.getarray = async function (idapp, filtri, myobjField) {
  const ListaIngresso = this;

  let myfilter2 = {
    surname: { $exists: true },
    $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
  };

  let myquery = getQueryProj(
    filtri,
    myobjField,
    myfilter2
  );

  return await ListaIngresso.aggregate(myquery);

};


ListaIngressoSchema.statics.getInvitati = async function (idapp, username, myobjField) {
  const ListaIngresso = this;

  return ListaIngresso.getarray(idapp,
    {
      idapp,
      deleted: false,
      invitante_username: username,
      username: { $ne: username },
    },
    myobjField);

};

/*
ListaIngressoSchema.statics.getUserByInvitante_Username = async function (idapp, invitante_username) {
  const ListaIngresso = this;

  const arr = ListaIngresso.getarray(idapp,
    {
      idapp,
      deleted: false,
      invitante_username: invitante_username,
      username: { $ne: {invitante_username} },
    },
    null);

  if (!!arr)
    return arr[0];
  else
    return null;
};

*/

ListaIngressoSchema.statics.getnumInvitati = async function (idapp, username) {
  const ListaIngresso = this;

  const arrlistainv = await ListaIngresso.find({
    idapp,
    invitante_username: username,
    deleted: false,
    username: { $ne: username },
  }, { username: 1 });

  const { User } = require('./user');

  // 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 (!arrlistainv.find((rec) => rec.username === inv.username))
      arrlistainv.push(inv);
  }

  if (!!arrlistainv)
    return arrlistainv.length;
  else
    return 0;

};

ListaIngressoSchema.statics.getnumInvitatiAttivi = async function (idapp, username) {
  const ListaIngresso = this;

  const { User } = require('./user');

  let myfilter2 = {
    surname: { $exists: true },
    $and: User.getQueryQualified(),
  };

  let myquery = getQueryProj({
      idapp,
      invitante_username: username,
      username: { $ne: username },
      deleted: false,
    },
    { username: 1, ind_order: 1 },
    myfilter2,
  );

  // { _id: null, count: { $sum: 1 }

  arrlistainv = await ListaIngresso.aggregate(myquery);

  // 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 } }],
    $and: User.getQueryQualified()
  });

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

  if (!!arrlistainv)
    return arrlistainv.length;
  else
    return 0;
};


ListaIngressoSchema.statics.aggiornaIndex = function (idapp) {
  const ListaIngresso = this;

  const arrlista = ListaIngresso.find({ idapp }).sort({ created: 1 });


};


const ListaIngresso = mongoose.model('ListaIngresso', ListaIngressoSchema);


module.exports = { ListaIngresso };


