import inspect

from rest_framework.settings import api_settings


class PolymorphicProxySerializer:
    """
    This class is to be used with :func:`@extend_schema <.extend_schema>` to
    signal a request/response might be polymorphic (accepts/returns data
    possibly from different serializers)

    Beware that this is not a real serializer and therefore is not derived from
    serializers.Serializer. It *cannot* be used in views as `serializer_class`
    or as field in a actual serializer. You likely want to handle this in the
    view method.

    Also make sure that each sub-serializer has a field named after the value of
    `resource_type_field` (discriminator field) for the the mapping and parity
    with with the generated schema.
    """

    def __init__(self, component_name, serializers, resource_type_field_name):
        self.component_name = component_name
        self.serializers = serializers
        self.resource_type_field_name = resource_type_field_name


class OpenApiSchemaBase:
    pass


class OpenApiParameter(OpenApiSchemaBase):
    QUERY = 'query'
    PATH = 'path'
    HEADER = 'header'
    COOKIE = 'cookie'

    def __init__(self, name, type=str, location=QUERY, required=False, description='', enum=None, deprecated=False):
        self.name = name
        self.type = type
        self.location = location
        self.required = required
        self.description = description
        self.enum = enum
        self.deprecated = deprecated


def extend_schema(
        operation_id=None,
        parameters=None,
        request=None,
        responses=None,
        auth=None,
        description=None,
        deprecated=None,
        tags=None,
        exclude=False,
        operation=None,
):
    """
    decorator for the "view" kind. partially or completely overrides what would be
    generated by drf-spectacular.

    :param operation_id: replaces the auto-generated operation_id. make sure there
        are no naming collisions.
    :param parameters: list of additional or replacement parameters added to the
        auto-discovered fields.
    :param responses: replaces the discovered Serializer. Takes a variety of
        inputs that can be used individually or combined

        - ``Serializer`` class
        - ``Serializer`` instance (e.g. ``Serializer(many=True)`` for listings)
        - ``dict`` with status codes as keys and `Serializers` as values.
        - :class:`.PolymorphicProxySerializer` for signaling that
          the operation may yield data from different serializers depending
          on the circumstances.
    :param request: replaces the discovered ``Serializer``.
    :param auth:
    :param description: replaces discovered doc strings
    :param deprecated: mark operation as deprecated
    :param tags: override default list of tags
    :param exclude: set True to exclude operation from schema
    :param operation: manually override what auto-discovery would generate. you must
        provide a OpenAPI3-compliant dictionary that gets directly translated to YAML.
    :return:
    """

    def decorator(f):
        class ExtendedSchema(api_settings.DEFAULT_SCHEMA_CLASS):
            def get_operation(self, path, method, registry):
                if exclude:
                    return None
                if operation is not None:
                    return operation
                return super().get_operation(path, method, registry)

            def get_operation_id(self, path, method):
                if operation_id:
                    return operation_id
                return super().get_operation_id(path, method)

            def get_override_parameters(self, path, method):
                if parameters:
                    return parameters
                return super().get_override_parameters(path, method)

            def get_auth(self, path, method):
                if auth:
                    return auth
                return super().get_auth(path, method)

            def get_request_serializer(self, path, method):
                if request:
                    return request
                return super().get_request_serializer(path, method)

            def get_response_serializers(self, path, method):
                if responses:
                    return responses
                return super().get_response_serializers(path, method)

            def get_description(self, path, method):
                if description:
                    return description
                return super().get_description(path, method)

            def is_deprecated(self, path, method):
                if deprecated:
                    return deprecated
                return super().is_deprecated(path, method)

            def get_tags(self, path, method):
                if tags is not None:
                    return tags
                return super().get_tags(path, method)

        if inspect.isclass(f):
            class ExtendedView(f):
                schema = ExtendedSchema()

            return ExtendedView
        elif callable(f):
            # custom actions have kwargs in their context, others don't. create it so our create_view
            # implementation can overwrite the default schema
            if not hasattr(f, 'kwargs'):
                f.kwargs = {}

            # this simulates what @action is actually doing. somewhere along the line in this process
            # the schema is picked up from kwargs and used. it's involved my dear friends.
            f.kwargs['schema'] = ExtendedSchema()
            return f
        else:
            return f

    return decorator


def extend_schema_field(field):
    """
    Decorator for the "field" kind. Can be used with ``SerializerMethodField`` (annotate the actual
    method) or with custom ``serializers.Field`` implementations.

    If your custom serializer field base class is already the desired type, decoration is not necessary.
    To override the discovered base class type, you can decorate your custom field class.

    Always takes precedence over other mechanisms (e.g. type hints, auto-discovery).

    :param field: accepts a ``Serializer`` or :class:`~.types.OpenApiTypes`
    """

    def decorator(f):
        f._spectacular_annotation = field
        return f

    return decorator
