from functools import wraps
import json
import re
import logging

from requests import Session as RequestSession

from django.conf import settings
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
from django.template import Template, Context
from django.utils.decorators import available_attrs
from django.views.generic.detail import SingleObjectMixin
from django.contrib.contenttypes.models import ContentType

from passerelle.base.context_processors import template_vars
from passerelle.base.models import ApiUser, AccessRight
from passerelle.base.signature import check_query, check_url

from .jsonresponse import to_json

def get_template_vars():
    """
    returns the template vars as dict, to be used in apps code
    """
    from django.http import HttpRequest
    return template_vars(HttpRequest())

def render_template_vars(value):
    """
    renders the template vars in a string
    """
    template = Template(value)
    return template.render(Context(get_template_vars()))


def response_for_json(request, data):
    response = HttpResponse(content_type='application/json')
    json_str = json.dumps(data)
    for variable in ('jsonpCallback', 'callback'):
        if variable in request.GET:
            identifier = request.GET[variable]
            if not re.match(r'^[$A-Za-z_][0-9A-Za-z_$]*$', identifier):
                return HttpResponseBadRequest('invalid JSONP callback name')
            json_str = '%s(%s);' % (identifier, json_str)
            break
    response.write(json_str)
    return response


def get_request_users(request):
    users = []

    users.extend(ApiUser.objects.filter(keytype=''))

    if 'orig' in request.GET and 'signature' in request.GET:
        orig = request.GET['orig']
        query = request.META['QUERY_STRING']
        signature_users = ApiUser.objects.filter(keytype='SIGN', username=orig)
        for signature_user in signature_users:
            if check_query(query, signature_user.key):
                users.append(signature_user)

    elif 'apikey' in request.GET:
        users.extend(ApiUser.objects.filter(keytype='API',
            key=request.GET['apikey']))

    elif request.META.has_key('HTTP_AUTHORIZATION'):
        (scheme, param) = request.META['HTTP_AUTHORIZATION'].split(' ',1)
        if scheme.lower() == 'basic':
            username, password = param.strip().decode('base64').split(':',1)
            users.extend(ApiUser.objects.filter(keytype='SIGN',
                    username=username, key=password))

    def ip_match(ip, match):
        if not ip:
            return True
        if ip == match:
            return True
        return False

    users = [x for x in users if ip_match(x.ipsource, request.META.get('REMOTE_ADDR'))]
    return users

def get_trusted_services():
    '''
    All services in settings.KNOWN_SERVICES are "trusted"
    '''
    trusted_services = []
    for service_type in getattr(settings, 'KNOWN_SERVICES', {}):
        for slug, service in settings.KNOWN_SERVICES[service_type].iteritems():
            if service.get('secret') and service.get('verif_orig'):
                trusted_service = service.copy()
                trusted_service['service_type'] = service_type
                trusted_service['slug'] = slug
                trusted_services.append(trusted_service)
    return trusted_services


def is_trusted(request):
    '''
    True if query-string is signed by a trusted service (see get_trusted_services() above)
    '''
    if not request.GET.get('orig') or not request.GET.get('signature'):
        return False
    full_path = request.get_full_path()
    for service in get_trusted_services():
        if (service.get('verif_orig') == request.GET['orig']
                and service.get('secret')
                and check_url(full_path, service['secret'])):
            return True
    return False


def is_authorized(request, obj, perm):
    if is_trusted(request):
        return True
    resource_type = ContentType.objects.get_for_model(obj)
    rights = AccessRight.objects.filter(resource_type=resource_type,
            resource_pk=obj.id, codename=perm)
    users = [x.apiuser for x in rights]
    return set(users).intersection(get_request_users(request))


def protected_api(perm):
    def decorator(view_func):
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(instance, request, *args, **kwargs):
            if not isinstance(instance, SingleObjectMixin):
                raise Exception("protected_api must be applied on a method of a class based view")
            obj = instance.get_object()
            if not is_authorized(request, obj, perm):
                raise PermissionDenied()
            return view_func(instance, request, *args, **kwargs)
        return _wrapped_view
    return decorator


# Wrapper around requests.Session
# logging requests input and output data

class LoggedRequest(RequestSession):

    def __init__(self, *args, **kwargs):
        self.logger = kwargs.pop('logger')
        super(LoggedRequest, self).__init__(*args, **kwargs)

    def request(self, method, url, **kwargs):
        params = kwargs.get('params', '')
        self.logger.info('%s %s %s' % (method, url, params),
            extra={'requests_url': url}
        )

        response = super(LoggedRequest, self).request(method, url, **kwargs)

        self.logger.debug('Request Headers: {}'.format(''.join([
            '%s: %s | ' % (k,v) for k,v in response.request.headers.items()
        ])))
        if response.request.body:
            self.logger.info('Request Payload: %r' %(response.request.body),
                extra={'requests_request_payload': '%r' %response.request.body})
        self.logger.info('Status code: %r' %(response.status_code),
            extra={'requests_response_status': response.status_code})
        resp_headers = ''.join([
            '%s: %s | ' % (k,v) for k,v in response.headers.items()
        ])
        self.logger.debug('Response Headers: %r' %resp_headers, extra={
            'requests_response_headers': resp_headers})
        content = response.content[:getattr(settings,
            'REQUESTS_RESPONSE_CONTENT_MAX_LENGTH',5000)]
        self.logger.debug('Response Content: %r' % content,
            extra={'requests_response_content': content})

        return response
