import inspect
from copy import deepcopy

from py2swagger.utils import OrderedDict
from py2swagger.yamlparser import YAMLDocstringParser
from rest_framework.serializers import ModelSerializer, ListSerializer

from .field import FieldIntrospector


class SerializerIntrospector(object):
    """
    DjangoRestFramework Serializer Introspector
    """

    def __init__(self, serializer):
        """
        :param serializer: DjangoRestFramework Serializer
        """
        if isinstance(serializer, ListSerializer):
            serializer = serializer.child

        self.serializer = serializer
        self.name = self._get_name()

        self.fields = self._collect_fields()

    def _get_name(self):
        """
        :return: Serializer name
        :rtype: str
        """
        serializer = self.serializer

        if inspect.isclass(serializer):
            name = serializer.__name__
        else:
            name = serializer.__class__.__name__

        if name == 'DefaultSerializer' and issubclass(serializer, ModelSerializer):
            model_name = self.serializer.Meta.model.__name__
            name = '{0}Serializer'.format(model_name.strip())
        return name

    def _collect_fields(self):
        serializer_fields = self._get_serializer_fields()
        docstring_fields = self._get_docstring_fields()
        _fields = OrderedDict()

        fields_set = set(serializer_fields.keys()).union(docstring_fields.keys())

        for field_name in fields_set:
            if field_name in docstring_fields:
                field_object = docstring_fields[field_name]
                _fields[field_name] = self._prepare_docstring_field_object(field_object)

            else:
                fi = FieldIntrospector(serializer_fields[field_name], self.__class__)
                _fields[field_name] = fi.prepare_field_object()

        return _fields

    @staticmethod
    def _prepare_docstring_field_object(field_object):
        result = OrderedDict()

        result['required'] = field_object.pop('required', False)
        result['readOnly'] = field_object.pop('readOnly', False)

        if 'response' not in field_object and 'request' not in field_object:
            result['request'] = field_object
            result['response'] = field_object
        else:
            result['request'] = field_object.get('request', field_object.get('response'))
            result['response'] = field_object.get('response', field_object.get('request'))

        return result

    def _get_docstring_fields(self):
        """
        Collect custom serializer fields described in serializer docstring

        :rtype: OrderedDict
        """
        if not inspect.isclass(self.serializer):
            self.serializer = self.serializer.__class__

        parser = YAMLDocstringParser()

        for cls in inspect.getmro(self.serializer):
            parser.update(inspect.getdoc(cls))

        doc_fields = parser.schema.get('fields', OrderedDict())

        return doc_fields

    def _get_serializer_fields(self):
        """
        :return: Serializer fields
        :rtype: dict
        """
        if hasattr(self.serializer, '__call__'):
            return self.serializer().fields.fields
        else:
            return self.serializer.get_fields()

    def _get_formdata_parameters(self):
        """
        Creates formdata parameters

        :return: list of parameters
        :rtype: list
        """
        parameters = []
        for name, field in self.fields.items():
            if field.get('readOnly'):
                continue

            f = deepcopy(field['request'])
            f['required'] = field.get('required')
            f['in'] = 'formData'
            f['name'] = name
            parameters.append(f)
        return parameters

    def _get_body_parameters(self):
        """
        Creates parameters for body

        :return: list of parameters
        :rtype: list
        """
        schema = self._get_schema(request=True)
        schema['schema']['id'] = 'Request{}'.format(schema['schema']['id'])

        schema['in'] = 'body'
        schema['name'] = 'data'
        return [schema]

    def get_parameters(self):
        """
        Creates parameters
        If there are 'file' in parameters returns formData parameters else body parameters

        :return: list of parameters
        :rtype: list
        """
        parameters = self._get_formdata_parameters()
        if any(p.get('type', None) == 'file' for p in parameters):
            return parameters
        else:
            return self._get_body_parameters()

    def _get_schema(self, multiple=False, inline=False, request=False):
        required = []
        properties = OrderedDict()
        schema = OrderedDict()
        if not inline:
            schema['id'] = self.name
        schema['type'] = 'object'

        for field_name, obj in self.fields.items():
            field_object = obj.get('request') if request else obj.get('response')

            if request and (obj.get('readOnly') or field_object.get('readOnly')):
                continue

            if obj.get('required') or field_object.get('required'):
                required.append(field_name)
            properties[field_name] = field_object

        schema['required'] = required
        schema['properties'] = properties

        result = OrderedDict()

        if multiple:
            result['type'] = 'array'
            result['items'] = OrderedDict(schema=schema)
        else:
            result = OrderedDict(schema=schema)

        return result

    def build_response_object(self, multiple=False, inline=False):
        """
        :param multiple: display as array?
        :type multiple: bool
        :param inline: display as inline without id
        :type inline: bool
        :return: full swagger schema for serializer
        :rtype: dict
        """
        return self._get_schema(multiple, inline)