import express from 'express';

import SampleValidate from './validate/sample';
import NoteFieldValidate from './validate/note_field';
import res400 from './validate/res400';
import SampleModel from '../models/sample'
import MaterialModel from '../models/material';
import NoteModel from '../models/note';
import NoteFieldModel from '../models/note_field';
import IdValidate from './validate/id';


const router = express.Router();

router.get('/samples', (req, res, next) => {
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;

  SampleModel.find({}).lean().exec((err, data) => {
    if (err) return next(err);
    res.json(data.map(e => SampleValidate.output(e)).filter(e => e !== null));  // validate all and filter null values from validation errors
  })
});

router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;

  const {error, value: sample} = SampleValidate.input(req.body, 'change');
  if (error) return res400(error, res);

  SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => {  // check if id exists
    if (err) return next(err);
    if (!sampleData) {
      return res.status(404).json({status: 'Not found'});
    }
    // only maintain and admin are allowed to edit other user's data
    if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;

    if (sample.hasOwnProperty('number') && sample.number !== sampleData.number) {
      if (!await numberCheck(sample, res, next)) return;
    }
    if (sample.hasOwnProperty('material_id')) {
      if (!await materialCheck(sample, res, next)) return;
    }
    else if (sample.hasOwnProperty('color')) {
      if (!await materialCheck(sample, res, next, sampleData.material_id)) return;
    }

    if (sample.hasOwnProperty('notes') && sampleData.note_id !== null) {  // deal with old notes data
      NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => {
        if (err) return console.error(err);
        if (data.hasOwnProperty('custom_fields')) {  // update note_fields
          customFieldsChange(Object.keys(data.custom_fields), -1);
        }
        NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => {  // delete old notes
          if (err) return console.error(err);
        })
      });
    }
    if (sample.hasOwnProperty('notes') && Object.keys(sample.notes).length > 0) {  // save new notes
      if (!await sampleRefCheck(sample, res, next)) return;
      if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {  // new custom_fields
        customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
      }
      let data = await new NoteModel(sample.notes).save().catch(err => { return next(err)});  // save new notes
      delete sample.notes;
      sample.note_id = data._id;
    }
    SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => {
      if (err) return next(err);
      res.json(SampleValidate.output(data));
    });

  });
});

router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;

  SampleModel.findById(req.params.id).lean().exec(async (err, sampleData: any) => {  // check if id exists
    if (err) return next(err);
    if (!sampleData) {
      return res.status(404).json({status: 'Not found'});
    }
    // only maintain and admin are allowed to edit other user's data
    if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;

    SampleModel.findByIdAndDelete(req.params.id).lean().exec(err => {  // delete sample
      if (err) return next(err);
      if (sampleData.note_id !== null) {
        NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec((err, data: any) => {  // delete notes
          if (err) return next(err);
          console.log(data);
          if (data.hasOwnProperty('custom_fields')) {  // update note_fields
            customFieldsChange(Object.keys(data.custom_fields), -1);
          }
          res.json({status: 'OK'});
          NoteModel.updateMany({'sample_references.id': req.params.id}, {$unset: {'sample_references.$': null}}).lean().exec(err => {  // remove sample_references
            if (err) console.error(err);
            NoteModel.collection.updateMany({sample_references: null}, {$pull: {sample_references: null}}, err => {  // only works with native MongoDB driver somehow
              if (err) console.error(err);
            });
          });
        });
      }
      else {
        res.json({status: 'OK'});
      }
    });
  });
});

router.post('/sample/new', async (req, res, next) => {
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;

  const {error, value: sample} = SampleValidate.input(req.body, 'new');
  if (error) return res400(error, res);

  if (!await numberCheck(sample, res, next)) return;
  if (!await materialCheck(sample, res, next)) return;
  if (!await sampleRefCheck(sample, res, next)) return;

  if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {  // new custom_fields
    customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
  }

  new NoteModel(sample.notes).save((err, data) => {
    if (err) return next(err);
    delete sample.notes;
    sample.note_id = data._id;
    sample.user_id = req.authDetails.id;
    console.log(sample);
    new SampleModel(sample).save((err, data) => {
      if (err) return next(err);
      res.json(SampleValidate.output(data.toObject()));
    });
  });
});

router.get('/sample/notes/fields', (req, res, next) => {
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;

  NoteFieldModel.find({}).lean().exec((err, data) => {
    if (err) return next(err);
    res.json(data.map(e => NoteFieldValidate.output(e)).filter(e => e !== null));  // validate all and filter null values from validation errors
  })
});


module.exports = router;


async function numberCheck (sample, res, next) {  // validate number, returns false if invalid
  const sampleData = await SampleModel.findOne({number: sample.number}).lean().exec().catch(err => {next(err); return false;});
  if (sampleData) {  // found entry with sample number
    res.status(400).json({status: 'Sample number already taken'});
    return false
  }
  return true;
}

async function materialCheck (sample, res, next, id = sample.material_id) {  // validate material_id and color, returns false if invalid
  const materialData = await MaterialModel.findById(id).lean().exec().catch(err => {next(err); return false;}) as any;
  if (materialData instanceof Error) {
    return false;
  }
  if (!materialData) {  // could not find material_id
    res.status(400).json({status: 'Material not available'});
    return false;
  }
  if (sample.hasOwnProperty('color') && !materialData.numbers.find(e => e.color === sample.color)) {  // color for material not specified
    res.status(400).json({status: 'Color not available for material'});
    return false;
  }
  return true;
}

function sampleRefCheck (sample, res, next) {  // validate sample_references, resolves false for invalid reference
  return new Promise(resolve => {
    if (sample.notes.sample_references.length > 0) {  // there are sample_references
      let referencesCount = sample.notes.sample_references.length;
      sample.notes.sample_references.forEach(reference => {
        SampleModel.findById(reference.id).lean().exec((err, data) => {
          if (err) {next(err); resolve(false)}
          if (!data) {
            res.status(400).json({status: 'Sample reference not available'});
            return resolve(false);
          }
          referencesCount --;
          if (referencesCount <= 0) {
            resolve(true);
          }
        });
      });
    }
    else {
      resolve(true);
    }
  });
}

function customFieldsChange (fields, amount) {
  fields.forEach(field => {
    NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}}, {new: true}).lean().exec((err, data: any) => {  // check if field exists
      if (err) return console.error(err);
      if (!data) {  // new field
        new NoteFieldModel({name: field, qty: 1}).save(err => {
          if (err) return console.error(err);
        })
      }
      else if (data.qty <= 0) {
        NoteFieldModel.findOneAndDelete({name: field}).lean().exec(err => {
          if (err) return console.error(err);
        });
      }
    });
  });
}