from abc import ABCMeta
import re

from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest
from py2swagger.introspector import BaseDocstringIntrospector
from py2swagger.utils import get_decorators, OrderedDict, load_class, clean_parameters, flatten
from rest_framework.views import get_view_name
from rest_framework import status

from .authentication import get_authentication_introspectors
from .filter import get_filter_introspectors
from .pagination import get_pagination_introspector
from .serializer import SerializerIntrospector


class BaseMethodIntrospector(BaseDocstringIntrospector):
    """
    Base DjangoRestFramework method introspector
    """
    __metaclass__ = ABCMeta

    DEFAULT_STATUS_CODE = status.HTTP_200_OK
    STATUS_CODES = {
        'create': status.HTTP_201_CREATED,
        'destroy': status.HTTP_204_NO_CONTENT
    }

    def __init__(self, view_introspector, http_method, method=None):
        self.http_method = http_method.upper()
        self.method = method or self.http_method.lower()
        self.introspector = view_introspector
        self.callback = view_introspector.callback
        self.method_callback = self._method_callback()
        self.view = self._create_view()

        super(BaseMethodIntrospector, self).__init__(self.method_callback)

        self.auth_introspectors = get_authentication_introspectors(self.view)

    def _create_view(self):
        """
        Creates DjangoRestFramework view
        :return: view
        """
        view = self.callback()

        view.kwargs = getattr(view, 'kwargs', dict())
        if hasattr(self.introspector.pattern, 'default_args'):
            view.kwargs.update(self.introspector.pattern.default_args)

        view.request = HttpRequest()
        view.request.user = AnonymousUser()
        view.request.method = self.method

        return view

    def _method_callback(self):
        """
        Creates callback from method
        :return: method callback
        """
        return getattr(self.callback, self.method, None)

    def _get_tags(self):
        """
        Get swagger operation tags for method
        Always returns at least one tag

        :return: swagger tags
        :rtype: list
        """
        tags = self.parser.get_tags() or self.introspector.parser.get_tags()
        if not tags:
            tags = [get_view_name(self.introspector.callback).lower()]

        return tags

    def _get_summary(self):
        """
        Get swagger operation summary for method
        Always returns value

        :return: swagger summary
        :rtype: str
        """
        summary = self.parser.get_summary()

        if not summary:
            summary = '{method} {view_name}'.format(
                method=self.method,
                view_name=get_view_name(self.introspector.callback)
            ).replace('_', ' ').title()

        return summary

    def _get_consumes(self):
        """
        Get method consumes contenttypes

        :rtype: list
        """
        consumes = [getattr(parser, 'media_type') for parser in self.view.parser_classes if
                    hasattr(parser, 'media_type')]
        return consumes

    def _get_produces(self):
        """
        Get method produces contenttypes

        :rtype: list
        """
        produces = [getattr(renderer, 'media_type') for renderer in self.view.renderer_classes if
                    hasattr(renderer, 'media_type')]
        return produces

    def _get_description(self):
        """
        Get method description from docstring

        :rtype: str
        """
        return self.parser.get_description()

    @property
    def parameters(self):
        """
        Collects parameters from introspectors

        :return: list of parameters
        :rtype: list
        """
        parameters = super(BaseMethodIntrospector, self).parameters
        parameters.extend(self.introspector.parameters)

        # add serializer parameters only in several http methods
        if self.http_method.lower() in ('post', 'put', 'patch'):
            serializer = self._get_serializer(request=True)
            if serializer:
                si = SerializerIntrospector(serializer)
                parameters.extend(si.get_parameters())

        # add filter and pagination parameters only in 'list' methods
        if 'list' in self.method.lower():
            # filter parameters
            filter_introspectors = get_filter_introspectors(self.view)
            for filter_introspector in filter_introspectors:
                parameters.extend(filter_introspector.parameters)
            # pagination parameters
            pagination_introspector = get_pagination_introspector(self.view)
            parameters.extend(pagination_introspector.parameters)

        # path parameters
        parameters.extend(self._get_path_parameters())

        return parameters

    def _get_path_parameters(self):
        """
        Creates parameters described in url path
        :return: list of parameters
        :rtype: list
        """
        params = []
        url_parameters = re.findall(r'/{(.+?)}', self.introspector.path)

        for parameter in url_parameters:
            params.append({
                'name': parameter,
                'type': 'string',
                'in': 'path',
                'required': True
            })

        return params

    def _get_parameters(self):
        """
        Collects and clean method parameters

        :return: list of parameters
        :rtype: list
        """
        return clean_parameters(self.parameters, self.method)

    @property
    def responses(self):
        """
        Collects method responses

        :return: swagger responses object
        :rtype: OrderedDict
        """
        responses = OrderedDict()
        responses.update(self.introspector.responses)
        responses.update(super(BaseMethodIntrospector, self).responses)

        serializer = self._get_serializer()

        if serializer and not responses.get(200, None):
            si = SerializerIntrospector(serializer)
            if 'list' in self.method.lower():
                pagination_introspector = get_pagination_introspector(self.view, si=si)
                responses.update(pagination_introspector.responses)
            else:
                response = OrderedDict([
                    ('description', 'Default response'),
                    ('schema', si.build_response_object()['schema']),
                ])
                responses[200] = response

        status_code = self.STATUS_CODES.get(self.method, self.DEFAULT_STATUS_CODE)
        response = responses.pop(200, None)
        # TODO this code wants to be rewritten
        if response:
            if status_code == status.HTTP_204_NO_CONTENT:
                response.pop('schema', None)
            if not responses.get(status_code, None):
                responses[status_code] = response

        if status_code not in responses:
            response = OrderedDict([
                ('description', 'Empty response'),
            ])
            responses[status_code] = response

        return responses

    def _get_serializer(self, request=False):
        """
        Get serializer for method
        Try to find serializer in method docstring or load method serializer

        :param request: is request serializer?
        :return: DjangoRestFramework Serializer Class
        """
        for parser in flatten((self.parsers, self.introspector.parsers)):
            serializer_path = parser.get_serializer(request)
            if serializer_path:
                return load_class(serializer_path)
        return self._get_view_serializer()

    def _get_view_serializer(self):
        """
        Get method serializer

        :return: DjangoRestFramework Serializer Class
        """
        serializer = None
        if hasattr(self.callback, 'get_serializer_class'):
            view = self._create_view()
            if view is not None:
                serializer = view.get_serializer_class()
        return serializer

    def get_serializers(self):
        """
        Get all serializers for method
        this method used for collect definitions

        :return: list DjangoRestFramework Serializer Class
        :rtype: list
        """
        serializers = []
        for parser in flatten((self.parsers, self.introspector.parsers)):
            serializer_paths = parser.get_serializers()
            for serializer_path in serializer_paths:
                serializers.append(load_class(serializer_path))

        serializer = self._get_view_serializer()
        if serializer:
            serializers.append(serializer)

        return serializers

    def get_security_definitions(self):
        security_definitions = super(BaseMethodIntrospector, self).security_definitions
        for introspector in flatten(self.auth_introspectors):
            security_definitions.update(introspector.security_definitions)
        return security_definitions

    def _get_security(self):
        security = super(BaseMethodIntrospector, self).security
        for introspector in flatten(self.auth_introspectors):
            security.extend(introspector.security)
        return security

    def get_operation(self):
        """
        Get full swagger operation object

        :return: swagger operation object
        :rtype: OrderedDict
        """

        operation = OrderedDict(
            tags=self._get_tags(),
            summary=self._get_summary(),
            description=self._get_description(),
            parameters=self._get_parameters(),
            produces=self._get_produces(),
            consumes=self._get_consumes(),
            responses=self.responses,
            security=self._get_security()
        )

        # TODO: SECURITY OBJECT SECURITY DEFINITIONS
        for key, value in list(operation.items()):
            # Remove empty keys
            if not value:
                operation.pop(key)

        return operation


class ViewSetMethodIntrospector(BaseMethodIntrospector):
    """
    Introspector for DjangoRestFramework ViewSet methods
    """

    def _create_view(self):
        view = super(ViewSetMethodIntrospector, self)._create_view()
        if not hasattr(view, 'action'):
            setattr(view, 'action', self.method)
        view.request.method = self.http_method
        return view


class ApiViewMethodIntrospector(BaseMethodIntrospector):
    """
    Introspector for DjangoRestFramework ApiView methods
    """
    def _method_callback(self):
        m = super(ApiViewMethodIntrospector, self)._method_callback()
        m = get_decorators(m)[-1]
        return m


class WrappedApiViewMethodIntrospector(BaseMethodIntrospector):
    """
    Introspector for DjangoRestFramework wrapped with decorators methods
    """
    def _method_callback(self):
        return self.callback