"""
Used to create Django Rest Framework serializers for Apache Thrift Data Types
"""
import copy
import datetime
import logging

from rest_framework.serializers import (
    BooleanField,
    CharField,
    DateTimeField,
    DecimalField,
    DictField,
    Field,
    IntegerField,
    ListField,
    ListSerializer,
    Serializer,
    SerializerMetaclass,
    ValidationError
)
from thrift.Thrift import TType

logger = logging.getLogger(__name__)

# used to map apache thrift data types to django serializer fields
mapping = {
    TType.STRING: CharField,
    TType.I08: IntegerField,
    TType.I16: IntegerField,
    TType.I32: IntegerField,
    TType.I64: IntegerField,
    TType.DOUBLE: DecimalField,
    TType.BOOL: BooleanField,
    TType.MAP: DictField
}


class UTCPosixTimestampDateTimeField(DateTimeField):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.default = self.current_time_ms
        self.initial = self.initial_value
        self.required = False

    def to_representation(self, obj):
        # Create datetime instance from milliseconds that is aware of timezon
        dt = datetime.datetime.fromtimestamp(obj / 1000, datetime.timezone.utc)
        return super().to_representation(dt)

    def to_internal_value(self, data):
        dt = super().to_internal_value(data)
        return int(dt.timestamp() * 1000)

    def initial_value(self):
        return self.to_representation(self.current_time_ms())

    def current_time_ms(self):
        return int(datetime.datetime.utcnow().timestamp() * 1000)


class ThriftEnumField(Field):

    def __init__(self, enumClass, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.enumClass = enumClass

    def to_representation(self, obj):
        if obj is None:
            return None
        return self.enumClass._VALUES_TO_NAMES[obj]

    def to_internal_value(self, data):
        if self.allow_null and data is None:
            return None
        if data not in self.enumClass._NAMES_TO_VALUES:
            raise ValidationError(
                "Not an allowed name of enum {}".format(
                    self.enumClass.__name__))
        return self.enumClass._NAMES_TO_VALUES.get(data, None)


def create_serializer(thrift_data_type, enable_date_time_conversion=False, **kwargs):
    """
    Create django rest framework serializer based on the thrift data type
    :param thrift_data_type: Thrift data type
    :param kwargs: Other Django Framework Serializer initialization parameters
    :param enable_date_time_conversion: enable conversion of field with name ending with time to
            UTCPosixTimestampDateTimeField instead of IntegerField
    :return: instance of custom serializer for the given thrift data type
    """
    return create_serializer_class(thrift_data_type, enable_date_time_conversion)(**kwargs)


def create_serializer_class(thrift_data_type, enable_date_time_conversion=False):
    class CustomSerializerMeta(SerializerMetaclass):

        def __new__(cls, name, bases, attrs):
            meta = attrs.get('Meta', None)
            thrift_spec = thrift_data_type.thrift_spec
            for field in thrift_spec:
                # Don't replace existing attrs to allow subclasses to override
                if field and field[2] not in attrs:
                    required = (field[2] in meta.required
                                if meta and hasattr(meta, 'required')
                                else False)
                    read_only = (field[2] in meta.read_only
                                 if meta and hasattr(meta, 'read_only')
                                 else False)
                    allow_null = not required
                    field_serializer = process_field(
                        field, enable_date_time_conversion, required=required, read_only=read_only,
                        allow_null=allow_null)
                    attrs[field[2]] = field_serializer
            return super().__new__(cls, name, bases, attrs)

    class CustomSerializer(Serializer, metaclass=CustomSerializerMeta):
        """
        Custom Serializer which handle the list fields which holds custom class objects
        """

        def process_nested_fields(self, validated_data):
            fields = self.fields
            params = copy.deepcopy(validated_data)
            for field_name, serializer in fields.items():
                if (isinstance(serializer, ListField) or
                        isinstance(serializer, ListSerializer)):
                    if (params.get(field_name, None) is not None or
                            not serializer.allow_null):
                        if isinstance(serializer.child, Serializer):
                            params[field_name] = [serializer.child.create(
                                item) for item in params[field_name]]
                        else:
                            params[field_name] = serializer.to_representation(
                                params[field_name])
                elif isinstance(serializer, Serializer):
                    if field_name in params and params[field_name] is not None:
                        params[field_name] = serializer.create(
                            params[field_name])
            return params

        def create(self, validated_data):
            params = self.process_nested_fields(validated_data)
            return thrift_data_type(**params)

        def update(self, instance, validated_data):
            return self.create(validated_data)

    return CustomSerializer


def process_field(field, enable_date_time_conversion, required=False, read_only=False, allow_null=False):
    """
    Used to process a thrift data type field
    :param field:
    :param required:
    :param read_only:
    :param allow_null:
    :return:
    """
    if field[1] in mapping:
        # handling scenarios when the thrift field type is present in the
        # mapping
        field_class = mapping[field[1]]
        kwargs = dict(required=required, read_only=read_only)
        # allow_null isn't allowed for BooleanField
        if field_class not in (BooleanField,):
            kwargs['allow_null'] = allow_null
        # allow_null CharField are also allowed to be blank
        if field_class == CharField:
            kwargs['allow_blank'] = allow_null
        thrift_model_class = mapping[field[1]]
        if enable_date_time_conversion and thrift_model_class == IntegerField and field[2].lower().endswith("time"):
            thrift_model_class = UTCPosixTimestampDateTimeField
        return thrift_model_class(**kwargs)
    elif field[1] == TType.LIST:
        # handling scenario when the thrift field type is list
        list_field_serializer = process_list_field(field)
        return ListField(child=list_field_serializer,
                         required=required,
                         read_only=read_only,
                         allow_null=allow_null)
    elif field[1] == TType.STRUCT:
        # handling scenario when the thrift field type is struct
        return create_serializer(field[3][0],
                                 required=required,
                                 read_only=read_only,
                                 allow_null=allow_null)


def process_list_field(field):
    """
    Used to process thrift list type field
    :param field:
    :return:
    """
    list_details = field[3]
    if list_details[0] in mapping:
        # handling scenario when the data type hold by the list is in the
        # mapping
        return mapping[list_details[0]]()
    elif list_details[0] == TType.STRUCT:
        # handling scenario when the data type hold by the list is a struct
        return create_serializer(list_details[1][0])
