# -*- coding: utf-8 -*-
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from permabots.models.base import PermabotsModel
from permabots.models import Bot, Response
from jinja2 import Environment
import requests
from django.conf.urls import url
import json
import logging
from permabots import validators
from rest_framework.status import is_success
from permabots import caching 
from permabots import utils

logger = logging.getLogger(__name__)


class AbstractParam(PermabotsModel):
    """
    Abstract parameter for :class:`Request <permabots.models.handler.Request>`
    """
    
    key = models.CharField(_('Key'), max_length=255, help_text=_("Name of the parameter"))
    value_template = models.CharField(_('Value template'), max_length=255, validators=[validators.validate_template], 
                                      help_text=_("Value template of the parameter. In jinja2 format. http://jinja.pocoo.org/"))
    
    class Meta:
        abstract = True
        verbose_name = _('Parameter')
        verbose_name_plural = _('Parameters')
        
    def __str__(self):
        return "(%s, %s)" % (self.key, self.value_template)
    
    def process(self, **context):
        """
        Render value_template of the parameter using context.
        
        :param context: Processing context
        """
        env = Environment(extensions=['jinja2_time.TimeExtension'])        
        value_template = env.from_string(self.value_template)
        return value_template.render(**context) 

@python_2_unicode_compatible
class Request(PermabotsModel):
    """
    HTTP Request to perform some processing when handling a message
    """
    url_template = models.CharField(_('Url template'), max_length=255, validators=[validators.validate_template], 
                                    help_text=_("Url to request. A jinja2 template. http://jinja.pocoo.org/"))
    GET, POST, PUT, PATCH, DELETE = ("Get", "Post", "Put", "Patch", "Delete")
    METHOD_CHOICES = (
        (GET, _("Get")),
        (POST, _("Post")),
        (PUT, _("Put")),
        (DELETE, _("Delete")),
        (PATCH, _("Patch")),
    )
    method = models.CharField(_("Method"), max_length=128, default=GET, choices=METHOD_CHOICES, help_text=_("Define Http method for the request"))
    data = models.TextField(null=True, blank=True, verbose_name=_("Data of the request"), help_text=_("Set POST/PUT/PATCH data in json format"),
                            validators=[validators.validate_template])
    
    class Meta:
        verbose_name = _('Request')
        verbose_name_plural = _('Requests')

    def __str__(self):
        return "%s(%s)" % (self.method, self.url_template)
    
    def _get_method(self):
        method = {self.GET: requests.get,
                  self.POST: requests.post,
                  self.PUT: requests.put,
                  self.PATCH: requests.patch,
                  self.DELETE: requests.delete}
        try:
            return method[self.method]
        except KeyError:
            logger.error("Method %s not valid" % self.method)
            return method[self.GET]
    
    def _url_params(self, **context):
        params = {}
        for param in self.url_parameters.all():
            params[param.key] = param.process(**context)
        return params
    
    def _header_params(self, **context):
        headers = {}
        for header in self.header_parameters.all():
            headers[header.key] = header.process(**context)
        return headers
    
    def data_required(self):
        return self.method != self.GET and self.method != self.DELETE
    
    def process(self, **context):
        """
        Process handler request. Before executing requests render templates with context
        
        :param context: Processing context
        :returns: Requests response `<http://docs.python-requests.org/en/master/api/#requests.Response>` _.
        """
        env = Environment(extensions=['jinja2_time.TimeExtension'])
        
        url_template = env.from_string(self.url_template)
        url = url_template.render(**context).replace(" ", "")
        logger.debug("Request %s generates url %s" % (self, url))        
        params = self._url_params(**context)
        logger.debug("Request %s generates params %s" % (self, params))
        headers = self._header_params(**context)
        logger.debug("Request %s generates header %s" % (self, headers))
        
        if self.data_required():
            data_template = env.from_string(self.data)
            data = data_template.render(**context)
            logger.debug("Request %s generates data %s" % (self, data))
            r = self._get_method()(url, data=json.loads(data), headers=headers, params=params)
        else:
            r = self._get_method()(url, headers=headers, params=params)

        return r
    
class UrlParam(AbstractParam):
    """
    Url Parameter associated to the request.
    """
    request = models.ForeignKey(Request, verbose_name=_('Request'), related_name="url_parameters",
                                help_text=_("Request which this Url Parameter is attached to"), on_delete=models.CASCADE)
    
    class Meta:
        verbose_name = _("Url Parameter")
        verbose_name_plural = _("Url Parameters")
        
class HeaderParam(AbstractParam):
    """
    Header Parameter associated to the request
    """
    request = models.ForeignKey(Request, verbose_name=_('Request'), related_name="header_parameters",
                                help_text=_("Request which this Url Parameter is attached to"), on_delete=models.CASCADE)
    
    class Meta:
        verbose_name = _("Header Parameter")
        verbose_name_plural = _("Header Parameters")

@python_2_unicode_compatible
class Handler(PermabotsModel):
    """
    Model to handler conversation message
    """
    bot = models.ForeignKey(Bot, verbose_name=_('Bot'), related_name="handlers",
                            help_text=_("Bot which Handler is attached to"), on_delete=models.CASCADE)
    name = models.CharField(_('Name'), max_length=100, db_index=True, help_text=_("Name for the handler"))
    pattern = models.CharField(_('Pattern'), max_length=255, validators=[validators.validate_pattern], 
                               help_text=_("""Regular expression the Handler will be triggered. 
                               Using https://docs.python.org/2/library/re.html#regular-expression-syntax"""))   
    request = models.OneToOneField(Request, null=True, blank=True, help_text=_("Request the Handler processes"),
                                   on_delete=models.SET_NULL)
    response = models.OneToOneField(Response, help_text=_("Template the handler uses to generate response"), on_delete=models.CASCADE)
    enabled = models.BooleanField(_('Enable'), default=True, help_text=_("Enable/disable handler"))
    source_states = models.ManyToManyField('State', verbose_name=_('Source States'), related_name='source_handlers', blank=True,
                                           help_text=_("Bot states the Handler needs to be to execute. Set none if any"))
    target_state = models.ForeignKey('State', verbose_name=_('Target State'), related_name='target_handlers', null=True, blank=True,
                                     help_text=_("This state will be set when handler ends processing"), on_delete=models.SET_NULL)
    priority = models.IntegerField(_('Priority'), default=0,
                                   help_text=_("Set priority execution. Higher value higher priority"))
    
    class Meta:
        verbose_name = _('Handler')
        verbose_name_plural = _('Handlers')
        ordering = ['-priority']

    def __str__(self):
        return "%s" % self.name
    
    def urlpattern(self):
        return url(self.pattern, self.process)
                
    def process(self, bot, message, service, state_context, **pattern_context):
        """
        Process conversation message.
        
        1. Generates context
            * service: name of integration service
            * state_context: historic dict of previous contexts. identified by state
            * pattern: url pattern dict
            * env: dict of environment variables associated to this bot
            * message: provider message
            * emoji: dict of emojis  use named notation with underscores `<http://apps.timwhitlock.info/emoji/tables/unicode>` _.
            
        2. Process request (if required)
        
        3. Generates response. Text and Keyboard
        
        4. Prepare target_state and context for updating chat&state info
        
        :param bot: Bot the handler belongs to
        :type Bot: :class:`Bot <permabots.models.bot.Bot>`
        :param message: Message from provider
        :param service: Identity integration
        :type service: string
        :param state_context: Previous contexts
        :type state_context: dict
        :param pattern_context: Dict variables obtained from handler pattern regular expression.
        :type pattern_context: dict
        :returns: Text and keyboard response, new state for the chat and context used.
        """
        env = {}
        for env_var in caching.get_or_set_related(bot, 'env_vars'):
            env.update(env_var.as_json())
        context = {'service': service,
                   'state_context': state_context,
                   'pattern': pattern_context,
                   'env': env,
                   'message': message.to_dict(),
                   'emoji': utils.create_emoji_context()}
        response_context = {}
        success = True
        if self.request:
            r = self.request.process(**context)
            logger.debug("Handler %s get request %s" % (self, r))        
            success = is_success(r.status_code)
            response_context['status'] = r.status_code
            try:
                response_context['data'] = r.json()
            except:
                response_context['data'] = {}
        context['response'] = response_context
        response_text, response_keyboard = self.response.process(**context)
        # update ChatState
        if self.target_state and success:
            context.pop('message', None)
            context.pop('env', None)
            context.pop('state_context', None)
            context.pop('service', None)
            context.pop('emoji', None)
            target_state = self.target_state
        else:
            target_state = None
            logger.warning("No target state for handler:%s for message %s" % 
                           (self, message))
        return response_text, response_keyboard, target_state, context
