from __future__ import unicode_literals

import logging

from django.conf import settings
from elasticsearch import Elasticsearch


logger = logging.getLogger(__name__)


class ElasticsearchError(Exception):
    pass


class ElasticsearchClientError(ElasticsearchError):
    pass


class ElasticsearchResultListError(ElasticsearchError):
    pass


class ElasticsearchResultList(object):

    def __init__(self, user, event_types=None, sort='-@timestamp'):
        self.client = ElasticsearchClient()
        self.user = user
        self.event_types = event_types
        self.sort = sort

    def _get_events(self, from_, size):
        return self.client.get_user_events(self.user, self.event_types, from_=from_, size=size, sort=self.sort)

    def __len__(self):
        if not hasattr(self, 'total'):
            self.total = self._get_events(0, 1)['total']
        return self.total

    def __getitem__(self, key):
        if isinstance(key, slice):
            if key.step is not None and key.step != 1:
                raise ElasticsearchResultListError('ElasticsearchResultList can be iterated only with step 1')
            start = key.start if key.start is not None else 0
            events_and_total = self._get_events(start, key.stop - start)
        else:
            events_and_total = self._get_events(key, 1)
        self.total = events_and_total['total']
        return events_and_total['events']


class ElasticsearchClient(object):

    def __init__(self):
        self.client = self._get_client()

    def get_user_events(self, user, event_types=None, sort='-@timestamp', index='_all', from_=0, size=10):
        """
        Return events filtered for given user and total count of available for user events
        """
        sort = sort[1:] + ':desc' if sort.startswith('-') else sort + ':asc'
        body = self._get_search_body(user, event_types)
        search_results = self.client.search(index=index, body=body, from_=from_, size=size, sort=sort)
        return {
            'events': [r['_source'] for r in search_results['hits']['hits']],
            'total': search_results['hits']['total'],
        }

    def _get_elastisearch_settings(self):
        try:
            return settings.NODECONDUCTOR['ELASTICSEARCH']
        except (KeyError, AttributeError):
            raise ElasticsearchClientError(
                'Can not get elasticsearch settings. ELASTICSEARCH item in settings.NODECONDUCTOR has'
                'to be defined. Or enable dummy elasticsearch mode.')

    def _get_client(self):
        # TODO return dummy client here
        elasticsearch_settings = self._get_elastisearch_settings()
        path = '%(protocol)s://%(username)s:%(password)s@%(host)s:%(port)s' % elasticsearch_settings
        return Elasticsearch(
            [path],
            use_ssl=elasticsearch_settings.get('use_ssl', False),
            verify_certs=elasticsearch_settings.get('verify_certs', False),
        )

    def _get_permitted_objects_uuids(self, user):
        """
        Return list object available UUIDs for user
        """
        # XXX: this method has to be refactored, because it adds dependencies from iaas and structure apps
        from nodeconductor.structure import models as structure_models
        from nodeconductor.structure.filters import filter_queryset_for_user

        return {
            'user_uuid': [user.uuid.hex],
            'project_uuid': filter_queryset_for_user(
                structure_models.Project.objects.all(), user).values_list('uuid', flat=True),
            'project_group_uuid': filter_queryset_for_user(
                structure_models.ProjectGroup.objects.all(), user).values_list('uuid', flat=True),
            'customer_uuid': filter_queryset_for_user(
                structure_models.Customer.objects.all(), user).values_list('uuid', flat=True),
        }

    def _format_to_elasticsearch_field_filter(self, field_name, field_values):
        """
        Return string '<field_name>:("<field_value1>", "<field_value2>"...)'
        """
        return '%s:("%s")' % (field_name, '", "'.join(field_values))

    def _get_search_body(self, user, event_types=None):
        permitted_objects_uuids = self._get_permitted_objects_uuids(user)

        query = ' OR '.join([
            self._format_to_elasticsearch_field_filter(item, uuids)
            for item, uuids in permitted_objects_uuids.items()
        ])
        if event_types:
            query = '(' + query + ') AND ' + self._format_to_elasticsearch_field_filter('event_type', event_types)
        logger.debug('Getting elasticsearch results for user: "%s" with query: %s', user, query)

        return {"query": {"query_string": {"query": query}}}

@@ -420,32 +420,50 @@
 vent_types=None,
+ search_text=None,
  sort='-@timesta
@@ -600,16 +600,55 @@
 t = sort
+%0A        self.search_text = search_text
 %0A%0A    de
@@ -728,60 +728,180 @@
 nts(
-self.user, self.event_types, from_=from_, size=size,
+%0A            user=self.user,%0A            event_types=self.event_types,%0A            search_text=self.search_text,%0A            from_=from_,%0A            size=size,%0A           
  sor
@@ -911,16 +911,25 @@
 elf.sort
+%0A        
 )%0A%0A    d
@@ -1626,32 +1626,64 @@
 lient(object):%0A%0A
+    FTS_FIELDS = ('message', )%0A%0A
     def __init__
@@ -1752,24 +1752,37 @@
 user_events(
+%0A            
 self, user, 
@@ -1794,24 +1794,42 @@
 _types=None,
+ search_text=None,
  sort='-@tim
@@ -2108,32 +2108,45 @@
 ser, event_types
+, search_text
 )%0A        search
@@ -4419,16 +4419,34 @@
 pes=None
+, search_text=None
 ):%0A     
@@ -4514,16 +4514,62 @@
 s(user)%0A
+        # Create query for user-related events
 %0A       
@@ -4745,57 +4745,112 @@
     
-if
+query = '(' + query + ')'%0A        # Filter it by
  event
-_
+ 
 types
-:
 %0A
+  
       
-      query = '(' +
+if event_types:%0A           
  query +
  ') 
@@ -4845,19 +4845,19 @@
  query +
+=
  '
-)
  AND ' +
@@ -4927,16 +4927,209 @@
 _types)%0A
+        # Add FTS to query%0A        if search_text:%0A            for field in self.FTS_FIELDS:%0A                query += ' AND ' + self._format_to_elasticsearch_field_filter(field, %5Bsearch_text%5D)%0A
         
@@ -5217,17 +5217,16 @@
  query)%0A
-%0A
         

Add FTS support to elasticsearch client (nc-463)