var mongoose = require('mongoose').set('debug', false)

const _ = require('lodash');

const { ObjectId } = require('mongodb');
const tools = require('../tools/general');

var { Project } = require('./project');
const Hours = require('./hours');

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

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

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

mongoose.set('debug', false);

var TodoSchema = new mongoose.Schema({
  userId: {
    type: String,
  },
  pos: {
    type: Number,
  },
  category: mongoose.Schema.Types.ObjectId,
  descr: {
    type: String,
  },
  note: {
    type: String,
  },
  priority: {
    type: Number,
  },
  statustodo: {
    type: Number,
    default: 0
  },
  created_at: {
    type: Date
  },
  modify_at: {
    type: Date
  },
  start_date: {
    type: Date,
  },
  completed_at: {
    type: Date
  },
  expiring_at: {
    type: Date,
  },
  enableExpiring: {
    type: Boolean,
    default: false
  },
  progress: {
    type: Number,
  },
  phase: {
    type: Number,
    default: 1
  },
  assignedToUsers: [
    { type: String }
  ],
  assigned_to_userId: {
    type: String,
  },
  hoursplanned: {
    type: Number,
  },
  hoursworked: {
    type: Number,
  },
  themecolor: {
    type: String,
  },
  themebgcolor: {
    type: String,
    default: '',
  },
  modified: {
    type: Boolean,
  },
  deleted: {
    type: Boolean,
    default: false,
  },
});

TodoSchema.methods.toJSON = function () {
  var todo = this;
  var todoObject = todo.toObject();

  // console.log(todoObject);

  return _.pick(todoObject, tools.allfieldTodoWithId());
};

TodoSchema.statics.findByUserIdAndIdParent = function (userId, category, phase = '') {
  var Todo = this;

  let tofind = {
    category: new ObjectId(category),
    $or:
      [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }]

  };

  if (userId !== '') {
    tofind['userId'] = userId;
  }

  if (!!phase) {
    tofind['phase'] = phase;
  }

  return Todo.find(tofind);

};

function getQueryFilterTodo(userId) {
  let myobj = [
      // { userId: userId },
      {
        $or:
          [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }]
      }
    ]
  ;
  return myobj;
}

/*
function getQueryFilterTodo(userId) {
  myobj = [{ privacyread: server_constants.Privacy.all }, { userId: userId }];
  return myobj;
}
*/

function getQueryTodo(filterMatchBefore = {}, userId) {

  let filterMatchAfter = {
    $or: getQueryFilterTodo(userId)
  };

  let myobjField = getobjFieldTodo(true);

  const query = [
    { $match: filterMatchBefore },
    {
      $lookup: {
        from: "projects",
        localField: "category",    // field in the Todo collection
        foreignField: "_id",  // field in the projects collection
        as: "fromItems"
      }
    },
    {
      $replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ["$fromItems", 0] }, "$$ROOT"] } }
    },
    { $match: filterMatchAfter },
    { $project: myobjField }];

  return query;
}

TodoSchema.statics.findAllByUserIdAndCat = function (userId) {
  var Todo = this;

  const query = getQueryTodo({
    $or: [{ deleted: { $exists: false } }, {
      deleted: {
        $exists: true,
        $eq: false
      }
    }]
  }, userId);

  return Todo.aggregate(query)
    .then(ris => {
      // console.log('ris TODO:', ris);
      //return tools.mapSort(ris)
      return ris
    });
};

TodoSchema.statics.getArrIdParentInTable = function (userId) {
  var Todo = this;

  return Todo.find(getQueryFilterTodo(userId)[0]).distinct("category")
    .then(arrcategory => {
      return arrcategory
    })

};

function getobjFieldTodo(withprojectfield) {
  let objfield = {};
  for (const field of tools.allfieldTodo()) {
    objfield[field] = 1;
  }

  if (withprojectfield) {
    objfield["privacyread"] = 1;
    objfield["privacywrite"] = 1;
  }

  // console.log("getobjFieldTodo", objfield);

  return objfield
}

TodoSchema.statics.enabletoModify = async function (userId, idTodo) {
  var Todo = this;

  let params = {};
  params['_id'] = new ObjectId(idTodo);
  const query = getQueryTodo(params, userId);

  return Todo.aggregate(query)
    .then(ris => {
      return (!!ris);
    })
    .catch(err => {
      console.log("ERR: ".err);
      return false;
    });
};


TodoSchema.statics.getAllTodo = async function (userId) {
  var Todo = this;

  const arralltodo = await Todo.findAllByUserIdAndCat(userId);
  // console.log('arralltodo', arralltodo);

  let obj = [];

  obj.arrcategories = await Todo.getArrIdParentInTable(userId);

  // console.log('obj.arrcategories', obj.arrcategories);

  let arrtodos = [];
  if (obj.arrcategories.length > 0) {
    for (const mycat of obj.arrcategories) {
      if (!!arralltodo) {
        const arrfiltrato = arralltodo.filter(item => item.category.toString() === mycat.toString());
        if (arrfiltrato.length > 0) {
          // const arrmap = tools.mapSort(arrfiltrato);
          arrtodos.push(arrfiltrato);
          // console.log('AGGIUNGI RECORDS TODO! cat: ', mycat, 'da aggiungere:', arrfiltrato.length, 'attuali', arrtodos.length);
          // console.log(arrtodos)
        } else {
          arrtodos.push([]);
        }
      }
    }
  }

  obj.arrtodos = arrtodos;

  // console.log('obj.arrtodos:');
  // logtodo(obj.arrtodos);

  return obj;

};

function getelem(item) {
  return item.descr + ' ' + item.category;
}

function logtodo(myarr) {
  console.log(getarrlog(myarr));
}

function getarrlog(myarr) {
  let mystr = "";
  for (const indcat in myarr) {
    mystr += indcat + ': \n';
    for (const indelem in myarr[indcat]) {
      mystr += '  [' + indelem + '] ' + getelem(myarr[indcat][indelem]) + '\n';
    }
  }

  return mystr
}

class CalcTodo {
  constructor(phase) {

    this.mydata = {
      _id: '',
      phase: phase,
      numitem: 0
    };
    this.clean()
  }

  clean() {
    this.mydata.hoursleft = 0;
    this.mydata.hoursplanned = 0;
    this.mydata.hoursworked = 0;
    this.mydata.progressCalc = 0;
  }

  async addDataProj(datain) {
    if (!!datain) {
      if (datain.actualphase === this.mydata.phase) {
        if (datain._id)
          datain.hoursworked = await Hours.calculateHoursTodo(datain._id.toString());
        CalcTodo.addFields(this.mydata, datain, true);
      }
    }
  }

  async addDataTodo(datain) {
    if (!!datain) {
      if (datain.phase === this.mydata.phase) {
        if (datain._id)
          datain.hoursworked = await Hours.calculateHoursTodo(datain._id.toString());
        CalcTodo.addFields(this.mydata, datain, false);
      }
    }
  }

  static addFields(recout, recin, isproj) {
    // console.log('addFields', recin);

    recout.hoursworked += recin.hoursworked;
    recout.hoursplanned += recin.hoursplanned;
    let hoursleft = (recin.hoursplanned - recin.hoursworked);
    if (hoursleft < 0)
      hoursleft = 0;
    recout.hoursleft += hoursleft;
    if (recin.progress === undefined) {
      recout.progressCalc += recin.progressCalc;
    } else {
      recout.progressCalc += recin.progress;
    }

    recout.numitem++;
  }

  static copyFields(recout, recin) {
    recout.hoursworked = recin.hoursworked;
    recout.hoursplanned = recin.hoursplanned;
    recout.hoursleft = recin.hoursleft;
    if (recin.progress === undefined)
      recout.progressCalc = recin.progressCalc;
    else {
      if (!!recin.progressCalc)
        recout.progressCalc = recin.progressCalc;
      else
        recout.progressCalc = 0;
    }
  }

  setValuesToRecord(objout) {
    CalcTodo.copyFields(objout, this.mydata)
  }

  endDataCalc() {
    if (this.mydata.numitem > 0) {
      this.mydata.progressCalc = Math.round(this.mydata.progressCalc / this.mydata.numitem);
    }
    // console.log('this.mydata.progressCalc', this.mydata.progressCalc)
  }

  getData() {
    // return tools.jsonCopy(this.mydata);
    return { ...this.mydata }
  }
}

TodoSchema.statics.calculateTreeTodo = async function (actualphase, userId, idproj, calcalsoUpper, masterproj, nocalcDown) {
  // console.log('calculateTreeTodo', 'actualphase', actualphase, idproj);

  const myrecproj = await Project.findProjectByUserId(userId, idproj);
  // const id_parent = await Project.getIdParentByIdProj(idproj);

  let objdata = new CalcTodo(actualphase);

  let promiseChain = Promise.resolve();

  return await Project.findByUserIdAndIdParent(userId, idproj)
    .then(arrsubproj => {
      // console.log('   ', arrsubproj.length, 'SubProjects trovati');
      // console.log('arrsubproj', 'userId', userId, 'idproj', idproj, arrsubproj.length);

      if (!nocalcDown) {
        // 1) Calculate the SubProjects of this project Main
        for (const subproj of arrsubproj) {
          if (!calcalsoUpper) {  // not include the first Project because it was already calculated before
            promiseChain = promiseChain.then(() => {
              return Todo.calculateTreeTodo(actualphase, userId, subproj._id, calcalsoUpper, masterproj, true)
                .then((subobjdata) => {
                  return objdata.addDataProj(subobjdata);
                });
            });
          } else {
            promiseChain = promiseChain.then(() => {
              return objdata.addDataProj(subproj);
            });
          }
        }
      }

      return promiseChain;
    })
    .then(() => {
      // console.log('objdata', objdata);
      // 2) Calculate the Todos of this project
      if (!!myrecproj)
        return Todo.calculateTodoHoursAndProgress(userId, idproj, myrecproj.actualphase);
      else
        return null
    })
    .then((objdatatodos) => {

      if (!!objdatatodos) {
        if (!!myrecproj) {
          if (myrecproj.actualphase === actualphase) {
            // console.log('objdatatodos', objdatatodos);
            return objdata.addDataTodo(objdatatodos)
              .then(() => {
                // End Calculate
                objdata.endDataCalc();

                // Update into the DB:
                return Project.updateCalc(userId, idproj, objdata, null)
                  .then((ris) => (ris) ? objdata.getData() : null);
              });

          } else {
            return Project.updateCalc(userId, idproj, objdata, null)
              .then((ris) => (ris) ? objdata.getData() : null);
          }
        }
      }
      return null;
    })
    .then((ris) => {
      if (calcalsoUpper) {
        if (!!myrecproj) {
          if (!!myrecproj.id_parent) {
            // Calculate also the upper Projects !
            return new Promise((resolve, reject) => {
              Todo.calculateTreeTodo(actualphase, userId, myrecproj.id_parent, true, masterproj, false);
              resolve(ris)
            });
          } else {
            return new Promise((resolve, reject) => {
              resolve(ris)
            });
          }
        }
      }

      return new Promise((resolve, reject) => {
        resolve(ris)
      });
    }).catch((e) => {
      console.log('error', e.message)
    })
};

TodoSchema.statics.calculateTodoHoursAndProgress = async function (userId, idproj, actualphase) {
  var Todo = this;

  let objdata = new CalcTodo(actualphase);

  return await Todo.findByUserIdAndIdParent(userId, idproj, actualphase)
    .then(arrtodo => {
      // console.log('   calculateTodo *', arrtodo.length, '* FOUND');

      let promiseChain = Promise.resolve();

      for (const itemtodo of arrtodo) {
        promiseChain = promiseChain.then(() => {
          return objdata.addDataTodo(itemtodo);
        })
      }
      return promiseChain;
    }).then(() => {

      objdata.endDataCalc();


      return objdata.getData();
    })
    .catch(e => {
      console.log('Error: ', e);
      return null;
    });

};


TodoSchema.pre('save', function (next) {
  // var todo = this;

  // console.log('todo.expiring_at', todo.expiring_at);

  next();
});


var Todo = mongoose.model('Todos', TodoSchema);

Todo.createIndexes()
  .then(() => { })
  .catch((err) => { throw err; });


module.exports = { Todo };

