define([
  'angular',
  'lodash',
  'config',
  'kbn',
  'moment'
],
function (angular, _, config, kbn, moment) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('ElasticDatasource', function($q, $http, templateSrv) {

    function ElasticDatasource(datasource) {
      this.type = 'elastic';
      this.basicAuth = datasource.basicAuth;
      this.url = datasource.url;
      this.name = datasource.name;
      this.index = datasource.index;
      this.grafanaDB = datasource.grafanaDB;
      this.searchMaxResults = config.search.max_results || 20;

      this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp;
      this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl;

      this.annotationEditorSrc = 'app/features/elasticsearch/partials/annotations.editor.html';
      this.supportAnnotations = true;
      this.supportMetrics = false;
    }

    ElasticDatasource.prototype._request = function(method, url, index, data) {
      var options = {
        url: this.url + "/" + index + url,
        method: method,
        data: data
      };

      if (this.basicAuth) {
        options.withCredentials = true;
        options.headers = {
          "Authorization": "Basic " + this.basicAuth
        };
      }

      return $http(options);
    };

    ElasticDatasource.prototype._get = function(url) {
      return this._request('GET', url, this.index)
        .then(function(results) {
          return results.data;
        });
    };

    ElasticDatasource.prototype._post = function(url, data) {
      return this._request('POST', url, this.index, data)
        .then(function(results) {
          return results.data;
        });
    };

    ElasticDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
      var range = {};
      var timeField = annotation.timeField || '@timestamp';
      var queryString = annotation.query || '*';
      var tagsField = annotation.tagsField || 'tags';
      var titleField = annotation.titleField || 'desc';
      var textField = annotation.textField || null;

      range[annotation.timeField]= {
        from: rangeUnparsed.from,
        to: rangeUnparsed.to,
      };

      var queryInterpolated = templateSrv.replace(queryString);
      var filter = { "bool": { "must": [{ "range": range }] } };
      var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
      var data = {
        "fields": [timeField, "_source"],
        "query" : { "filtered": { "query" : query, "filter": filter } },
        "size": 100
      };

      return this._request('POST', '/_search', annotation.index, data).then(function(results) {
        var list = [];
        var hits = results.data.hits.hits;

        var getFieldFromSource = function(source, fieldName) {
          if (!fieldName) { return; }

          var fieldNames = fieldName.split('.');
          var fieldValue = source;

          for (var i = 0; i < fieldNames.length; i++) {
            fieldValue = fieldValue[fieldNames[i]];
            if (!fieldValue) {
              console.log('could not find field in annotatation: ', fieldName);
              return '';
            }
          }

          if (_.isArray(fieldValue)) {
            fieldValue = fieldValue.join(', ');
          }
          return fieldValue;
        };

        for (var i = 0; i < hits.length; i++) {
          var source = hits[i]._source;
          var fields = hits[i].fields;
          var time = source[timeField];

          if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
            time = fields[timeField];
          }

          var event = {
            annotation: annotation,
            time: moment.utc(time).valueOf(),
            title: getFieldFromSource(source, titleField),
            tags: getFieldFromSource(source, tagsField),
            text: getFieldFromSource(source, textField)
          };

          list.push(event);
        }
        return list;
      });
    };

    ElasticDatasource.prototype._getDashboardWithSlug = function(id) {
      return this._get('/dashboard/' + kbn.slugifyForUrl(id))
        .then(function(result) {
          return angular.fromJson(result._source.dashboard);
        }, function() {
          throw "Dashboard not found";
        });
    };

    ElasticDatasource.prototype.getDashboard = function(id, isTemp) {
      var url = '/dashboard/' + id;
      if (isTemp) { url = '/temp/' + id; }

      var self = this;
      return this._get(url)
        .then(function(result) {
          return angular.fromJson(result._source.dashboard);
        }, function(data) {
          if(data.status === 0) {
            throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
          } else {
            // backward compatible fallback
            return self._getDashboardWithSlug(id);
          }
        });
    };

    ElasticDatasource.prototype.saveDashboard = function(dashboard) {
      var title = dashboard.title;
      var temp = dashboard.temp;
      if (temp) { delete dashboard.temp; }

      var data = {
        user: 'guest',
        group: 'guest',
        title: title,
        tags: dashboard.tags,
        dashboard: angular.toJson(dashboard)
      };

      if (temp) {
        return this._saveTempDashboard(data);
      }
      else {

        var id = encodeURIComponent(kbn.slugifyForUrl(title));
        var self = this;

        return this._request('PUT', '/dashboard/' + id, this.index, data)
          .then(function(results) {
            self._removeUnslugifiedDashboard(results, title, id);
            return { title: title, url: '/dashboard/db/' + id };
          }, function() {
            throw 'Failed to save to elasticsearch';
          });
      }
    };

    ElasticDatasource.prototype._removeUnslugifiedDashboard = function(saveResult, title, id) {
      if (saveResult.statusText !== 'Created') { return; }
      if (title === id) { return; }

      var self = this;
      this._get('/dashboard/' + title).then(function() {
        self.deleteDashboard(title);
      });
    };

    ElasticDatasource.prototype._saveTempDashboard = function(data) {
      return this._request('POST', '/temp/?ttl=' + this.saveTempTTL, this.index, data)
        .then(function(result) {

          var baseUrl = window.location.href.replace(window.location.hash,'');
          var url = baseUrl + "#dashboard/temp/" + result.data._id;

          return { title: data.title, url: url };

        }, function(err) {
          throw "Failed to save to temp dashboard to elasticsearch " + err.data;
        });
    };

    ElasticDatasource.prototype.deleteDashboard = function(id) {
      return this._request('DELETE', '/dashboard/' + id, this.index)
        .then(function(result) {
          return result.data._id;
        }, function(err) {
          throw err.data;
        });
    };

    ElasticDatasource.prototype.searchDashboards = function(queryString) {
      var endsInOpen = function(string, opener, closer) {
        var character;
        var count = 0;
        for (var i = 0, len = string.length; i < len; i++) {
          character = string[i];

          if (character === opener) {
            count++;
          } else if (character === closer) {
            count--;
          }
        }

        return count > 0;
      };

      var tagsOnly = queryString.indexOf('tags!:') === 0;
      if (tagsOnly) {
        var tagsQuery = queryString.substring(6, queryString.length);
        queryString = 'tags:' + tagsQuery + '*';
      }
      else {
        if (queryString.length === 0) {
          queryString = 'title:';
        }

        // make this a partial search if we're not in some reserved portion of the language,  comments on conditionals, in order:
        // 1. ends in reserved character, boosting, boolean operator ( -foo)
        // 2. typing a reserved word like AND, OR, NOT
        // 3. open parens (groupiing)
        // 4. open " (term phrase)
        // 5. open [ (range)
        // 6. open { (range)
        // see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax
        if (!queryString.match(/(\*|\]|}|~|\)|"|^\d+|\s[\-+]\w+)$/) &&
            !queryString.match(/[A-Z]$/) &&
            !endsInOpen(queryString, '(', ')') &&
            !endsInOpen(queryString, '"', '"') &&
            !endsInOpen(queryString, '[', ']') && !endsInOpen(queryString, '[', '}') &&
            !endsInOpen(queryString, '{', ']') && !endsInOpen(queryString, '{', '}')
        ){
          queryString += '*';
        }
      }

      var query = {
        query: { query_string: { query: queryString } },
        facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
        size: this.searchMaxResults,
        sort: ["_uid"]
      };

      return this._post('/dashboard/_search', query)
        .then(function(results) {
          if(_.isUndefined(results.hits)) {
            return { dashboards: [], tags: [] };
          }

          var resultsHits = results.hits.hits;
          var displayHits = { dashboards: [], tags: results.facets.tags.terms || [] };

          for (var i = 0, len = resultsHits.length; i < len; i++) {
            var hit = resultsHits[i];
            displayHits.dashboards.push({
              id: hit._id,
              title: hit._source.title,
              tags: hit._source.tags
            });
          }

          displayHits.tagsOnly = tagsOnly;
          return displayHits;
        });
    };

    return ElasticDatasource;

  });

});
