from __future__ import absolute_import, division, print_function, unicode_literals

import redis
import redis.connection
import pickle
import logging
import traceback
import json

from django import db

from .__about__ import __version__
from . import connection
from .pool import pool

# Logger.
logger = logging.getLogger(__name__)


class RedisObserverEventHandler(object):
    """
    Query observer handler that receives events via Redis.
    """

    def __call__(self):
        """
        Entry point.
        """

        # Establish a connection with Redis server.
        self._redis = redis.StrictRedis(**connection.get_redis_settings())
        self._pubsub = self._redis.pubsub(ignore_subscribe_messages=True)
        self._pubsub.subscribe(connection.QUERYOBSERVER_REDIS_CHANNEL)

        while self._pubsub.subscribed:
            event = self._pubsub.get_message(ignore_subscribe_messages=True, timeout=0.1)
            if not event:
                continue

            # Events are assumed to be pickled data.
            try:
                event = pickle.loads(event['data'])
            except ValueError:
                logger.error("Ignoring received malformed event '{}'.", event['data'][:20])
                continue

            # Handle event.
            try:
                event_name = event.pop('event')
                handler = getattr(self, 'event_%s' % event_name)
            except AttributeError:
                logger.error("Ignoring unimplemented event '{}'.", event_name)
                continue
            except KeyError:
                logger.error("Ignoring received malformed event '{}'.", event)
                continue

            try:
                handler(**event)
            except:
                logger.error("Unhandled exception while executing event '{}'.", event_name)
                logger.error(traceback.format_exc())
            finally:
                db.close_old_connections()

        self._pubsub.close()

    def shutdown(self):
        self._pubsub.unsubscribe()
        self._pubsub.punsubscribe()

    def event_table_insert(self, table):
        pool.notify_update(table)

    def event_table_update(self, table):
        pool.notify_update(table)

    def event_table_remove(self, table):
        pool.notify_update(table)

    def event_subscriber_gone(self, subscriber):
        pool.remove_subscriber(subscriber)


class WSGIObserverCommandHandler(object):
    """
    A WSGI-based RPC server for the query observer API.
    """

    def __call__(self, environ, start_response):
        """
        Handles an incoming RPC request.
        """

        content_length = int(environ['CONTENT_LENGTH'])

        try:
            request = pickle.loads(environ['wsgi.input'].read(content_length))
            if not isinstance(request, dict):
                raise ValueError

            command = request.pop('command')
            handler = getattr(self, 'command_%s' % command)
        except (KeyError, ValueError, AttributeError, EOFError):
            start_response(b'400 Bad Request', [(b'Content-Type', b'text/json')])
            return [json.dumps({'error': "Bad request."})]

        try:
            response = handler(**request)
            start_response(b'200 OK', [(b'Content-Type', b'text/json')])
            return [json.dumps(response)]
        except TypeError:
            logger.exception("Observer request failed with bad request error.")
            start_response(b'400 Bad Request', [(b'Content-Type', b'text/json')])
            return [json.dumps({'error': "Bad request."})]
        except:
            logger.error("Unhandled exception while executing command '{}'.", command)
            logger.error(traceback.format_exc())

            start_response(b'500 Internal Server Error', [(b'Content-Type', b'text/json')])
            return [json.dumps({'error': "Internal server error."})]
        finally:
            db.close_old_connections()

    def command_status(self):
        """
        Return some status information about the observer pool.
        """

        return {
            'version': __version__,
            'backend': '{}.{}'.format(
                pool.backend.__class__.__module__,
                pool.backend.__class__.__name__
            ),
            'statistics': pool.statistics,
        }

    def command_stop_all(self):
        """
        Stops all observers.
        """

        pool.stop_all()

    def command_create_observer(self, request, subscriber):
        """
        Starts observing a specific viewset.

        :param request: The `queryobservers.request.Request` to observe
        :param subscriber: Subscriber channel name
        :return: Serialized current query results
        """

        observer = pool.observe_viewset(request, subscriber)
        return {
            'observer': observer.id,
            'items': observer.evaluate(),
        }

    def command_unsubscribe_observer(self, observer, subscriber):
        """
        Unsubscribes a specific subscriber from an observer.

        :param observer: Query observer identifier
        :param subscriber: Subscriber channel name
        """

        pool.unobserve_viewset(observer, subscriber)

@@ -194,16 +194,60 @@
 mport db
+%0Afrom django.utils.encoding import force_str
 %0A%0Afrom .
@@ -2666,32 +2666,277 @@
 t.%0A        %22%22%22%0A%0A
+        def respond(status, content):%0A            %22%22%22Response generation helper.%22%22%22%0A            headers = %5B(str('Content-Type'), str('text/json'))%5D%0A            start_response(force_str(status), headers)%0A            return %5Bjson.dumps(content)%5D%0A%0A
         content_
@@ -3330,32 +3330,31 @@
         
-start_
+return 
 respon
-se(b
+d(
 '400 Bad
@@ -3368,74 +3368,8 @@
 t', 
-%5B(b'Content-Type', b'text/json')%5D)%0A            return %5Bjson.dumps(
 %7B'er
@@ -3390,17 +3390,16 @@
 uest.%22%7D)
-%5D
 %0A%0A      
@@ -3459,32 +3459,31 @@
         
-start_
+return 
 respon
-se(b
+d(
 '200 OK'
@@ -3488,74 +3488,8 @@
 K', 
-%5B(b'Content-Type', b'text/json')%5D)%0A            return %5Bjson.dumps(
 resp
@@ -3493,17 +3493,16 @@
 esponse)
-%5D
 %0A       
@@ -3612,32 +3612,31 @@
         
-start_
+return 
 respon
-se(b
+d(
 '400 Bad
@@ -3650,74 +3650,8 @@
 t', 
-%5B(b'Content-Type', b'text/json')%5D)%0A            return %5Bjson.dumps(
 %7B'er
@@ -3668,25 +3668,24 @@
  request.%22%7D)
-%5D
 %0A        exc
@@ -3842,24 +3842,23 @@
     
-start_
+return 
 respon
-se(b
+d(
 '500
@@ -3886,74 +3886,8 @@
 r', 
-%5B(b'Content-Type', b'text/json')%5D)%0A            return %5Bjson.dumps(
 %7B'er
@@ -3918,17 +3918,16 @@
 rror.%22%7D)
-%5D
 %0A       

Fix response generation on Python 3