/*
  Account is a User's single Circuit
 */

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

mongoose.Promise = global.Promise;
mongoose.level = 'F';

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

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


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

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

const AccountSchema = new Schema({
  _id: {
    type: String,
    default: function () {
      return new ObjectID().toString();
    },
  },
  idapp: {
    type: String,
  },
  username: {
    type: String,
  },
  groupname: {  // For the Groups
    type: String,
  },
  contocom: {  // For the Conto Comunitario dei Circuiti
    type: String,
  },
  circuitId: {  // ----- REF TO Circuit
    type: String,
  },
  name: {
    type: String,
  },
  deperibile: {
    type: Boolean,
  },
  fidoConcesso: {
    type: Number,
  },
  qta_maxConcessa: {
    type: Number,
  },
  importo_iniziale: {
    type: Number,
    default: 0,
  },
  saldo: {
    type: Number,
    default: 0,
  },
  totTransato: {
    type: Number,
    default: 0,
  },
  saldo_pend: {
    type: Number,
    default: 0,
  },
  totTransato_pend: {
    type: Number,
    default: 0,
  },
  regulation_ok: {
    type: Boolean,
  },
  deleted: {
    type: Boolean,
    default: false,
  },
  date_created: {
    type: Date,
    default: Date.now,
  },
  date_updated: {
    type: Date,
  },
});

AccountSchema.statics.findAllIdApp = async function (idapp) {
  const Account = this;

  const myfind = { idapp, deleted: false };

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

AccountSchema.pre('save', async function (next) {
  if (this.isNew) {
    this._id = new ObjectID().toString();
  }

  next();
});

AccountSchema.statics.getFieldsForSearch = function () {
  return [
    { field: 'name', type: tools.FieldType.string },
    { field: 'username', type: tools.FieldType.string },
    { field: 'groupname', type: tools.FieldType.string },
    { field: 'contocom', type: tools.FieldType.string },
  ];

};

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

AccountSchema.statics.getAccountsByUsername = async function (idapp, username) {
  const Account = this;

  if (username === undefined)
    return false;

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

  return await Account.find(myquery).lean();

};

AccountSchema.statics.calcTotCircolante = async function (idapp, circuitId) {
  const Account = this;

  try {
    let aggr1 = [
      {
        $match: { idapp, circuitId, saldo: { $gt: 0 } },
      },
      { $group: { _id: null, count: { $sum: '$saldo' } } }
    ];

    const ris = await Account.aggregate(aggr1);

    return ris ? ris[0].count : 0;

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

};
AccountSchema.methods.calcPending = async function (mittente) {
  try {
    const account = this;

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

    // *** Calc Pending Transactions ***

    const circuit = await Circuit.getCircuitById(account.circuitId);
    if (circuit) {
      let prec_saldo_pend = account.saldo_pend;
      let prec_totTransato_pend = account.totTransato_pend;
      let transatopending = 0;

      if (mittente) {
        let pendingtransactionsMittente = await SendNotif.getSumPendingTransactionsMittente(account.idapp, account.username, circuit.name, account.groupname);
        let saldopendingMitt = pendingtransactionsMittente.reduce((sum, rec) => sum + rec.extrarec.qty, 0);
        transatopending = pendingtransactionsMittente.reduce((sum, rec) => sum + Math.abs(rec.extrarec.qty), 0);

        account.saldo_pend = account.saldo - saldopendingMitt;
      } else {
        // let pendingtransactionsDest = await SendNotif.getSumPendingTransactionsMittente(account.idapp, account.username, circuit.name, account.groupname);

        // let saldopendingDest = pendingtransactionsDest.reduce((sum, rec) => sum + rec.extrarec.qty, 0);
        // transatopending = pendingtransactionsDest.reduce((sum, rec) => sum + Math.abs(rec.extrarec.qty), 0);

        // account.saldo_pend = account.saldo + saldopendingDest;
        account.saldo_pend = account.saldo;
      }
      account.totTransato_pend = account.totTransato + transatopending;

      if (prec_saldo_pend !== account.saldo_pend || prec_totTransato_pend !== account.totTransato_pend) {

        const myaccountupdate = {
          saldo_pend: account.saldo_pend,
          totTransato_pend: account.totTransato_pend,
        };

        // Update Record
        return await Account.findByIdAndUpdate(account.id,
          {
            $set: myaccountupdate
          }).then((ris) => {
            console.log('calcPending', ris);
          }).catch((err) => {
            console.error('calcPending', err);
          });


      }
    }


    // -----

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

}

AccountSchema.statics.addtoSaldo = async function (myaccount, amount, mitt) {
  const Account = this;

  try {
    let myaccountupdate = {};

    if (myaccount) {
      myaccount.saldo = myaccount.saldo + amount;
      if (!myaccount.totTransato) {
        myaccount.totTransato = 0;
      }
      myaccount.totTransato += Math.abs(amount);
      myaccount.date_updated = new Date();

      myaccountupdate.saldo = myaccount.saldo;
      myaccountupdate.totTransato = myaccount.totTransato;
      myaccountupdate.date_updated = myaccount.date_updated;

      const ris = await Account.updateOne({ _id: myaccount.id },
        {
          $set: myaccountupdate
        });


      await myaccount.calcPending(mitt);

      return ris;

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

  return null;
};

AccountSchema.pre('save', async function (next) {
  if (this.isNew) {
    if (!this.date_created)
      this.date_created = new Date();
  }

  next();
});

AccountSchema.statics.getAccountByUsernameAndCircuitId = async function (idapp, username, circuitId, createifnotexist, confido, groupname = '', contocom = "") {
  const Account = this;

  try {

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

    if (username === undefined)
      return false;

    let myquery = {
      idapp,
      circuitId,
    };

    if (groupname) {
      myquery.groupname = groupname;
    } else if (contocom) {
      myquery.contocom = contocom;
    } else {
      myquery.username = username;
    }

    let mycircuit = await Circuit.getCircuitById(circuitId);

    if (mycircuit) {
      let myaccount = await Account.findOne(myquery);

      if (!myaccount && createifnotexist) {
        myaccount = new Account({
          _id: new ObjectID().toString(),
          idapp,
          username: (!groupname && !contocom) ? username : '',
          groupname,
          contocom,
          circuitId: mycircuit._id,
          deperibile: false,
          importo_iniziale: 0,
          saldo: 0,
          saldo_pend: 0,
          fidoConcesso: 0,
          qta_maxConcessa: 0,
          totTransato: 0,
          totTransato_pend: 0,
        });

        if (contocom) {
          myaccount.fidoConcesso = shared_consts.CIRCUIT_PARAMS.SCOPERTO_MIN_CONTO_COMUNITARIO;
          myaccount.qta_maxConcessa = shared_consts.CIRCUIT_PARAMS.SCOPERTO_MAX_CONTO_COMUNITARIO;
        } else {
          if (!mycircuit.fido_scoperto_default_grp)
            mycircuit.fido_scoperto_default_grp = shared_consts.CIRCUIT_PARAMS.SCOPERTO_MIN_GRP;
          if (!mycircuit.qta_max_default_grp)
            mycircuit.qta_max_default_grp = shared_consts.CIRCUIT_PARAMS.SCOPERTO_MAX_GRP;

          if (groupname) {
            myaccount.fidoConcesso = mycircuit.fido_scoperto_default_grp;
            myaccount.qta_maxConcessa = mycircuit.qta_max_default_grp;
          } else {
            myaccount.fidoConcesso = mycircuit.fido_scoperto_default;
            myaccount.qta_maxConcessa = mycircuit.qta_max_default;
          }
        }

        if (!confido) {
          myaccount.fidoConcesso = 0;
        }

        return await myaccount.save();
      }

      return myaccount;
    }
    return null;

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

};

AccountSchema.statics.isExistAccountByUsernameAndCircuitId = async function (idapp, username, circuitId, groupname = '', contocom = "") {
  const Account = this;

  try {

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

    if (username === undefined)
      return false;

    let myquery = {
      idapp,
      circuitId,
    };

    if (groupname) {
      myquery.groupname = groupname;
    } else if (contocom) {
      myquery.contocom = contocom;
    } else {
      myquery.username = username;
    }

    let mycircuit = await Circuit.getCircuitById(circuitId);

    if (mycircuit) {
      let myaccount = await Account.findOne(myquery);
      return !!myaccount
    }

    return false;

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

};

AccountSchema.statics.createAccount = async function (idapp, username, circuitName, confido, groupname = '', contocom = '') {

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

  try {
    mycircuit = await Circuit.findOne({ name: circuitName }, { _id: 1 });
    if (mycircuit) {
      return await Account.getAccountByUsernameAndCircuitId(idapp, username, mycircuit._id, true, confido, groupname, contocom);
    } else {
      return null;
    }
  } catch (e) {
    console.error('error', e);
    return null;
  }

};

AccountSchema.statics.getUserAccounts = async function (idapp, username) {
  const { SendNotif } = require('../models/sendnotif');

  try {
    let aggr1 = [
      {
        $match: { idapp, username },
      },
      {
        $lookup: {
          from: 'circuits',
          localField: 'circuitId',
          foreignField: '_id',
          as: 'circuit',
        },
      },
      { $unwind: '$circuit' },
      {
        $lookup: {
          from: 'sendnotifs',
          as: 'notifspending',
          let: {
            circuitname: '$circuit.name',
            username: '$username',
            idapp: '$idapp',
            typedir: shared_consts.TypeNotifs.TYPEDIR_CIRCUITS,
            typeid: shared_consts.TypeNotifs.ID_CIRCUIT_SENDCOINSREQ,
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    { $eq: ['$idapp', '$$idapp'] },
                    { $eq: ['$typedir', '$$typedir'] },
                    { $eq: ['$typeid', '$$typeid'] },
                    { $eq: ['$status', 0] },
                    { $eq: ['$sender', '$$username'] },
                    { $eq: ['$extrarec.circuitname', '$$circuitname'] },
                  ],
                },
              },
            },
            {
              $group:
                { _id: "$extrarec.notifIdToUpdate", result: { $first: "$$ROOT" } }
            },

          ],
        },
      },
    ];

    const ris = await this.aggregate(aggr1);

    // console.log('1 - INIZIA getUserAccounts ')
    // console.log(aggr1);

    /* if (ris) {
      for (const account of ris) {
        try {
          //++OTTIMIZZARE QUESTA PARTE ! (CON UNA QUERY...)
          // console.log('  1B - PENDIND... ')
          
          let pendingtransactions = await SendNotif.getSumPendingTransactions(idapp, username, account.circuit.name);
          let saldopending = pendingtransactions.reduce((sum, rec) => sum + rec.extrarec.qty, 0);
          if (saldopending !== 0) {
            account.saldo -= saldopending;
            account.totTransato = account.totTransato || 0;
          }

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

      }
    } */

    return ris;
  } catch (e) {
    console.error('getUserAccounts', e);
  }

};

AccountSchema.statics.getGroupAccounts = async function (idapp, groupname) {

  if (!groupname) {
    return [];
  }
  try {
    let aggr1 = [
      {
        $match: { idapp, groupname },
      },
      {
        $lookup: {
          from: 'circuits',
          localField: 'circuitId',
          foreignField: '_id',
          as: 'circuit',
        },
      },
      { $unwind: '$circuit' },
      {
        $lookup: {
          from: 'sendnotifs',
          as: 'notifspending',
          let: {
            circuitname: '$circuit.name',
            groupname: '$groupname',
            idapp: '$idapp',
            typedir: shared_consts.TypeNotifs.TYPEDIR_CIRCUITS,
            typeid: shared_consts.TypeNotifs.ID_CIRCUIT_SENDCOINSREQ,
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    { $eq: ['$typedir', '$$typedir'] },
                    { $eq: ['$typeid', '$$typeid'] },
                    { $eq: ['$status', 0] },
                    { $eq: ['$sendergroup', '$$groupname'] },
                    { $eq: ['$idapp', '$$idapp'] },
                    { $eq: ['$extrarec.circuitname', '$$circuitname'] },
                  ],
                },
              },
            },
            {
              $group:
                { _id: "$extrarec.notifIdToUpdate", result: { $first: "$$ROOT" } }
            },
          ],
        },
      },
    ];

    const ris = await this.aggregate(aggr1);

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

};

// Imposta a tutti i Conti Collettivi, i seguenti minimi e massimi
AccountSchema.statics.SetMinMaxCollettivi = async function (idapp, valmin, valmax) {
  const Account = this;

  const ris = await Account.updateMany({ idapp, groupname: { "$nin": [null, ""] } },
    {
      $set:
      {
        fidoConcesso: valmin,
        qta_maxConcessa: valmax,
      }
    });

};

// Imposta a tutti i Conti Comunitari, i seguenti minimi e massimi
AccountSchema.statics.SetMinMaxComunitari = async function (idapp, valmin, valmax) {
  const Account = this;

  const ris = await Account.updateMany({ idapp, contocom: { "$nin": [null, ""] } },
    {
      $set:
      {
        fidoConcesso: valmin,
        qta_maxConcessa: valmax,
      }
    });

};

// Imposta a tutti i Conti Personali, i seguenti minimi e massimi
AccountSchema.statics.SetMinMaxPersonali = async function (idapp, fidoConcesso, qta_maxConcessa, circuitId) {
  const Account = this;

  if (circuitId) {
    const ris = await Account.updateMany({ idapp, circuitId, fidoConcesso: { $gt: 0 }, username: { "$nin": [null, ""] } },
      {
        $set:
        {
          fidoConcesso,
          qta_maxConcessa,
        }
      });

    // Se aggiorno questi dati, allora devo aggiornare anche gli account del RIS Nazionale
    await Account.updateAccountCircuitoNazionaleByLimiti(idapp, circuitId, fidoConcesso, qta_maxConcessa);

  } else {
    const ris = await Account.updateMany({ idapp, fidoConcesso: { $gt: 0 }, username: { "$nin": [null, ""] } },
      {
        $set:
        {
          fidoConcesso,
          qta_maxConcessa,
        }
      });
  }

};

AccountSchema.statics.updateFido = async function (idapp, username, groupname, circuitId, fido) {

  let paramstoupdate = {
    fidoConcesso: fido,
  };
  let risult = null;
  if (groupname)
    risult = await Account.updateOne({ idapp, circuitId, groupname }, { $set: paramstoupdate });
  else
    risult = await Account.updateOne({ idapp, username, circuitId }, { $set: paramstoupdate });

  return risult;
};

AccountSchema.statics.updateQtaMax = async function (idapp, username, groupname, circuitId, qtamax) {

  let paramstoupdate = {
    qta_maxConcessa: qtamax,
  };
  let risult = null;
  if (groupname)
    risult = await Account.updateOne({ idapp, circuitId, groupname }, { $set: paramstoupdate });
  else
    risult = await Account.updateOne({ idapp, username, circuitId }, { $set: paramstoupdate });

  return risult;
};

AccountSchema.statics.getAccountsCircuitiNazionali = async function (idapp) {
  const { Circuit } = require('../models/circuit');

  const circuit = await Circuit.find({ idapp, showAlways: true });

  return Account.find({ idapp, circuitId: circuit.id });
};

AccountSchema.statics.updateAccountCircuitoNazionaleByLimiti = async function (idapp, circuitId, fidoConcesso, qta_maxConcessa) {
  const { Circuit } = require('../models/circuit');

  try {
    const accounts = await this.getAccountsCircuitiNazionali(idapp);

    for (const account of accounts) {
      const circuitId = account.circuitId;
      const circuit = await Circuit.findOne({ _id: circuitId });
      if (circuit) {
        let fido = 0;
        let qtamax = 0;

        if (account.groupname) {
          fido = circuit.fido_scoperto_default_grp * shared_consts.CIRCUIT_CFG.MULT_FIDO_GROUP;
          qtamax = circuit.qta_max_default_grp * shared_consts.CIRCUIT_CFG.MULT_FIDO_GROUP;
        } else {
          fido = circuit.fido_scoperto_default * shared_consts.CIRCUIT_CFG.MULT_FIDO_USER;
          qtamax = circuit.qta_max_default * shared_consts.CIRCUIT_CFG.MULT_FIDO_USER;
        }

        let paramstoupdate = {
          fidoConcesso: fido,
          qta_maxConcessa: qtamax,
        };

        risult = await Account.updateOne({ _id: account.id }, { $set: paramstoupdate });
      }
    }
    return risult;
  } catch (e) {
    console.error('updateAccountCircuitoNazionaleByLimiti', e);
  }
  
  return false;
  
};

AccountSchema.statics.canEditAccountAdmins = async function (username, id) {
  const account = await Account.findOne({ _id: id }).lean();

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

  if (account) {
    const circuit = await Circuit.findOne({ _id: account.circuitId }).lean();
    if (circuit) {
      return circuit.admins.findIndex((admin) => admin.username === username) >= 0;
    }
  }
  return false;
};

AccountSchema.statics.addToPeopleOfMyAccount = async function (idapp, username, circuitId, person_username, perm) {

  return await Account.updateOne({ idapp, username, circuitId },
    {
      $push:
      {
        people: {
          username: person_username,
          perm,
          date: new Date(),
        },
      },
    });

};

// Rimuovi dagli Admin del Account
AccountSchema.statics.removeAdminOfAccount = async function (idapp, username, circuitId, person_username, perm) {
  const { Circuit } = require('../models/circuit');

  return await Circuit.updateOne({ idapp, username, circuitId },
    { $pull: { people: { username: { $in: [person_username] } } } });
};

// Rimuovi l'account
AccountSchema.statics.removeAccount = async function (accountId) {
  const { Circuit } = require('../models/circuit');

  return await Circuit.deleteOne({ _id: accountId});
};

AccountSchema.statics.updateSaldoAndTransato_AllAccounts = async function (idapp) {

  const recaccounts = await Account.find({ idapp });

  for (const account of recaccounts) {
    await account.calcPending();
  }

};

const Account = mongoose.model('Account', AccountSchema);

module.exports = { Account };
