import re

from importlib import import_module
from urllib.parse import urljoin

from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError
from django.urls import NoReverseMatch

from rest_framework import status
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.views import (
    APIView,
    exception_handler as original_exception_handler,
    set_rollback,
)

from normandy.base.api.utils import get_api_endpoints
from normandy.base.decorators import api_cache_control


class APIRootView(APIView):
    """
    An API root view that lists the urls that share it's version namespace.
    """

    _ignore_model_permissions = True
    exclude_from_schema = True
    urlconf = None

    @api_cache_control(max_age=settings.API_CACHE_TIME)
    def get(self, request, *args, **kwargs):
        ret = {}

        # Get the version namespace
        namespace = getattr(request.resolver_match, "namespace", "")
        for ns in namespace.split(":"):
            if re.match(r"v[0-9]+", ns, re.I):
                namespace = ns
                break

        if self.urlconf:
            urlconf = self.urlconf
        else:
            urlconf = import_module(settings.ROOT_URLCONF)
        endpoints = get_api_endpoints(urlconf.urlpatterns)

        for endpoint in sorted(endpoints, key=lambda e: e["pattern"].name):
            name = endpoint["pattern"].name
            if endpoint["method"] == "GET" and namespace in endpoint["namespace"].split(":"):
                allow_cdn = getattr(endpoint["pattern"], "allow_cdn", True)

                if not allow_cdn and settings.APP_SERVER_URL:
                    base = settings.APP_SERVER_URL
                else:
                    base = request.build_absolute_uri()

                try:
                    full_name = (
                        f'{endpoint["namespace"]}:{name}' if endpoint["namespace"] else name
                    )
                    path = reverse(full_name, *args, **kwargs)
                except NoReverseMatch:
                    continue

                full_url = urljoin(base, path)
                ret[name] = full_url

        return Response(ret)


def exception_handler(exc, context):
    """
    Returns the response that should be used for any given exception.

    Adds support the DRF default to also handle django.core.exceptions.ValidationError

    Any unhandled exceptions may return `None`, which will cause a 500 error
    to be raised.
    """
    response = original_exception_handler(exc, context)

    if response:
        return response

    elif isinstance(exc, DjangoValidationError):
        data = {"messages": exc.messages}
        set_rollback()
        return Response(data, status=status.HTTP_400_BAD_REQUEST)

    return None