import sys
import types
import logging
from django import http
from django.conf import settings
from django.core import signals, urlresolvers
from django.core.handlers.base import BaseHandler
from django.core.exceptions import (
    PermissionDenied, SuspiciousOperation,
)
from django.http.multipartparser import MultiPartParserError
from django.utils import lru_cache
from django.utils.encoding import force_text
from django.views import debug
from ..gro_api.utils import system_layout

PATCH_SWAGGER = 'rest_framework_swagger' in settings.INSTALLED_APPS
if PATCH_SWAGGER:
    from rest_framework_swagger.urlparser import UrlParser

django_handlers_logger = logging.getLogger('django.request')

class FakeURLConfModule:
    def __init__(self, urls):
        self.urlpatterns = urls

@lru_cache.lru_cache(maxsize=None)
def inner_get_resolver(urlconf, layout):
    # Loading this upon module import causes problems, so we're going to be
    # lazy about it
    from ..gro_api.urls import get_current_urls

    return urlresolvers.RegexURLResolver(
        r'^/', FakeURLConfModule(get_current_urls())
    )

def outer_get_resolver(urlconf):
    return inner_get_resolver(urlconf, system_layout.current_value)

# Monkey patching `django.core.urlresolvers.get_resolver` doesn't completely
# solve the problem in Django 1.8.3 because
# `django.core.handlers.base.BaseHandler` creates a new
# `django.core.urlresolvers.RegexURLResolver` on every request. This is
# addressed by ticket #14200 (https://code.djangoproject.com/ticket/14200).
# A patch for this problem has been written and accepted, and should appear in
# the next Django release. Until then, we essentially apply the accepted patch
# ourselves by monkey patching `BaseHandler.get_response`.
def new_get_response(self, request):
    # Setup default url resolver for this thread, this code is outside
    # the try/except so we don't get a spurious "unbound local
    # variable" exception in the event an exception is raised before
    # resolver is set
    urlconf = settings.ROOT_URLCONF
    urlresolvers.set_urlconf(urlconf)
    resolver = urlresolvers.get_resolver(urlconf)
    try:
        response = None
        # Apply request middleware
        for middleware_method in self._request_middleware:
            response = middleware_method(request)
            if response:
                break

        if response is None:
            if hasattr(request, 'urlconf'):
                # Reset url resolver with a custom urlconf.
                urlconf = request.urlconf
                urlresolvers.set_urlconf(urlconf)
                resolver = urlresolvers.get_resolver(urlconf)

            resolver_match = resolver.resolve(request.path_info)
            callback, callback_args, callback_kwargs = resolver_match
            request.resolver_match = resolver_match

            # Apply view middleware
            for middleware_method in self._view_middleware:
                response = middleware_method(request, callback, callback_args, callback_kwargs)
                if response:
                    break

        if response is None:
            wrapped_callback = self.make_view_atomic(callback)
            try:
                response = wrapped_callback(request, *callback_args, **callback_kwargs)
            except Exception as e:
                # If the view raised an exception, run it through exception
                # middleware, and if the exception middleware returns a
                # response, use that. Otherwise, reraise the exception.
                for middleware_method in self._exception_middleware:
                    response = middleware_method(request, e)
                    if response:
                        break
                if response is None:
                    raise

        # Complain if the view returned None (a common error).
        if response is None:
            if isinstance(callback, types.FunctionType):    # FBV
                view_name = callback.__name__
            else:                                           # CBV
                view_name = callback.__class__.__name__ + '.__call__'
            raise ValueError("The view %s.%s didn't return an HttpResponse object. It returned None instead."
                             % (callback.__module__, view_name))

        # If the response supports deferred rendering, apply template
        # response middleware and then render the response
        if hasattr(response, 'render') and callable(response.render):
            for middleware_method in self._template_response_middleware:
                response = middleware_method(request, response)
                # Complain if the template response middleware returned None (a common error).
                if response is None:
                    raise ValueError(
                        "%s.process_template_response didn't return an "
                        "HttpResponse object. It returned None instead."
                        % (middleware_method.__self__.__class__.__name__))
            response = response.render()

    except http.Http404 as e:
        django_handlers_logger.warning('Not Found: %s', request.path,
                    extra={
                        'status_code': 404,
                        'request': request
                    })
        if settings.DEBUG:
            response = debug.technical_404_response(request, e)
        else:
            response = self.get_exception_response(request, resolver, 404)

    except PermissionDenied:
        django_handlers_logger.warning(
            'Forbidden (Permission denied): %s', request.path,
            extra={
                'status_code': 403,
                'request': request
            })
        response = self.get_exception_response(request, resolver, 403)

    except MultiPartParserError:
        django_handlers_logger.warning(
            'Bad request (Unable to parse request body): %s', request.path,
            extra={
                'status_code': 400,
                'request': request
            })
        response = self.get_exception_response(request, resolver, 400)

    except SuspiciousOperation as e:
        # The request logger receives events for any problematic request
        # The security logger receives events for all SuspiciousOperations
        security_logger = logging.getLogger('django.security.%s' %
                        e.__class__.__name__)
        security_logger.error(
            force_text(e),
            extra={
                'status_code': 400,
                'request': request
            })
        if settings.DEBUG:
            return debug.technical_500_response(request, *sys.exc_info(), status_code=400)

        response = self.get_exception_response(request, resolver, 400)

    except SystemExit:
        # Allow sys.exit() to actually exit. See tickets #1023 and #4701
        raise

    except:  # Handle everything else.
        # Get the exception info now, in case another exception is thrown later.
        signals.got_request_exception.send(sender=self.__class__, request=request)
        response = self.handle_uncaught_exception(request, resolver, sys.exc_info())

    try:
        # Apply response middleware, regardless of the response
        for middleware_method in self._response_middleware:
            response = middleware_method(request, response)
            # Complain if the response middleware returned None (a common error).
            if response is None:
                raise ValueError(
                    "%s.process_response didn't return an "
                    "HttpResponse object. It returned None instead."
                    % (middleware_method.__self__.__class__.__name__))
        response = self.apply_response_fixes(request, response)
    except:  # Any exception should be gathered and handled
        signals.got_request_exception.send(sender=self.__class__, request=request)
        response = self.handle_uncaught_exception(request, resolver, sys.exc_info())

    response._closable_objects.append(request)

    return response

if PATCH_SWAGGER:
    def new_get_apis(self, patterns=None, urlconf=None, filter_path=None, exclude_namespaces=[]):
        # Loading this upon module import causes problems, so we're going to be
        # lazy about it
        from ..gro_api.urls import get_current_urls

        real_urlconf = FakeURLConfModule(get_current_urls())
        return self.old_get_apis(
            patterns, real_urlconf, filter_path, exclude_namespaces
        )

def patch_resolvers():
    urlresolvers.get_resolver = outer_get_resolver
    BaseHandler.get_response = new_get_response
    if PATCH_SWAGGER and not hasattr(UrlParser, 'old_get_apis'):
        UrlParser.old_get_apis = UrlParser.get_apis
        UrlParser.get_apis = new_get_apis