import './add_graphite_func';
import './func_editor';

import { each, eachRight, map, remove } from 'lodash';
import GraphiteQuery, { GraphiteTagOperator } from './graphite_query';
import { QueryCtrl } from 'app/plugins/sdk';
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
import { auto } from 'angular';
import { TemplateSrv } from '@grafana/runtime';
import { dispatch } from 'app/store/store';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';

const GRAPHITE_TAG_OPERATORS = ['=', '!=', '=~', '!=~'];
const TAG_PREFIX = 'tag: ';

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

  queryModel: GraphiteQuery;
  segments: any[] = [];
  addTagSegments: any[] = [];
  removeTagValue: string;
  supportsTags = false;
  paused = false;

  // to avoid error flooding, these errors are shown only once per session
  private _tagsAutoCompleteErrorShown = false;
  private _metricAutoCompleteErrorShown = false;

  /** @ngInject */
  constructor(
    $scope: any,
    $injector: auto.IInjectorService,
    private uiSegmentSrv: any,
    private templateSrv: TemplateSrv,
    $timeout: any
  ) {
    super($scope, $injector);
    this.supportsTags = this.datasource.supportsTags;
    this.paused = false;
    this.target.target = this.target.target || '';

    this.datasource.waitForFuncDefsLoaded().then(() => {
      this.queryModel = new GraphiteQuery(this.datasource, this.target, templateSrv);
      this.buildSegments(false);
    });

    this.removeTagValue = '-- remove tag --';
  }

  parseTarget() {
    this.queryModel.parseTarget();
    this.buildSegments();
  }

  toggleEditorMode() {
    this.target.textEditor = !this.target.textEditor;
    this.parseTarget();
  }

  buildSegments(modifyLastSegment = true) {
    this.segments = map(this.queryModel.segments, (segment) => {
      return this.uiSegmentSrv.newSegment(segment);
    });

    const checkOtherSegmentsIndex = this.queryModel.checkOtherSegmentsIndex || 0;

    promiseToDigest(this.$scope)(this.checkOtherSegments(checkOtherSegmentsIndex, modifyLastSegment));

    if (this.queryModel.seriesByTagUsed) {
      this.fixTagSegments();
    }
  }

  addSelectMetricSegment() {
    this.queryModel.addSelectMetricSegment();
    this.segments.push(this.uiSegmentSrv.newSelectMetric());
  }

  checkOtherSegments(fromIndex: number, modifyLastSegment = true) {
    if (this.queryModel.segments.length === 1 && this.queryModel.segments[0].type === 'series-ref') {
      return Promise.resolve();
    }

    if (fromIndex === 0) {
      this.addSelectMetricSegment();
      return Promise.resolve();
    }

    const path = this.queryModel.getSegmentPathUpTo(fromIndex + 1);
    if (path === '') {
      return Promise.resolve();
    }

    return this.datasource
      .metricFindQuery(path)
      .then((segments: any) => {
        if (segments.length === 0) {
          if (path !== '' && modifyLastSegment) {
            this.queryModel.segments = this.queryModel.segments.splice(0, fromIndex);
            this.segments = this.segments.splice(0, fromIndex);
            this.addSelectMetricSegment();
          }
        } else if (segments[0].expandable) {
          if (this.segments.length === fromIndex) {
            this.addSelectMetricSegment();
          } else {
            return this.checkOtherSegments(fromIndex + 1);
          }
        }
      })
      .catch((err: any) => {
        this.handleMetricsAutoCompleteError(err);
      });
  }

  setSegmentFocus(segmentIndex: any) {
    each(this.segments, (segment, index) => {
      segment.focus = segmentIndex === index;
    });
  }

  getAltSegments(index: number, prefix: string) {
    let query = prefix && prefix.length > 0 ? '*' + prefix + '*' : '*';
    if (index > 0) {
      query = this.queryModel.getSegmentPathUpTo(index) + '.' + query;
    }
    const options = {
      range: this.panelCtrl.range,
      requestId: 'get-alt-segments',
    };

    return this.datasource
      .metricFindQuery(query, options)
      .then((segments: any[]) => {
        const altSegments = map(segments, (segment) => {
          return this.uiSegmentSrv.newSegment({
            value: segment.text,
            expandable: segment.expandable,
          });
        });

        if (index > 0 && altSegments.length === 0) {
          return altSegments;
        }

        // add query references
        if (index === 0) {
          eachRight(this.panelCtrl.panel.targets, (target) => {
            if (target.refId === this.queryModel.target.refId) {
              return;
            }

            altSegments.unshift(
              this.uiSegmentSrv.newSegment({
                type: 'series-ref',
                value: '#' + target.refId,
                expandable: false,
              })
            );
          });
        }

        // add template variables
        eachRight(this.templateSrv.getVariables(), (variable) => {
          altSegments.unshift(
            this.uiSegmentSrv.newSegment({
              type: 'template',
              value: '$' + variable.name,
              expandable: true,
            })
          );
        });

        // add wildcard option
        altSegments.unshift(this.uiSegmentSrv.newSegment('*'));

        if (this.supportsTags && index === 0) {
          this.removeTaggedEntry(altSegments);
          return this.addAltTagSegments(prefix, altSegments);
        } else {
          return altSegments;
        }
      })
      .catch((err: any): any[] => {
        this.handleMetricsAutoCompleteError(err);
        return [];
      });
  }

  addAltTagSegments(prefix: string, altSegments: any[]) {
    return this.getTagsAsSegments(prefix).then((tagSegments: any[]) => {
      tagSegments = map(tagSegments, (segment) => {
        segment.value = TAG_PREFIX + segment.value;
        return segment;
      });
      return altSegments.concat(...tagSegments);
    });
  }

  removeTaggedEntry(altSegments: any[]) {
    altSegments = remove(altSegments, (s) => s.value === '_tagged');
  }

  segmentValueChanged(segment: { type: string; value: string; expandable: any }, segmentIndex: number) {
    this.error = null;
    this.queryModel.updateSegmentValue(segment, segmentIndex);

    if (this.queryModel.functions.length > 0 && this.queryModel.functions[0].def.fake) {
      this.queryModel.functions = [];
    }

    if (segment.type === 'tag') {
      const tag = removeTagPrefix(segment.value);
      this.pause();
      this.addSeriesByTagFunc(tag);
      return null;
    }

    if (segment.expandable) {
      return promiseToDigest(this.$scope)(
        this.checkOtherSegments(segmentIndex + 1).then(() => {
          this.setSegmentFocus(segmentIndex + 1);
          this.targetChanged();
        })
      );
    } else {
      this.spliceSegments(segmentIndex + 1);
    }

    this.setSegmentFocus(segmentIndex + 1);
    this.targetChanged();

    return null;
  }

  spliceSegments(index: any) {
    this.segments = this.segments.splice(0, index);
    this.queryModel.segments = this.queryModel.segments.splice(0, index);
  }

  emptySegments() {
    this.queryModel.segments = [];
    this.segments = [];
  }

  targetTextChanged() {
    this.updateModelTarget();
    this.refresh();
  }

  updateModelTarget() {
    this.queryModel.updateModelTarget(this.panelCtrl.panel.targets);
  }

  targetChanged() {
    if (this.queryModel.error) {
      return;
    }

    const oldTarget = this.queryModel.target.target;
    this.updateModelTarget();

    if (this.queryModel.target !== oldTarget && !this.paused) {
      this.panelCtrl.refresh();
    }
  }

  addFunction(funcDef: any) {
    const newFunc = this.datasource.createFuncInstance(funcDef, {
      withDefaultParams: true,
    });
    newFunc.added = true;
    this.queryModel.addFunction(newFunc);
    this.smartlyHandleNewAliasByNode(newFunc);

    if (this.segments.length === 1 && this.segments[0].fake) {
      this.emptySegments();
    }

    if (!newFunc.params.length && newFunc.added) {
      this.targetChanged();
    }

    if (newFunc.def.name === 'seriesByTag') {
      this.parseTarget();
    }
  }

  removeFunction(func: any) {
    this.queryModel.removeFunction(func);
    this.targetChanged();
  }

  moveFunction(func: any, offset: any) {
    this.queryModel.moveFunction(func, offset);
    this.targetChanged();
  }

  addSeriesByTagFunc(tag: string) {
    const newFunc = this.datasource.createFuncInstance('seriesByTag', {
      withDefaultParams: false,
    });
    const tagParam = `${tag}=`;
    newFunc.params = [tagParam];
    this.queryModel.addFunction(newFunc);
    newFunc.added = true;

    this.emptySegments();
    this.targetChanged();
    this.parseTarget();
  }

  smartlyHandleNewAliasByNode(func: { def: { name: string }; params: number[]; added: boolean }) {
    if (func.def.name !== 'aliasByNode') {
      return;
    }

    for (let i = 0; i < this.segments.length; i++) {
      if (this.segments[i].value.indexOf('*') >= 0) {
        func.params[0] = i;
        func.added = false;
        this.targetChanged();
        return;
      }
    }
  }

  getAllTags() {
    return this.datasource.getTags().then((values: any[]) => {
      const altTags = map(values, 'text');
      altTags.splice(0, 0, this.removeTagValue);
      return mapToDropdownOptions(altTags);
    });
  }

  getTags(index: number, tagPrefix: any) {
    const tagExpressions = this.queryModel.renderTagExpressions(index);
    return this.datasource
      .getTagsAutoComplete(tagExpressions, tagPrefix)
      .then((values: any) => {
        const altTags = map(values, 'text');
        altTags.splice(0, 0, this.removeTagValue);
        return mapToDropdownOptions(altTags);
      })
      .catch((err: any) => {
        this.handleTagsAutoCompleteError(err);
      });
  }

  getTagsAsSegments(tagPrefix: string) {
    const tagExpressions = this.queryModel.renderTagExpressions();
    return this.datasource
      .getTagsAutoComplete(tagExpressions, tagPrefix)
      .then((values: any) => {
        return map(values, (val) => {
          return this.uiSegmentSrv.newSegment({
            value: val.text,
            type: 'tag',
            expandable: false,
          });
        });
      })
      .catch((err: any) => {
        this.handleTagsAutoCompleteError(err);
      });
  }

  getTagOperators() {
    return mapToDropdownOptions(GRAPHITE_TAG_OPERATORS);
  }

  getAllTagValues(tag: { key: any }) {
    const tagKey = tag.key;
    return this.datasource.getTagValues(tagKey).then((values: any[]) => {
      const altValues = map(values, 'text');
      return mapToDropdownOptions(altValues);
    });
  }

  getTagValues(tag: { key: any }, index: number, valuePrefix: any) {
    const tagExpressions = this.queryModel.renderTagExpressions(index);
    const tagKey = tag.key;
    return this.datasource.getTagValuesAutoComplete(tagExpressions, tagKey, valuePrefix).then((values: any[]) => {
      const altValues = map(values, 'text');
      // Add template variables as additional values
      eachRight(this.templateSrv.getVariables(), (variable) => {
        altValues.push('${' + variable.name + ':regex}');
      });
      return mapToDropdownOptions(altValues);
    });
  }

  tagChanged(tag: any, tagIndex: any) {
    this.queryModel.updateTag(tag, tagIndex);
    this.targetChanged();
  }

  addNewTag(segment: { value: any }) {
    const newTagKey = segment.value;
    const newTag = { key: newTagKey, operator: '=' as GraphiteTagOperator, value: '' };
    this.queryModel.addTag(newTag);
    this.targetChanged();
    this.fixTagSegments();
  }

  removeTag(index: any) {
    this.queryModel.removeTag(index);
    this.targetChanged();
  }

  fixTagSegments() {
    // Adding tag with the same name as just removed works incorrectly if single segment is used (instead of array)
    this.addTagSegments = [this.uiSegmentSrv.newPlusButton()];
  }

  showDelimiter(index: number) {
    return index !== this.queryModel.tags.length - 1;
  }

  pause() {
    this.paused = true;
  }

  unpause() {
    this.paused = false;
    this.panelCtrl.refresh();
  }

  getCollapsedText() {
    return this.target.target;
  }

  private handleTagsAutoCompleteError(error: Error): void {
    console.error(error);
    if (!this._tagsAutoCompleteErrorShown) {
      this._tagsAutoCompleteErrorShown = true;
      dispatch(notifyApp(createErrorNotification(`Fetching tags failed: ${error.message}.`)));
    }
  }

  private handleMetricsAutoCompleteError(error: Error): void {
    console.error(error);
    if (!this._metricAutoCompleteErrorShown) {
      this._metricAutoCompleteErrorShown = true;
      dispatch(notifyApp(createErrorNotification(`Fetching metrics failed: ${error.message}.`)));
    }
  }
}

function mapToDropdownOptions(results: any[]) {
  return map(results, (value) => {
    return { text: value, value: value };
  });
}

function removeTagPrefix(value: string): string {
  return value.replace(TAG_PREFIX, '');
}
