"""Generates API documentation by introspection."""
from django.contrib.auth.models import AnonymousUser
import rest_framework

from rest_framework import viewsets, mixins
from rest_framework.generics import GenericAPIView

from rest_framework.serializers import BaseSerializer, ListField

from .introspectors import (
    APIViewIntrospector,
    GenericViewIntrospector,
    BaseMethodIntrospector,
    ViewSetIntrospector,
    WrappedAPIViewIntrospector,
    get_data_type,
)
from .compat import OrderedDict
from .utils import extract_base_path, get_serializer_name, get_default_value


class DocumentationGenerator(object):
    # Serializers defined in docstrings
    explicit_serializers = set()

    # Serializers defined in fields
    fields_serializers = set()

    # Response classes defined in docstrings
    explicit_response_types = dict()

    def __init__(self, for_user=None, config=None, request=None, config_name=None):
        self.config = config
        self.config_name = config_name
        self.user = for_user or AnonymousUser()
        self.request = request

    def get_root(self, endpoints_conf):
        self.default_payload_definition_name = self.config.get("default_payload_definition_name", None)
        self.default_payload_definition = self.config.get("default_payload_definition", None)
        if self.default_payload_definition:
            self.explicit_response_types.update({
                self.default_payload_definition_name: self.default_payload_definition
            })
        return {
            'swagger': '2.0',
            'info': self.config.get('info', {
                'contact': '',
            }),
            'basePath': self.config.get("basePath", '').format(
                version=self.request.parser_context['kwargs']['version']
            ),
            'host': self.config.get('host', ''),
            'schemes': self.config.get('schemes', ''),
            'paths': self.get_paths(endpoints_conf),
            'definitions': self.get_definitions(endpoints_conf),
            'securityDefinitions': self.config.get('securityDefinitions', {})
        }

    def get_paths(self, endpoints_conf):
        paths_dict = {}
        for endpoint in endpoints_conf:
            # remove the base_path from the begining of the path
            endpoint['path'] = extract_base_path(path=endpoint['path'], base_path=self.config.get('basePath'))
            path_item = self.get_path_item(endpoint)
            if path_item:
                paths_dict[endpoint['path']] = path_item

        paths_dict = OrderedDict(sorted(paths_dict.items()))
        return paths_dict

    def get_path_item(self, api_endpoint):
        introspector = self.get_introspector(api_endpoint)

        path_item = {}

        for operation in self.get_operations(api_endpoint, introspector):
            path_item[operation.pop('method').lower()] = operation
        if not path_item:
            return False

        method_introspectors = self.get_method_introspectors(api_endpoint, introspector)
        # we get the main parameters (common to all operations) from the first view operation
        # only path parameters are commont to all operations
        path_item['parameters'] = method_introspectors[0].build_path_parameters()

        return path_item

    def get_method_introspectors(self, api_endpoint, introspector):
        return [method_introspector for method_introspector in introspector if
                isinstance(method_introspector, BaseMethodIntrospector)
                and not method_introspector.get_http_method() == "OPTIONS"]

    def get_operations(self, api_endpoint, introspector):
        """
        Returns docs for the allowed methods of an API endpoint
        """
        operations = []

        for method_introspector in self.get_method_introspectors(api_endpoint, introspector):
            doc_parser = method_introspector.get_yaml_parser()
            # check if operation is allowed on the current swagger config name
            operation_config_name = doc_parser.get_param(param_name='swagger_config_name', default=False)
            if operation_config_name and operation_config_name != self.config_name:
                continue

            serializer = self._get_method_serializer(method_introspector)

            operation_method = method_introspector.get_http_method()

            operation = {
                'method': operation_method,
                'description': method_introspector.get_description(),
                'summary': method_introspector.get_summary(),
                'operationId': method_introspector.get_operation_id(),
                'produces': doc_parser.get_param(param_name='produces', default=self.config.get('produces')),
                'tags': doc_parser.get_param(param_name='tags', default=[]),
                'parameters': self._get_operation_parameters(method_introspector, operation_method)
            }

            if doc_parser.yaml_error is not None:
                operation['notes'] += '<pre>YAMLError:\n {err}</pre>'.format(
                    err=doc_parser.yaml_error)

            response_messages = {}
            # set default response reference
            if self.default_payload_definition:
                response_messages['default'] = {
                    "description": "error payload",
                    "schema": {
                        "$ref": "#/definitions/{}".format(self.default_payload_definition_name)
                    }
                }

            # write the default "success" responses
            success_code, success_body = self._get_operation_success_response(
                doc_parser, serializer, introspector, method_introspector
            )
            response_messages[success_code] = success_body

            # overwrite default and add more responses from docstrings
            response_messages.update(doc_parser.get_response_messages())

            operation['responses'] = response_messages

            operations.append(operation)

        return operations

    def _get_operation_success_response(self, doc_parser, serializer, introspector, method_introspector):
        response_type = self._get_method_response_type(doc_parser, serializer, introspector, method_introspector)
        operation_method = method_introspector.get_http_method().lower()

        success_code = "200"

        if operation_method == "delete":
            return ("204", {"description": "Deleted"})

        if operation_method == "post":
            success_code = "201"

        success_body = {
            'description': 'Successful operation',
            'schema': {
                '$ref': '#/definitions/' + response_type
            } if response_type != 'object' else {