import angular, { auto } from 'angular';
import _ from 'lodash';
import { InfluxQueryBuilder } from './query_builder';
import InfluxQueryModel from './influx_query_model';
import queryPart from './query_part';
import { QueryCtrl } from 'app/plugins/sdk';
import { TemplateSrv } from '@grafana/runtime';
import { InfluxQuery } from './types';
import InfluxDatasource from './datasource';

export class InfluxQueryCtrl extends QueryCtrl {
  static templateUrl = 'partials/query.editor.html';

  datasource: InfluxDatasource;
  queryModel: InfluxQueryModel;
  queryBuilder: any;
  groupBySegment: any;
  resultFormats: any[];
  orderByTime: any[];
  policySegment: any;
  tagSegments: any[];
  selectMenu: any;
  measurementSegment: any;
  removeTagFilterSegment: any;

  /** @ngInject */
  constructor(
    $scope: any,
    $injector: auto.IInjectorService,
    private templateSrv: TemplateSrv,
    private uiSegmentSrv: any
  ) {
    super($scope, $injector);
    this.target = this.target;
    this.queryModel = new InfluxQueryModel(this.target, templateSrv, this.panel.scopedVars);
    this.queryBuilder = new InfluxQueryBuilder(this.target, this.datasource.database);
    this.groupBySegment = this.uiSegmentSrv.newPlusButton();
    this.resultFormats = [
      { text: 'Time series', value: 'time_series' },
      { text: 'Table', value: 'table' },
      { text: 'Logs', value: 'logs' },
    ];

    this.policySegment = uiSegmentSrv.newSegment(this.target.policy);

    if (!this.target.measurement) {
      this.measurementSegment = uiSegmentSrv.newSelectMeasurement();
    } else {
      this.measurementSegment = uiSegmentSrv.newSegment(this.target.measurement);
    }

    this.tagSegments = [];
    for (const tag of this.target.tags) {
      if (!tag.operator) {
        if (/^\/.*\/$/.test(tag.value)) {
          tag.operator = '=~';
        } else {
          tag.operator = '=';
        }
      }

      if (tag.condition) {
        this.tagSegments.push(uiSegmentSrv.newCondition(tag.condition));
      }

      this.tagSegments.push(uiSegmentSrv.newKey(tag.key));
      this.tagSegments.push(uiSegmentSrv.newOperator(tag.operator));
      this.tagSegments.push(uiSegmentSrv.newKeyValue(tag.value));
    }

    this.fixTagSegments();
    this.buildSelectMenu();
    this.removeTagFilterSegment = uiSegmentSrv.newSegment({
      fake: true,
      value: '-- remove tag filter --',
    });
  }

  /**
   * Only called for flux
   */
  onChange = (target: InfluxQuery) => {
    this.target.query = target.query;
  };

  onRunQuery = () => {
    this.panelCtrl.refresh();
  };

  removeOrderByTime() {
    this.target.orderByTime = 'ASC';
  }

  buildSelectMenu() {
    const categories = queryPart.getCategories();
    this.selectMenu = _.reduce(
      categories,
      (memo, cat, key) => {
        const menu = {
          text: key,
          submenu: cat.map((item: any) => {
            return { text: item.type, value: item.type };
          }),
        };
        memo.push(menu);
        return memo;
      },
      [] as any
    );
  }

  getGroupByOptions() {
    const query = this.queryBuilder.buildExploreQuery('TAG_KEYS');

    return this.datasource
      .metricFindQuery(query)
      .then((tags: any) => {
        const options = [];
        if (!this.queryModel.hasFill()) {
          options.push(this.uiSegmentSrv.newSegment({ value: 'fill(null)' }));
        }
        if (!this.target.limit) {
          options.push(this.uiSegmentSrv.newSegment({ value: 'LIMIT' }));
        }
        if (!this.target.slimit) {
          options.push(this.uiSegmentSrv.newSegment({ value: 'SLIMIT' }));
        }
        if (!this.target.tz) {
          options.push(this.uiSegmentSrv.newSegment({ value: 'tz' }));
        }
        if (this.target.orderByTime === 'ASC') {
          options.push(this.uiSegmentSrv.newSegment({ value: 'ORDER BY time DESC' }));
        }
        if (!this.queryModel.hasGroupByTime()) {
          options.push(this.uiSegmentSrv.newSegment({ value: 'time($interval)' }));
        }
        for (const tag of tags) {
          options.push(this.uiSegmentSrv.newSegment({ value: 'tag(' + tag.text + ')' }));
        }
        return options;
      })
      .catch(this.handleQueryError.bind(this));
  }

  groupByAction() {
    switch (this.groupBySegment.value) {
      case 'LIMIT': {
        this.target.limit = 10;
        break;
      }
      case 'SLIMIT': {
        this.target.slimit = 10;
        break;
      }
      case 'tz': {
        this.target.tz = 'UTC';
        break;
      }
      case 'ORDER BY time DESC': {
        this.target.orderByTime = 'DESC';
        break;
      }
      default: {
        this.queryModel.addGroupBy(this.groupBySegment.value);
      }
    }

    const plusButton = this.uiSegmentSrv.newPlusButton();
    this.groupBySegment.value = plusButton.value;
    this.groupBySegment.html = plusButton.html;
    this.groupBySegment.fake = true;
    this.panelCtrl.refresh();
  }

  addSelectPart(selectParts: any, cat: any, subitem: { value: any }) {
    this.queryModel.addSelectPart(selectParts, subitem.value);
    this.panelCtrl.refresh();
  }

  handleSelectPartEvent(selectParts: any, part: any, evt: { name: any }) {
    switch (evt.name) {
      case 'get-param-options': {
        const fieldsQuery = this.queryBuilder.buildExploreQuery('FIELDS');
        return this.datasource
          .metricFindQuery(fieldsQuery)
          .then(this.transformToSegments(true))
          .catch(this.handleQueryError.bind(this));
      }
      case 'part-param-changed': {
        this.panelCtrl.refresh();
        break;
      }
      case 'action': {
        this.queryModel.removeSelectPart(selectParts, part);
        this.panelCtrl.refresh();
        break;
      }
      case 'get-part-actions': {
        return Promise.resolve([{ text: 'Remove', value: 'remove-part' }]);
      }
    }
    return Promise.resolve();
  }

  handleGroupByPartEvent(part: any, index: any, evt: { name: any }) {
    switch (evt.name) {
      case 'get-param-options': {
        const tagsQuery = this.queryBuilder.buildExploreQuery('TAG_KEYS');
        return this.datasource
          .metricFindQuery(tagsQuery)
          .then(this.transformToSegments(true))
          .catch(this.handleQueryError.bind(this));
      }
      case 'part-param-changed': {
        this.panelCtrl.refresh();
        break;
      }
      case 'action': {
        this.queryModel.removeGroupByPart(part, index);
        this.panelCtrl.refresh();
        break;
      }
      case 'get-part-actions': {
        return Promise.resolve([{ text: 'Remove', value: 'remove-part' }]);
      }
    }
    return Promise.resolve();
  }

  fixTagSegments() {
    const count = this.tagSegments.length;
    const lastSegment = this.tagSegments[Math.max(count - 1, 0)];

    if (!lastSegment || lastSegment.type !== 'plus-button') {
      this.tagSegments.push(this.uiSegmentSrv.newPlusButton());
    }
  }

  measurementChanged() {
    this.target.measurement = this.measurementSegment.value;
    this.panelCtrl.refresh();
  }

  getPolicySegments() {
    const policiesQuery = this.queryBuilder.buildExploreQuery('RETENTION POLICIES');
    return this.datasource
      .metricFindQuery(policiesQuery)
      .then(this.transformToSegments(false))
      .catch(this.handleQueryError.bind(this));
  }

  policyChanged() {
    this.target.policy = this.policySegment.value;
    this.panelCtrl.refresh();
  }

  // Only valid for InfluxQL queries
  toggleEditorMode() {
    if (this.datasource.isFlux) {
      return; // nothing
    }

    try {
      this.target.query = this.queryModel.render(false);
    } catch (err) {
      console.error('query render error');
    }
    this.target.rawQuery = !this.target.rawQuery;
  }

  getMeasurements(measurementFilter: any) {
    const query = this.queryBuilder.buildExploreQuery('MEASUREMENTS', undefined, measurementFilter);
    return this.datasource
      .metricFindQuery(query)
      .then(this.transformToSegments(true))
      .catch(this.handleQueryError.bind(this));
  }

  handleQueryError(err: any): any[] {
    this.error = err.message || 'Failed to issue metric query';
    return [];
  }

  transformToSegments(addTemplateVars: any) {
    return (results: any) => {
      const segments = _.map(results, (segment) => {
        return this.uiSegmentSrv.newSegment({
          value: segment.text,
          expandable: segment.expandable,
        });
      });

      if (addTemplateVars) {
        for (const variable of this.templateSrv.getVariables()) {
          segments.unshift(
            this.uiSegmentSrv.newSegment({
              type: 'value',
              value: '/^$' + variable.name + '$/',
              expandable: true,
            })
          );
        }
      }

      return segments;
    };
  }

  getTagsOrValues(segment: { type: string }, index: number) {
    if (segment.type === 'condition') {
      return Promise.resolve([this.uiSegmentSrv.newSegment('AND'), this.uiSegmentSrv.newSegment('OR')]);
    }

    if (segment.type === 'operator') {
      const nextValue = this.tagSegments[index + 1].value;
      if (/^\/.*\/$/.test(nextValue)) {
        return Promise.resolve(this.uiSegmentSrv.newOperators(['=~', '!~']));
      } else {
        return Promise.resolve(this.uiSegmentSrv.newOperators(['=', '!=', '<>', '<', '>']));
      }
    }

    let query, addTemplateVars;
    if (segment.type === 'key' || segment.type === 'plus-button') {
      query = this.queryBuilder.buildExploreQuery('TAG_KEYS');
      addTemplateVars = false;
    } else if (segment.type === 'value') {
      query = this.queryBuilder.buildExploreQuery('TAG_VALUES', this.tagSegments[index - 2].value);
      addTemplateVars = true;
    }

    return this.datasource
      .metricFindQuery(query)
      .then(this.transformToSegments(addTemplateVars))
      .then((results: any) => {
        if (segment.type === 'key') {
          results.splice(0, 0, angular.copy(this.removeTagFilterSegment));
        }
        return results;
      })
      .catch(this.handleQueryError.bind(this));
  }

  getFieldSegments() {
    const fieldsQuery = this.queryBuilder.buildExploreQuery('FIELDS');
    return this.datasource
      .metricFindQuery(fieldsQuery)
      .then(this.transformToSegments(false))
      .catch(this.handleQueryError);
  }

  tagSegmentUpdated(segment: { value: any; type: string; cssClass: string }, index: number) {
    this.tagSegments[index] = segment;

    // handle remove tag condition
    if (segment.value === this.removeTagFilterSegment.value) {
      this.tagSegments.splice(index, 3);
      if (this.tagSegments.length === 0) {
        this.tagSegments.push(this.uiSegmentSrv.newPlusButton());
      } else if (this.tagSegments.length > 2) {
        this.tagSegments.splice(Math.max(index - 1, 0), 1);
        if (this.tagSegments[this.tagSegments.length - 1].type !== 'plus-button') {
          this.tagSegments.push(this.uiSegmentSrv.newPlusButton());
        }
      }
    } else {
      if (segment.type === 'plus-button') {
        if (index > 2) {
          this.tagSegments.splice(index, 0, this.uiSegmentSrv.newCondition('AND'));
        }
        this.tagSegments.push(this.uiSegmentSrv.newOperator('='));
        this.tagSegments.push(this.uiSegmentSrv.newFake('select tag value', 'value', 'query-segment-value'));
        segment.type = 'key';
        segment.cssClass = 'query-segment-key';
      }

      if (index + 1 === this.tagSegments.length) {
        this.tagSegments.push(this.uiSegmentSrv.newPlusButton());
      }
    }

    this.rebuildTargetTagConditions();
  }

  rebuildTargetTagConditions() {
    const tags: any[] = [];
    let tagIndex = 0;
    let tagOperator: string | null = '';

    _.each(this.tagSegments, (segment2, index) => {
      if (segment2.type === 'key') {
        if (tags.length === 0) {
          tags.push({});
        }
        tags[tagIndex].key = segment2.value;
      } else if (segment2.type === 'value') {
        tagOperator = this.getTagValueOperator(segment2.value, tags[tagIndex].operator);
        if (tagOperator) {
          this.tagSegments[index - 1] = this.uiSegmentSrv.newOperator(tagOperator);
          tags[tagIndex].operator = tagOperator;
        }
        tags[tagIndex].value = segment2.value;
      } else if (segment2.type === 'condition') {
        tags.push({ condition: segment2.value });
        tagIndex += 1;
      } else if (segment2.type === 'operator') {
        tags[tagIndex].operator = segment2.value;
      }
    });

    this.target.tags = tags;
    this.panelCtrl.refresh();
  }

  getTagValueOperator(tagValue: string, tagOperator: string): string | null {
    if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) {
      return '=~';
    } else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) {
      return '=';
    }
    return null;
  }
}
