import { inject as service } from '@ember/service';
import { on } from '@ember/object/evented';
import { later } from '@ember/runloop';
import Component from '@ember/component';
import {
  set, get, computed, observer, setProperties
} from '@ember/object';
import { alias } from '@ember/object/computed';
import { convertToMillis } from 'shared/utils/util';
import { parseSi } from 'shared/utils/parse-unit';
import ReservationCheck from 'shared/mixins/reservation-check';
import CatalogUpgrade from 'shared/mixins/catalog-upgrade';
import layout from './template';

const EXPOSED_OPTIONS = ['exporter-node.enabled', 'exporter-node.ports.metrics.port',
  'exporter-kubelets.https', 'prometheus.retention', 'grafana.persistence.enabled',
  'prometheus.persistence.enabled', 'prometheus.persistence.storageClass',
  'grafana.persistence.storageClass', 'grafana.persistence.size',
  'prometheus.persistence.size', 'prometheus.resources.core.requests.cpu',
  'exporter-node.resources.limits.cpu', 'exporter-node.resources.limits.memory',
  'prometheus.resources.core.limits.cpu', 'prometheus.resources.core.requests.memory',
  'prometheus.resources.core.limits.memory', 'operator.resources.limits.memory'];

const NODE_EXPORTER_CPU = 100;
const NODE_EXPORTER_MEMORY = 30;
const CLUSTER_CPU = 900;
const CLUSTER_MEMORY = 970;
const MONITORING_TEMPLATE = 'system-library-rancher-monitoring';

const CLUSTER_HIDDEN_KEYS = { 'operator-init.enabled': 'true', }

const PROMETHEUS_TOLERATION = 'prometheus.tolerations'

const SCHEDULABLE_COMPONENTS =  ['prometheus', 'grafana', 'operator', 'exporter-kube-state']

export default Component.extend(ReservationCheck, CatalogUpgrade, {
  scope:    service(),
  settings: service(),

  layout,

  templateId:                  MONITORING_TEMPLATE,
  answers:                     null,
  customAnswers:               null,
  level:                       'cluster',
  confirmDisable:              false,
  justDeployed:                false,
  enablePrometheusPersistence: false,
  enableGrafanaPersistence:    false,
  enableNodeExporter:          true,
  prometheusPersistenceSize:   '50Gi',
  grafanaPersistenceSize:      '10Gi',
  requestsCpu:                 '750',
  limitsCpu:                   '1000',
  nodeExporterLimitsCpu:       '200',
  nodeExporterLimitsMemory:    '200',
  operatorLimitsMemory:        '500',
  requestsMemory:              '750',
  limitsMemory:                '1000',
  prometheusStorageClass:      null,
  grafanaStorageClass:         null,
  nodeSelectors:               null,
  retention:                   12,
  port:                        9796,
  projectLevelMinCpu:          500,
  projectLevelMinMemory:       500,

  cluster:      alias('scope.currentCluster'),
  project:      alias('scope.currentProject'),
  istioEnabled: alias('cluster.istioEnabled'),

  actions: {
    promptDisable() {
      if (get(this, 'istioEnabled') && get(this, 'level') === 'cluster') {
        return
      }

      set(this, 'confirmDisable', true);
      later(this, function() {
        if (this.isDestroyed || this.isDestroying) {
          return;
        }
        set(this, 'confirmDisable', false);
      }, 10000);
    },

    enable(cb) {
      const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');

      let answers = get(this, 'level') === 'cluster' ? CLUSTER_HIDDEN_KEYS : {};

      if ( get(this, 'level') === 'cluster' ) {
        answers['exporter-node.enabled'] = `${ get(this, 'enableNodeExporter') }`;
        answers['exporter-node.ports.metrics.port'] = `${ get(this, 'port') }`;
        answers['exporter-kubelets.https'] = `${ !(get(this, 'scope.currentCluster.isGKE') || get(this, 'scope.currentCluster.isAKS')) }`;
        answers['exporter-node.resources.limits.cpu'] = `${ get(this, 'nodeExporterLimitsCpu') }m`;
        answers['exporter-node.resources.limits.memory'] = `${ get(this, 'nodeExporterLimitsMemory') }Mi`;
        answers['operator.resources.limits.memory'] = `${ get(this, 'operatorLimitsMemory') }Mi`;
      }

      answers['prometheus.retention'] = `${ get(this, 'retention') }h`;
      answers['grafana.persistence.enabled'] = `${ get(this, 'enableGrafanaPersistence') }`;
      answers['prometheus.persistence.enabled'] = `${ get(this, 'enablePrometheusPersistence') }`;
      answers['prometheus.persistence.storageClass'] = `${ get(this, 'prometheusStorageClass') === null ? 'default' : get(this, 'prometheusStorageClass') }`;
      answers['grafana.persistence.storageClass'] = `${ get(this, 'grafanaStorageClass') === null ? 'default' : get(this, 'grafanaStorageClass') }`;
      answers['grafana.persistence.size'] = `${ get(this, 'grafanaPersistenceSize') }`;
      answers['prometheus.persistence.size'] = `${ get(this, 'prometheusPersistenceSize') }`;
      answers['prometheus.resources.core.requests.cpu'] = `${ get(this, 'requestsCpu') }m`;
      answers['prometheus.resources.core.limits.cpu'] = `${ get(this, 'limitsCpu') }m`;
      answers['prometheus.resources.core.requests.memory'] = `${ get(this, 'requestsMemory') }Mi`;
      answers['prometheus.resources.core.limits.memory'] = `${ get(this, 'limitsMemory') }Mi`;
      if ( !get(this, 'enabled') || get(this, 'useReleaseName') ) {
        answers['prometheus.persistent.useReleaseName'] = 'true';
      }

      Object.keys(answers).filter((key) => SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.nodeSelectors[`))).forEach((key) => {
        delete answers[key]
      });

      (get(this, 'nodeSelectors') || []).forEach((selector, index) => {
        let s = get(selector, 'key');
        let v = get(selector, 'value');

        if ( v && v.startsWith('"') && v.endsWith('"') ) {
          s += `="${ get(selector, 'value') }"`;
        } else if (v) {
          s += `="${ get(selector, 'value') }"`;
        }
        SCHEDULABLE_COMPONENTS.forEach((c) => {
          answers[`${ c }.nodeSelectors[${ index }]`] = s;
        });
      });

      Object.keys(answers).filter((key) => SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.tolerations[`))).forEach((key) => {
        delete answers[key]
      });

      SCHEDULABLE_COMPONENTS.map((component) => {
        (get(this, `prometheusTolerations`) || []).map((t, index) => {
          Object.keys(t).map((key) => {
            if (t[key]) {
              answers[`${ component }.tolerations[${ index }].${ key }`] = t[key].toString()
            }
          })
        });
      })

      let action = get(this, 'enabled') ?  'editMonitoring' : 'enableMonitoring';

      const customAnswers = get(this, 'customAnswers') || {};

      Object.keys(customAnswers).forEach((key) => {
        answers[key] = customAnswers[key]
      });

      const params = { answers };

      if ( !get(this, 'enabled') && get(this, 'templateVersion') ) {
        params['version'] = get(this, 'templateVersion');
      }

      resource.doAction(action, params).then(() => {
        if (this.isDestroyed || this.isDestroying) {
          return;
        }

        set(this, 'justDeployed', true);

        if ( action === 'editMonitoring' ) {
          this.send('upgrade');
        }
        cb(true);
        this.fetchSettings();
      }).catch(() => {
        cb(false);
      });
    },

    disable() {
      const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');

      resource.doAction('disableMonitoring').then(() => {
        if (this.isDestroyed || this.isDestroying) {
          return;
        }

        setProperties(this, {
          app:           null,
          answers:       null,
          customAnswers: null,
        })
      })
    }
  },

  enabled: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
    return get(this, 'level') === 'cluster' ? get(this, 'cluster.enableClusterMonitoring') : get(this, 'project.enableProjectMonitoring');
  }),

  canDisableMonitor: computed('cluster.canDisableMonitor', 'project.canDisableMonitor', 'level', function() {
    return get(this, 'level') === 'cluster' ? get(this, 'cluster.canDisableMonitor') : get(this, 'project.canDisableMonitor');
  }),

  canSaveMonitor: computed('cluster.canSaveMonitor', 'project.canSaveMonitor', 'level', function() {
    return get(this, 'level') === 'cluster' ? get(this, 'cluster.canSaveMonitor') : get(this, 'project.canSaveMonitor');
  }),

  clusterLevelMinCpu: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
    const allNodes = get(this, 'scope.currentCluster.nodes') || [];
    const schedulableNodes = allNodes.filterBy('isUnschedulable', false);

    return  CLUSTER_CPU + get(schedulableNodes, 'length') * NODE_EXPORTER_CPU;
  }),

  clusterLevelMinMemory: computed('cluster.enableClusterMonitoring', 'project.enableProjectMonitoring', 'level', function() {
    const allNodes = get(this, 'scope.currentCluster.nodes') || [];
    const schedulableNodes = allNodes.filterBy('isUnschedulable', false);

    return  CLUSTER_MEMORY + get(schedulableNodes, 'length') * NODE_EXPORTER_MEMORY;
  }),

  saveDisabled: computed('canSaveMonitor', 'insufficientPrometheusMemory', 'insufficientPrometheusCpu', 'insufficient', 'enabled', function() {
    return !get(this, 'canSaveMonitor') || get(this, 'insufficient') || get(this, 'insufficientPrometheusCpu') || get(this, 'insufficientPrometheusMemory');
  }),

  insufficientPrometheusCpu: computed('schedulableNodes.@each.{allocatable,requested}', 'requestsCpu', 'scope.currentCluster.nodes.@each.{allocatable,requested}', function() {
    const allNodes = get(this, 'scope.currentCluster.nodes') || [];

    if ( get(allNodes, 'length') === 0 ) {
      return false;
    }

    let maxLeftCpu = 0;

    get(this, 'schedulableNodes').forEach((node) => {
      const left =  convertToMillis(get(node, 'allocatable.cpu')) - convertToMillis(get(node, 'requested.cpu') || '0');

      if ( left > maxLeftCpu) {
        maxLeftCpu = left;
      }
    });

    return !get(this, 'enabled') && maxLeftCpu <= get(this, 'prometheusRequestCpu');
  }),

  prometheusRequestCpu: computed('requestsCpu', function() {
    return parseInt(get(this, 'requestsCpu'), 10) + NODE_EXPORTER_CPU + 200;
  }),

  prometheusRequestMemory: computed('requestsMemory', function() {
    return parseInt(get(this, 'requestsMemory'), 10) + NODE_EXPORTER_MEMORY + 200;
  }),

  insufficientPrometheusMemory: computed('schedulableNodes.@each.{allocatable,requested}', 'requestsMemory', 'scope.currentCluster.nodes.@each.{allocatable,requested}', function() {
    const allNodes = get(this, 'scope.currentCluster.nodes') || [];

    if ( get(allNodes, 'length') === 0 ) {
      return false;
    }

    let maxLeftMemory = 0;

    get(this, 'schedulableNodes').forEach((node) => {
      const left =  (parseSi(get(node, 'allocatable.memory'), 1024) / 1048576) - (parseSi(get(node, 'requested.memory') || '0', 1024) / 1048576);

      if ( left > maxLeftMemory) {
        maxLeftMemory = left;
      }
    });

    return !get(this, 'enabled') && maxLeftMemory <= get(this, 'prometheusRequestMemory');
  }),

  schedulableNodes: computed('nodeSelectors', 'scope.currentCluster.nodes.@each.{allocatable,requested}', function() {
    const allNodes = get(this, 'scope.currentCluster.nodes') || [];
    const out = allNodes.filterBy('isUnschedulable', false)
      .filter((node) => (get(this, 'nodeSelectors') || [])
        .every((selector) => {
          const labelValue = (get(node, 'labels') || {})[get(selector, 'key')];

          if ( get(selector, 'value') === '' ) {
            return labelValue !== undefined;
          } else {
            return get(selector, 'value') === labelValue;
          }
        }));

    return out;
  }),

  initSettings: on('init', observer('scope.currentProject.id', 'scope.currentCluster.id', function() {
    if ( get(this, 'enabled') ) {
      this.fetchSettings();
    }
  })),

  fetchSettings() {
    const resource = get(this, 'level') === 'cluster' ? get(this, 'cluster') : get(this, 'project');

    if ( !resource ) {
      return;
    }

    set(this, 'loading', true);
    resource.waitForAction('viewMonitoring').then(() => {
      if (this.isDestroyed || this.isDestroying) {
        return;
      }

      resource.doAction('viewMonitoring').then((res) => {
        if (this.isDestroyed || this.isDestroying) {
          return;
        }

        const body = get(res, 'answers');
        const answers = {};
        const customAnswers = {};

        Object.keys(body || {}).forEach((key) => {
          if ( EXPOSED_OPTIONS.indexOf(key) > -1 ||
            SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.nodeSelectors[`)) ||
            SCHEDULABLE_COMPONENTS.find((c) => key.startsWith(`${ c }.tolerations[`))
          ) {
            answers[key] = body[key];
          } else if (!Object.keys(CLUSTER_HIDDEN_KEYS).includes(key)) {
            customAnswers[key] = body[key];
          }
        });

        set(this, 'answers', answers);
        set(this, 'customAnswers', customAnswers);
        this.updateConfig(answers);
        set(this, 'loading', false);
      }).catch(() => {
        set(this, 'loading', false);
      });
    })
  },

  updateConfig(answers) {
    if ( answers['prometheus.persistent.useReleaseName'] ) {
      set(this, 'useReleaseName', answers['prometheus.persistent.useReleaseName']);
    } else {
      set(this, 'useReleaseName', null);
    }

    if ( answers['prometheus.resources.core.requests.cpu'] ) {
      const requestsCpu = convertToMillis(answers['prometheus.resources.core.requests.cpu'])

      setProperties(this, {
        requestsCpu,
        preRequestsCpu: requestsCpu,
      })
    }

    if ( answers['prometheus.resources.core.limits.cpu'] ) {
      set(this, 'limitsCpu', convertToMillis(answers['prometheus.resources.core.limits.cpu']));
    }

    if ( answers['exporter-node.resources.limits.cpu'] ) {
      set(this, 'nodeExporterLimitsCpu', convertToMillis(answers['exporter-node.resources.limits.cpu']));
    }

    if ( answers['prometheus.resources.core.requests.memory'] ) {
      const requestsMemory = parseSi(answers['prometheus.resources.core.requests.memory'], 1024) / 1048576

      setProperties(this, {
        requestsMemory,
        preRequestsMemory: requestsMemory,
      })
    }

    if ( answers['prometheus.resources.core.limits.memory'] ) {
      set(this, 'limitsMemory', parseSi(answers['prometheus.resources.core.limits.memory'], 1024) / 1048576);
    }

    if ( answers['exporter-node.resources.limits.memory'] ) {
      set(this, 'nodeExporterLimitsMemory', parseSi(answers['exporter-node.resources.limits.memory'], 1024) / 1048576);
    } else {
      set(this, 'nodeExporterLimitsMemory', '50');
    }

    if ( answers['operator.resources.limits.memory'] ) {
      set(this, 'operatorLimitsMemory', parseSi(answers['operator.resources.limits.memory'], 1024) / 1048576);
    } else {
      set(this, 'operatorLimitsMemory', '100');
    }

    if ( answers['prometheus.retention'] ) {
      set(this, 'retention', answers['prometheus.retention'].substr(0, answers['prometheus.retention'].length - 1));
    }
    if ( answers['grafana.persistence.enabled'] ) {
      set(this, 'enableGrafanaPersistence', answers['grafana.persistence.enabled'] === 'true');
    }
    if ( answers['prometheus.persistence.enabled'] ) {
      set(this, 'enablePrometheusPersistence', answers['prometheus.persistence.enabled'] === 'true');
    }
    if ( answers['prometheus.persistence.storageClass'] ) {
      set(this, 'prometheusStorageClass', answers['prometheus.persistence.storageClass'] === 'default' ? null : answers['prometheus.persistence.storageClass']);
    }
    if ( answers['grafana.persistence.storageClass'] ) {
      set(this, 'grafanaStorageClass', answers['grafana.persistence.storageClass'] === 'default' ? null : answers['grafana.persistence.storageClass']);
    }
    if ( answers['grafana.persistence.size'] ) {
      set(this, 'grafanaPersistenceSize', answers['grafana.persistence.size'])
    }
    if ( answers['prometheus.persistence.size'] ) {
      set(this, 'prometheusPersistenceSize', answers['prometheus.persistence.size'])
    }
    if ( get(this, 'level') === 'cluster' ) {
      if ( answers['exporter-node.enabled'] ) {
        set(this, 'enableNodeExporter', answers['exporter-node.enabled'] === 'true');
      }
      if ( answers['exporter-node.ports.metrics.port'] ) {
        set(this, 'port', answers['exporter-node.ports.metrics.port'])
      }
    }
    let nodeSelectorsStr = '';

    Object.keys(answers).filter((key) => key.startsWith('prometheus.nodeSelectors[') ).forEach((key) => {
      let value = answers[key];

      if ( value ) {
        const index = value.indexOf('=');

        if ( index > -1 ) {
          let keyStr = value.slice(index + 1);

          if ( keyStr && keyStr.startsWith('"') && keyStr.endsWith('"') ) {
            keyStr = keyStr.slice(1, keyStr.length - 1);
          }

          value = `${ value.slice(0, index) }=${ keyStr }`;
        }
      }
      nodeSelectorsStr += `${ value },`;
    });

    set(this, 'nodeSelectorsStr', nodeSelectorsStr);

    const prometheusTolerations = []

    const prometheusTolerationKeys = Object.keys(answers).filter((key) => key.startsWith(PROMETHEUS_TOLERATION) )
    const prometheusTolerationIndexs = prometheusTolerationKeys.map((k) => {
      return k.replace(`${ PROMETHEUS_TOLERATION }[`, '').split('].').get('firstObject')
    }).uniq()

    prometheusTolerationIndexs.map((idx) => {
      prometheusTolerations.pushObject({
        key:               answers[`${ PROMETHEUS_TOLERATION }[${ idx }].key`] || '',
        operator:          answers[`${ PROMETHEUS_TOLERATION }[${ idx }].operator`] || '',
        value:             answers[`${ PROMETHEUS_TOLERATION }[${ idx }].value`] || '',
        effect:            answers[`${ PROMETHEUS_TOLERATION }[${ idx }].effect`] || '',
        tolerationSeconds: answers[`${ PROMETHEUS_TOLERATION }[${ idx }].tolerationSeconds`] || '',
      })
    })
    set(this, 'prometheusTolerations', prometheusTolerations)
  },
});
