import logging
from .storage import Storage
from google.cloud import datastore
import datetime
from shortuuid import ShortUUID
from operator import itemgetter


class GoogleCloudDatastore(Storage):

    def __init__(self, namespace=None):
        self._logger = logging.getLogger("flask-blogging")
        self._client = datastore.Client(namespace=namespace)

    def _get_new_post_id(self):
        key = self._client.key('PostIDCounter', 'Counter')
        query = self._client.get(key)

        if query:
            counter = dict(query)
        else:
            counter = None

        if counter:
            counter = counter["value"]+1
            key = self._client.key('PostIDCounter', 'Counter')
            task = self._client.get(key)
            task['value'] = counter
            self._client.put(task)

            return int(counter)
        else:
            # Create a new counter
            key = self._client.key('PostIDCounter', 'Counter')
            counter = datastore.Entity(key=key)
            counter.update({
                    'value': 1,
            })
            self._client.put(counter)
            return 1

    def save_post(self, title, text, user_id, tags, draft=False,
                  post_date=None, last_modified_date=None, meta_data=None,
                  post_id=None):
        if post_id is not None:
            update_op = True
        else:
            update_op = False

        post_id = post_id or self._get_new_post_id()
        current_datetime = datetime.datetime.utcnow()
        post_date = post_date or current_datetime
        last_modified_date = last_modified_date or current_datetime
        tags = self.normalize_tags(tags)
        draft = True if draft else False

        if not update_op:
            key = self._client.key('Post', int(post_id))
            post = datastore.Entity(key=key, exclude_from_indexes=['text'])
            post.update({
                    'title': title,
                    'text': text,
                    'user_id': user_id,
                    'tags': tags or [],
                    'draft': draft,
                    'post_date': post_date,
                    'last_modified_date': last_modified_date,
                    'meta_data': meta_data,
                    'post_id': int(post_id)
            })
            self._client.put(post)
            return post_id
        else:
            key = self._client.key('Post', int(post_id))
            post = self._client.get(key)
            if not post:
                post_id = self._get_new_post_id()
                key = self._client.key('Post', int(post_id))
                post = datastore.Entity(key=key, exclude_from_indexes=['text'])
            post.update({
                    'title': title,
                    'text': text,
                    'user_id': user_id,
                    'tags': tags or [],
                    'draft': draft,
                    'post_date': post_date,
                    'last_modified_date': last_modified_date,
                    'meta_data': meta_data,
                    'post_id': int(post_id)
            })
            self._client.put(post)
            return int(post_id)

    def _filter_posts_by_tag(self, tag):
        if not tag:
            return []
        else:
            query = self._client.query(kind='Post')
            query.projection = ['post_id', 'tags']
            proj_result = list(query.fetch())

            proj_result = [dict(entity) for entity in proj_result]
            ids = set()

            for entity in proj_result:
                if entity["tags"] == tag:
                    ids.add(entity["post_id"])

            return list(ids)

    def get_posts(self, count=10, offset=0, recent=True, tag=None,
                  user_id=None, include_draft=False):
        """TODO: implement cursors support, if it will be needed.
           But for the regular blog, it is overhead and
           cost savings are minimal.
        """
        query = self._client.query(kind='Post')

        if tag:
            norm_tag = self.normalize_tag(tag)
            posts_ids = self._filter_posts_by_tag(norm_tag)

            if posts_ids:
                keys = [self._client.key('Post', id) for id in posts_ids]
                posts = self._client.get_multi(keys)
            else:
                posts = []
        else:
            if user_id:
                query.add_filter('user_id', '=', user_id)
            if include_draft:
                query.add_filter('draft', '=', include_draft)
            if recent:
                query.order = ['-post_date']
            posts = list(query.fetch(offset=offset, limit=count))

        if not posts:
            return []

        res = []
        for post in posts:
            p = dict(post)
            res.append(p)

        if tag and recent:
            res = sorted(res, key=itemgetter('post_date'), reverse=True)
        elif tag and not recent:
            res = sorted(res, key=itemgetter('post_date'))

        if tag:
            res = res[offset:offset+count]

        return res

    def count_posts(self, tag=None, user_id=None, include_draft=False):
        query = self._client.query(kind='Post')

        if tag:
            norm_tag = self.normalize_tag(tag)
            query.add_filter('tags', '=', norm_tag)
        if user_id:
            query.add_filter('user_id', '=', user_id)
        if include_draft:
            query.add_filter('draft', '=', include_draft)

        posts = list(query.fetch())
        result = len(posts)

        return result

    def get_post_by_id(self, post_id):
        if post_id:
            query = self._client.query(kind='Post')
            query.add_filter('post_id', '=', int(post_id))
            post = list(query.fetch())

            if post:
                res = dict(post[0])
                return res

        return None

    def delete_post(self, post_id):
        if post_id:
            key = self._client.key('Post', int(post_id))

            try:
                self._client.delete(key)
            except Exception as ex:
                self._logger.error(str(ex))
                return False

            return True
        else:
            return False