/////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
//                                                                                      /
// All rights reserved. This program and the accompanying materials                     /
// are made available under the terms of the Apache License, Version 2.0                /
// which accompanies this distribution, and is available at                             /
// http://www.apache.org/licenses/LICENSE-2.0                                           /
/////////////////////////////////////////////////////////////////////////////////////////
import { Mongo } from 'meteor/mongo';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
import * as R from 'ramda';
import { Constants } from '/imports/api/constants/constants';
import { MysqlSchema } from './configuration-groups/mysql-configuration';
import { OpenStackSchema } from './configuration-groups/open-stack-configuration';
import { MonitoringSchema } from './configuration-groups/monitoring-configuration';
import { CLISchema } from './configuration-groups/cli-configuration';
import { AMQPSchema } from './configuration-groups/amqp-configuration';
//import { NfvProviderSchema } from './configuration-groups/nfv-provider-configuration';
import { AciSchema } from './configuration-groups/aci-configuration';
import {
  isMonitoringSupported,
  isListeningSupported,
} from '/imports/api/supported_environments/supported_environments';

export const Environments = new Mongo.Collection(
  'environments_config', { idGeneration: 'MONGO' });

export const requiredConfGroups = [
  'mysql',
  'OpenStack',
  'CLI',
];

export const optionalConfGroups = [
 // 'NFV_provider',
  'AMQP',
  'Monitoring',
  'ACI',
];

let simpleSchema = new SimpleSchema({
  _id: { type: { _str: { type: String, regEx: SimpleSchema.RegEx.Id } } },
  auth: {
    type: Object,
    blackbox: true,
    defaultValue: {
      'view-env': [
      ],
      'edit-env': [
      ]
    }
  },
  configuration: {
    type: [Object],
    blackbox: true,
    autoValue: function () {
      console.log('start - autovalue - environment - configuration');
      //console.log(this);
      let that = this;

      if (that.isSet) {
        let confGroups = that.value;

        let { 
          isMonitoringSupportedRes, 
          isListeningSupportedRes,
          enable_monitoring, 
          listen 
        } = extractCalcEnvSupportedRelatedValues(that);
        let dbNode = getDbNode(that);
        let aci_enabled = extractValue('aci_enabled', that, dbNode);

        if (enable_monitoring && isMonitoringSupportedRes) {
          if (! R.find(R.propEq('name', 'Monitoring'), confGroups)) {
            confGroups = R.append(createNewConfGroup('Monitoring'), confGroups);
          }
        } else {
          console.log('env - configurations - autovalue - monitoring not supported');
          confGroups = R.reject(R.propEq('name', 'Monitoring'), confGroups);
        }

        if (listen && isListeningSupportedRes) {
          if (! R.find(R.propEq('name', 'AMQP'), confGroups)) {
            confGroups = R.append(createNewConfGroup('AMQP'), confGroups);
          }
        } else {
          console.log('env - configurations - autovalue - listening not supported');
          confGroups = R.reject(R.propEq('name', 'AMQP'), confGroups);
        }

        if (aci_enabled) {
          if (! R.find(R.propEq('name', 'ACI'), confGroups)) {
            confGroups = R.append(createNewConfGroup('ACI'), confGroups);
          }
        } else {
          console.log('env - configurations - autovalue - aci not requested');
          confGroups = R.reject(R.propEq('name', 'ACI'), confGroups);
        }

        confGroups = cleanOptionalGroups(confGroups, optionalConfGroups);
        console.log('env - configurations - autovalue - after clean optional groups');

        let newValue = R.map(function(confGroup) {
          let schema = getSchemaForGroupName(confGroup.name);
          return schema.clean(confGroup);
        }, confGroups);

        console.log('end - autovalue - environment - configurations');
        console.log(newValue);
        return newValue;

      } else {
        console.log('env - configurations - autovalue - is not set');
        let newValue = R.map((confName) => {
          let schema = getSchemaForGroupName(confName);
          return schema.clean({});
        }, requiredConfGroups);
        console.log('end - autovalue - environment - configurations');
        console.log(newValue);
        return newValue;
      }
    },
    custom: function () {
      console.log('start - custom - environment - configurations');
      //console.log(this);
      let that = this;
      let configurationGroups = that.value;

      let subErrors = [];

      let { 
        isMonitoringSupportedRes, 
        isListeningSupportedRes,
        enable_monitoring, 
        listen 
      } = extractCalcEnvSupportedRelatedValues(that);

      let requiredConfGroupsTemp = R.clone(requiredConfGroups);
      if (enable_monitoring && isMonitoringSupportedRes) {
        requiredConfGroupsTemp = R.append('Monitoring', requiredConfGroupsTemp);
      }
      if (listen && isListeningSupportedRes) {
        requiredConfGroupsTemp = R.append('AMQP', requiredConfGroupsTemp);
      }

      console.log('env - configurations - custom - after mon & listen check');

      let invalidResult = R.find(function(groupName) {
        subErrors = checkGroup(groupName, configurationGroups, true);
        if (subErrors.length > 0) { return true; }
        return false;
      }, requiredConfGroupsTemp);

      console.log(`env - configurations - custom - after require groups check`);

      if (R.isNil(invalidResult)) {
        invalidResult = R.find(function(groupName) {
          subErrors = checkGroup(groupName, configurationGroups, false);
          if (subErrors.length > 0) { return true; }
          return false;
        }, optionalConfGroups);
      }

      console.log(`env - configurations - custom - after optional groups check`);

      if (! R.isNil(invalidResult)) {
        console.log(`env - configrations - custom - invalid result end: ${R.toString(subErrors)}`);
        throw {
          isError: true,
          type: 'subGroupError',
          data: subErrors,
          message: constructSubGroupErrorMessage(subErrors)
        };
      }
    },

  },
  user: {
    type: String,
  },
  distribution: {
    type: String,
    defaultValue: 'Mirantis-8.0',
    custom: function () {
      let that = this;
      let constsDist = Constants.findOne({ name: 'distributions' });

      if (R.isNil(constsDist.data)) { return 'notAllowed'; }
      let distributions = constsDist.data;

      if (R.isNil(R.find(R.propEq('value', that.value), distributions))) {
        return 'notAllowed';
      }
    },
  },
  last_scanned: {
    type: String, defaultValue: ''
  },
  name: {
    type: String,
    defaultValue: 'MyEnvironmentName',
    min: 6,
  },
  type_drivers: {
    type: String,
    defaultValue: 'gre',
    custom: function () {
      let that = this;
      let TypeDriversRec = Constants.findOne({ name: 'type_drivers' });

      if (R.isNil(TypeDriversRec.data)) { return 'notAllowed'; }
      let TypeDrivers = TypeDriversRec.data;

      if (R.isNil(R.find(R.propEq('value', that.value), TypeDrivers))) {
        return 'notAllowed';
      }
    },
  },

  mechanism_drivers: {
    type: [String],
    defaultValue: ['ovs'],
    minCount: 1,
    custom: function () {
      let that = this;
      let consts = Constants.findOne({ name: 'mechanism_drivers' });

      if (R.isNil(consts.data)) { return 'notAllowed'; }
      let mechanismDrivers = consts.data;

      let result = R.find((driver) => {
        if (R.find(R.propEq('value', driver), mechanismDrivers)) {
          return false;
        }
        return true;
      }, that.value);

      if (result) { return 'notAllowed'; }

    },
  },

  operational: {
    type: String,
    allowedValues: ['stopped', 'running', 'error'],
    defaultValue: 'stopped'
  },

  scanned: { type: Boolean, defaultValue: false },

  type: {
    type: String,
    autoValue: function () {
      return 'environment';
    },
  },

  app_path: {
    type: String,
    autoValue: function () {
      return '/home/scan/calipso_prod/app';
    }
  },

  listen: {
    type: Boolean,
    autoValue: function () {
      console.log('env - listen - autoValue - start');
      let that = this;
      let newValue = that.value;
      console.log(`- current value: ${R.toString(newValue)}`);

      let { isListeningSupportedRes } = extractCalcEnvSupportedRelatedValues(that);

      if (!isListeningSupportedRes) {
        console.log('* listening not supported');
        console.log(`* ${R.toString(isListeningSupportedRes)}`);
        newValue = false;
      }

      return newValue;
    },
  },

  enable_monitoring: {
    type: Boolean,
    autoValue: function () {
      console.log('env - enable_monitoring - autoValue - start');
      let that = this;
      let newValue = that.value;
      console.log(`- current value: ${R.toString(newValue)}`);

      let { isMonitoringSupportedRes } = extractCalcEnvSupportedRelatedValues(that);

      if (!isMonitoringSupportedRes) {
        console.log('* monitoring not supported');
        console.log(`* ${R.toString(isMonitoringSupportedRes)}`);
        newValue = false;
      }

      return newValue;
    },
  },
  aci_enabled: {
    type: Boolean,
    defaultValue: false,
  },
});

/*
simpleSchema.addValidator(function () {
  //let that = this;
});
*/

// Bug in simple schema. cant add custom message to instance specific
// schema.
// https://github.com/aldeed/meteor-simple-schema/issues/559
// Version 2 fixes it but it is rc.
//Environments.schema.messages({
SimpleSchema.messages({
  confGroupInvalid: 'Configuration group is invalid.'
});

Environments.schema = simpleSchema;
Environments.attachSchema(Environments.schema);

function getSchemaForGroupName(groupName) {
  switch (groupName) {
  case 'mysql':
    return MysqlSchema;
  case 'OpenStack':
    return OpenStackSchema;
  case 'CLI':
    return CLISchema;
  case 'AMQP':
    return AMQPSchema;
//  case 'NFV_provider':
//    return NfvProviderSchema;
  case 'ACI':
    return AciSchema;
  case 'Monitoring':
    return MonitoringSchema;
  default:
    throw 'group name is not recognized. group: ' + groupName;
  }
}

function constructSubGroupErrorMessage(errors) {
  let message = 'Validation errors on sub groups:';
  message = message + R.reduce((acc, item) => {
    return acc + '\n- ' + item.group + ': ' + item.message;
  }, '', errors);

  return message;
}

function checkGroup(groupName, configurationGroups, groupRequired) {
  let subErrors = [];
  let confGroup = R.find(R.propEq('name', groupName), configurationGroups);

  if (R.isNil(confGroup)) {
    if (groupRequired) {
      subErrors = R.append({
        field: 'configuration',
        group: groupName,
        message: 'group ' + groupName + ' is required'
      }, subErrors);
    }
    return subErrors;
  }

  let validationContext = getSchemaForGroupName(groupName).newContext();

  if (! validationContext.validate(confGroup)) {
    subErrors = R.reduce(function (acc, invalidField) {
      return R.append({
        field: invalidField,
        group: groupName,
        message: validationContext.keyErrorMessage(invalidField.name),
      }, acc);
    }, [], validationContext.invalidKeys());

    return subErrors;
  }

  return subErrors;
}

export function createNewConfGroup(groupName) {
  let schema = getSchemaForGroupName(groupName);
  return schema.clean({});
}

function cleanOptionalGroups(confGroups, optionalConfGroups) {
  return R.filter((conf) => {
    if (R.contains(conf.name, optionalConfGroups)) {
      return !isConfEmpty(conf);
    }

    return true;
  }, confGroups);
}

function isConfEmpty(conf) {
  return ! R.any((key) => {
    if (key === 'name') { return false; } // We ignore the key 'name'. It is a 'type' key.
    let val = conf[key];
    return ! ( R.isNil(val) || R.isEmpty(val));
  })(R.keys(conf));
}

function extractValue(name, schemaValidator, dbNode) {
  console.log('env - extract value');
  console.log(`-name: ${R.toString(name)}`);
  //console.log(`-schemaValidator: ${R.toString(schemaValidator)}`);
  console.log(`-dbNode: ${R.toString(dbNode)}`);

  let field = schemaValidator.field(name);
  let value = field.value;

  console.log(`extract value - schema value: ${R.toString(value)}`);

  if (R.isNil(field.value) && !field.isSet && dbNode) {
    console.log(`extract value - db value: ${R.toString(dbNode[name])}`);
    value = dbNode[name];
  }

  console.log(`extract value - result: ${R.toString(value)}`);
  return value;
}

function getDbNode(schemaHelper) {
  let _id = R.defaultTo(schemaHelper.docId, R.path(['value'], schemaHelper.field('_id')));
  let dbNode = R.defaultTo(null, Environments.findOne({ _id: _id }));
  return dbNode;
}

function extractCalcEnvSupportedRelatedValues(schemaHelper) {
  let dbNode = getDbNode(schemaHelper);

  let dist = extractValue('distribution', schemaHelper, dbNode);
  let typeDrivers = extractValue('type_drivers', schemaHelper, dbNode);
  let mechDrivers = extractValue('mechanism_drivers', schemaHelper, dbNode);
  let enable_monitoring = extractValue('enable_monitoring', schemaHelper, dbNode);
  let listen = extractValue('listen', schemaHelper, dbNode);

  let isMonitoringSupportedRes = isMonitoringSupported(dist, typeDrivers, mechDrivers);
  let isListeningSupportedRes = isListeningSupported(dist, typeDrivers, mechDrivers);

  return {
    enable_monitoring,
    listen,
    isMonitoringSupportedRes,
    isListeningSupportedRes,
  };
}