from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from .methods import save_to_mongo, DictToObject


class MongoSerializer(serializers.Serializer):

    def __init__(self, instance=None, pk=None, *args, **kwargs):
        # instance and pk should not conflict in updating and retrieving like: updating: serializer(pk=1, data={..}),
        # retrieving: serializer(instance).data
        self.pk = pk
        self.request = kwargs.pop('request', None)
        if kwargs.get('partial'):      # in updating only provided fields should validate
            for key in self.fields:
                self.fields[key].required = False
        super().__init__(instance=instance, *args, **kwargs)
        self.context.update({'request': self.request})

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        rep2 = representation.copy()
        if self.partial:
            for key in rep2:    # can't change dictionary (representation) during iteration
                if representation.get(key) is None:
                    representation.pop(key)
        return representation

    def save(self, mongo_collection, **kwargs):
        change = True if self.pk else False
        if not change:
            return self.create(mongo_collection)
        else:
            return self.update(self.pk, mongo_collection)

    def create(self, mongo_collection):
        current_class = self.__class__
        serialized = self.serialize_and_filter(self.validated_data)
        return save_to_mongo(collection=mongo_collection, data=serialized)

    def update(self, pk, mongo_collection):
        # because partial=True don't raise error when 'validated_data' doesn't provide required fields
        current_class = self.__class__
        serialized = self.serialize_and_filter(self.validated_data)
        return save_to_mongo(mongo_collection, pk, data=serialized)

    def serialize_and_filter(self, validated_data):
        # serialize and next, keep only fields provided in request.data and remove unexpected others
        current_class = self.__class__
        serialized = self.get_serialize(validated_data)
        if self.partial:
            serialized = self._field_filtering_for_update(validated_data, serialized)
        return serialized

    def get_serialize(self, validated_data):
        # when partial=True, current_class(validated_data) doesn't raise error even doesn't provide required fields
        current_class = self.__class__
        serialized = current_class(DictToObject(validated_data), partial=self.partial).data
        return serialized

    def _field_filtering_for_update(self, input, output):  # input should be validated_data, output like serializer.data
        # keep only fields provided in validated_data and remove unexpected others (fields with defaut value,
        # None value or ...). we prevent override unexpected keys in db.
        for key in output.copy():
            if key not in input:
                del output[key]
        return output
