const mongoose = require('mongoose');
const boom = require('@hapi/boom');
const options = {
    loggerLevel: 'error',
    useNewUrlParser: true,
    //reconnectInterval: 2000,
    //reconnectTries: 30, // Retry up to 30 times
    useCreateIndex: true,
    useUnifiedTopology: true
};

console.log("--- MongoDB connecting... ", process.env.MONGO_URI);

// Connect to DB
mongoose.connect(process.env.MONGO_URI, options);

// When successfully connected
mongoose.connection.on('connected', () => {
  console.log('--- MongoDB connected');
});

// When successfully reconnected
mongoose.connection.on('reconnected', () => {
  console.log('--- MongoDB dbevent: reconnected');
});

// If the connection throws an error
mongoose.connection.on('error', (err) => {
  console.log(`--- MongoDB dbevent: error: ${err}`);
  //console.log(`Retry mongo connect`);
  //mongoose.connect(process.env.MONGO_URI, options);
});
  

// Get Data Models
const Scenario = require('./models/Scenario');
const Schedule = require('./models/Schedule');
const Provision = require('./models/Provision');
const Destroy = require('./models/Destroy');
const User = require('./models/User');
const VmType = require('./models/VmType');
const ApiKey = require('./models/ApiKey');
const Notification = require('./models/Notification');
const Subscription = require('./models/Subscription');
const Event = require('./models/Event');


const getNewCountExtend = function(provision) {
  return provision.countExtend !== undefined? (provision.countExtend + 1) : 1;
};

const getNewTimeRunning = function (provision) {
  let timeRunning = provision.timeRunning !== undefined?  provision.timeRunning : 0; //minutes
  
  if ( provision.statusVms === 'Stopped' ) {
    return timeRunning;
  } else {
    let runningFrom = provision.runningFrom? new Date(provision.runningFrom).getTime() : new Date(provision.created).getTime();
    
    let diffMinutes = Math.abs(new Date().getTime() - runningFrom)/1000/60;
    let minutesFromLastRunning = Math.floor(diffMinutes);
    return Math.floor(minutesFromLastRunning + timeRunning);
  }

};


const getPage = async ( model, filter, page, populates, select ) => {
  var sort = {};
  var modelAttributes = Object.keys(model.schema.tree);
  if ( modelAttributes.indexOf("created") !== -1) {
    sort = {created: -1};
  }

  try {
    var exec = model.find(filter, select).sort(sort);
    var totalDocs = await model.countDocuments(filter);
    
    var isPage = false;
    if ( page && page.page > 0 && page.size !== undefined ) {
      var skip = 0;
      skip = (page.page - 1) * page.size;
      exec = exec.skip(skip);
      exec = exec.limit(page.size);
      isPage = true;
    }
    
    if ( populates ) {
      populates = JSON.parse(populates);
      populates.forEach(p=> {
        exec = exec.populate(p);
      });
    } else {
      if ( model === Provision ) {
        exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
      }
      
      if ( model === ApiKey ) {
        exec = exec.populate('user');
      }

      if ( model = Scenario ) {
        exec = exec.populate('subscription').populate('deployOpts');
      }
    }
    
    const entity = await exec;

    var out = {
      total: totalDocs, 
      count: entity.length,
      results: entity
    }
    if ( isPage && (page.page * page.size < totalDocs)) {
      out.next = {
        page: page.page + 1,
        size: page.size
      };
    }
    
    return out;

  } catch (err) {
    throw boom.boomify(err)
  }

};

const get = async (model, filter, select, skip, limit, populates, reply) => {
  var sort = {};
  var modelAttributes = Object.keys(model.schema.tree);

  if ( model === Scenario ) {
    if ( modelAttributes.indexOf("updated") !== -1) {
      sort.updated = -1;
    }
  }
  
  if ( modelAttributes.indexOf("created") !== -1) {
    sort.created = -1;
  }
  try {
    var exec = model.find(filter, select).sort(sort);
    var totalDocs = await model.countDocuments(filter);
    
    skip = skip? parseInt(skip) : 0;
    exec = exec.skip(skip);
    
    if ( limit ) {
      limit = parseInt(limit);
      exec = exec.limit(limit);
    }
    
    if ( populates ) {
      populates = JSON.parse(populates);
      populates.forEach(p=> {
        exec = exec.populate(p);
      });
    } else {
      if ( model === Provision ) {
        exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
      }
      
      if ( model === ApiKey ) {
        exec = exec.populate('user');
      }

      if ( model = Scenario ) {
        exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
      }
    }
    
    const entity = await exec;
    var out = {
      total: totalDocs, 
      count: entity.length,
      results: entity
    }
    if ( limit && (skip + limit) < totalDocs) {
      out.nextSkip = skip+limit;
      out.nextLimit = limit;
    }
    return out;

  } catch (err) {
    throw boom.boomify(err)
  }
}

const getById = async (model, id, reply) => {
  try {
    var exec = model.findById(id);
    if ( model === Provision ) {
      exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
    }
    if ( model === ApiKey ) {
      exec = exec.populate('user');
    }
    if ( model = Scenario ) {
      exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
    }
    const entity = await exec;
    return entity;
  } catch (err) {
    throw boom.boomify(err);
  }
};

const getOne = async (model, filter, reply) => {
  try {
    var exec = model.findOne(filter);
    if ( model === Provision ) {
      exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
    }
    if ( model === ApiKey ) {
      exec = exec.populate('user');
    }
    if ( model = Scenario ) {
      exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
    }
    const entity = await exec;
    return entity;
  } catch (err) {
    throw boom.boomify(err);
  }
};

const add = async (model, data, reply) => {
  try {
    const entity = new model(data)
    return entity.save();
  } catch (err) {
    throw boom.boomify(err);
  }
};

const update = async (model, id, body, reply) => {
  try {
    const { ...updateData } = body;
    updateData.updated = new Date();
    //console.log("UPDATE", id, updateData);
    var exec =  model.findByIdAndUpdate(id, updateData, { new: true });
    if ( model === Provision ) {
      exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
    }
    if ( model === ApiKey ) {
      exec = exec.populate('user');
    }
    if ( model = Scenario ) {
      exec = exec.populate('subscription').populate('deployOpts');
    }
    const update = await exec;
    return update;
  } catch (err) {
    throw boom.boomify(err)
  }
};

const updateMany = async (model, filter, body, reply) => {
  try {
    const { ...updateData } = body;
    
    updateData.updated = new Date();
    
    var exec =  model.updateMany(filter, updateData);
    
    if ( model === Provision ) {
      exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
    }
    if ( model === ApiKey ) {
      exec = exec.populate('user');
    }
    if ( model = Scenario ) {
      exec = exec.populate('subscription').populate('deployOpts');
    }

    return await exec;

  } catch (err) {
    throw boom.boomify(err)
  }
};

const del = async (model, id, reply) => {
  try {
    const entity = await model.findByIdAndRemove(id)
    return entity;
  } catch (err) {
    throw boom.boomify(err)
  }
}

const count = async (model, filter, reply) => {
  try {
    var totalDocs = await model.countDocuments(filter);
    return totalDocs;
  } catch (err) {
    throw boom.boomify(err)
  }
}

function _m(model) {
  return {
    get: async (filter, select, skip, limit, populates, reply) => {
      return get(model, filter, select, skip, limit, populates, reply);
    },
    getPage: async (filter, page, populates, select, reply) => {
      return getPage(model, filter, page, populates, select, reply);
    },
    getById: async (id, reply) => {
      return getById(model, id, reply);
    },
    getOne: async (filter, reply)=>  {
      return getOne(model, filter, reply);
    },
    add: async (data, reply) => {
      return add(model, data, reply);
    },
    update: async (id, data, reply) => {
      return update(model, id, data, reply);
    },
    updateMany: async(filter, data, reply) => {
      return updateMany(model, filter, data, reply);
    },
    del: async (id, reply) => {
      return del(model, id, reply);
    },
    count: async (filter, reply) => {
      return count(model, filter, reply);
    }
  }
}


module.exports = {
  schedule: _m(Schedule),
  provision: _m(Provision),
  destroy: _m(Destroy),
  scenario: _m(Scenario),
  vmtype: _m(VmType),
  apiKey: _m(ApiKey),
  notification: _m(Notification),
  subscription: _m(Subscription),
  event: _m(Event),
  user: _m(User),
  utils: {
    getNewTimeRunning: getNewTimeRunning,
    getNewCountExtend: getNewCountExtend
  },
  mongoose: mongoose,
  models: {
    Provision: Provision,
    Destroy: Destroy,
    Scenario: Scenario,
    User: User,
    Schedule: Schedule,
    VmType: VmType,
    Notification: Notification,
    ApiKey: ApiKey,
    Subscription: Subscription,
    Event: Event
  }
};




