import Joi from 'joi';

import IdValidate from './id';
import UserValidate from './user';
import MaterialValidate from './material';
import MeasurementValidate from './measurement';
import globals from '../../globals';


export default class SampleValidate {
  private static sample = {
    number: Joi.string()
      .max(128),

    color: Joi.string()
      .max(128)
      .allow(''),

    type: Joi.string()
      .valid('as-delivered/raw', 'processed'),

    batch: Joi.string()
      .max(128)
      .allow(''),

    condition: Joi.object(),

    notes: Joi.object({
      comment: Joi.string()
        .max(512)
        .allow(''),

      sample_references: Joi.array()
        .items(Joi.object({
          sample_id: IdValidate.get(),

          relation: Joi.string()
            .max(128)
        })),

      custom_fields: Joi.object()
        .pattern(/.*/, Joi.alternatives()
          .try(
            Joi.string().max(128),
            Joi.number(),
            Joi.boolean(),
            Joi.date()
          )
        )
    }),

    added: Joi.date()
      .iso()
      .min('1970-01-01T00:00:00.000Z'),

    status: Joi.string()
      .valid(...Object.values(globals.status))
  };

  static readonly sampleKeys = [  // keys which can be found in the sample directly
    '_id',
    'color',
    'number',
    'type',
    'batch',
    'added',
    'condition',
    'material_id',
    'note_id',
    'user_id'
  ];

  private static sortKeys = [
    '_id',
    'color',
    'number',
    'type',
    'batch',
    'added',
    'status',
    'material.name',
    'material.supplier',
    'material.group',
    'material.properties.*',
    'condition.*',
    `measurements.(?!${globals.spectrum.spectrum}.${globals.spectrum.dpt})*`
  ];

  private static fieldKeys = [
    ...SampleValidate.sortKeys,
    'condition',
    'notes',
    'material_id',
    'material',
    'note_id',
    'user_id',
    'material._id',
    'material.numbers',
    `measurements.${globals.spectrum.spectrum}.${globals.spectrum.dpt}`,
  ];

  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
    if (param === 'new') {
      return Joi.object({
        color: this.sample.color.required(),
        type: this.sample.type.required(),
        batch: this.sample.batch.required(),
        condition: this.sample.condition.required(),
        material_id: IdValidate.get().required(),
        notes: this.sample.notes.required()
      }).validate(data);
    }
    else if (param === 'change') {
      return Joi.object({
        color: this.sample.color,
        type: this.sample.type,
        batch: this.sample.batch,
        condition: this.sample.condition,
        material_id: IdValidate.get(),
        notes: this.sample.notes,
      }).validate(data);
    }
    else if (param === 'new-admin') {
      return Joi.object({
        number: this.sample.number,
        color: this.sample.color.required(),
        type: this.sample.type.required(),
        batch: this.sample.batch.required(),
        condition: this.sample.condition.required(),
        material_id: IdValidate.get().required(),
        notes: this.sample.notes.required()
      }).validate(data);
    }
    else {
      return{error: 'No parameter specified!', value: {}};
    }
  }

  // validate output and strip unwanted properties, returns null if not valid
  static output (data, param = 'refs+added', additionalParams = []) {
    if (param === 'refs+added') {
      param = 'refs';
      data.added = data._id.getTimestamp();
    }
    data = IdValidate.stringify(data);
    let joiObject;
    if (param === 'refs') {
      joiObject = {
        _id: IdValidate.get(),
        number: this.sample.number,
        color: this.sample.color,
        type: this.sample.type,
        batch: this.sample.batch,
        condition: this.sample.condition,
        material_id: IdValidate.get(),
        material: MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}),
        note_id: IdValidate.get().allow(null),
        notes: this.sample.notes,
        user_id: IdValidate.get(),
        added: this.sample.added,
        status: this.sample.status
      };
    }
    else if(param === 'details') {
      joiObject = {
        _id: IdValidate.get(),
        number: this.sample.number,
        color: this.sample.color,
        type: this.sample.type,
        batch: this.sample.batch,
        condition: this.sample.condition,
        material: MaterialValidate.outputV(),
        measurements: Joi.array().items(MeasurementValidate.outputV()),
        notes: this.sample.notes,
        user: UserValidate.username(),
        status: this.sample.status
      }
    }
    else {
      return null;
    }
    additionalParams.forEach(param => {
      joiObject[param] = Joi.any();
    });
    const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
    return error !== undefined? null : value;
  }

  static query (data, dev = false) {
    if (data.filters && data.filters.length) {
      const filterValidation = Joi.array().items(Joi.string()).validate(data.filters);
      if (filterValidation.error) return filterValidation;
      try {
        for (let i in data.filters) {
          try {
            data.filters[i] = decodeURIComponent(data.filters[i]);
          }
          catch (ignore) {}
          data.filters[i] = JSON.parse(data.filters[i]);
          data.filters[i].values = data.filters[i].values.map(e => {  // validate filter values
            if (e === null) {  // null values are always allowed
              return null;
            }
            let validator;
            let field = data.filters[i].field;
            if (/material\./.test(field)) {  // select right validation model
              validator = MaterialValidate.outputV().append({
                number: Joi.string().max(128).allow(''),
                properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128))
              });
              field = field.replace('material.', '').split('.')[0];
            }
            else if (/measurements\./.test(field) || /condition\./.test(field)) {
              validator = Joi.object({
                value: Joi.alternatives()
                  .try(
                    Joi.number(),
                    Joi.string().max(128),
                    Joi.boolean(),
                    Joi.array()
                  )
                  .allow(null)
              });
              field = 'value';
            }
            else {
              validator = Joi.object(this.sample);
            }
            const {value, error} = validator.validate({[field]: e});
            console.log(error);
            if (error) throw error;  // reject invalid values
            return value[field];
          });
        }
      }
      catch (err) {
        return {error: {details: [{message: 'Invalid JSON string for filter parameter'}]}, value: null}
      }
    }
    const acceptedStatuses = [globals.status.val, globals.status.new];
    if (dev) {  // dev and admin can also access deleted samples
      acceptedStatuses.push(globals.status.del)
    }
    return Joi.object({
      status: Joi.array().items(Joi.string().valid(...acceptedStatuses)).default([globals.status.val]),
      'from-id': IdValidate.get(),
      'to-page': Joi.number().integer(),
      'page-size': Joi.number().integer().min(1),
      sort: Joi.string().pattern(
        new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')-(asc|desc)$', 'm')
      ).default('_id-asc'),
      output: Joi.string().valid('json', 'flatten', 'csv').default('json'),
      fields: Joi.array().items(Joi.string().pattern(
        new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
      )).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added'])
        .messages({'string.pattern.base': 'Invalid field name'}),
      filters: Joi.array().items(Joi.object({
        mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'stringin'),
        field: Joi.string().pattern(
          new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')
        ).messages({'string.pattern.base': 'Invalid filter field name'}),
        values: Joi.array().items(Joi.alternatives().try(
          Joi.string().max(128), Joi.number(), Joi.boolean(), Joi.date().iso(), Joi.object(), null
        )).min(1)
      })).default([])
    }).with('to-page', 'page-size').validate(data);
  }
}