from django.db import transaction
from django.utils.translation import ugettext as _

from openslides.poll.serializers import default_votes_validator
from openslides.utils.rest_api import (
    DecimalField,
    DictField,
    IntegerField,
    ListField,
    ListSerializer,
    ModelSerializer,
    SerializerMethodField,
    ValidationError,
)

from ..utils.validate import validate_html
from .models import (
    Assignment,
    AssignmentOption,
    AssignmentPoll,
    AssignmentRelatedUser,
    AssignmentVote,
    models,
)


def posts_validator(data):
    """
    Validator for open posts. It checks that the values for the open posts are greater than 0.
    """
    if (data['open_posts'] and data['open_posts'] is not None and data['open_posts'] < 1):
        raise ValidationError({'detail': _('Value for {} must be greater than 0').format('open_posts')})
    return data


class AssignmentRelatedUserSerializer(ModelSerializer):
    """
    Serializer for assignment.models.AssignmentRelatedUser objects.
    """
    class Meta:
        model = AssignmentRelatedUser
        fields = (
            'id',
            'user',
            'elected',
            'assignment',
            'weight')  # js-data needs the assignment-id in the nested object to define relations.


class AssignmentVoteSerializer(ModelSerializer):
    """
    Serializer for assignment.models.AssignmentVote objects.
    """
    class Meta:
        model = AssignmentVote
        fields = ('weight', 'value',)


class AssignmentOptionSerializer(ModelSerializer):
    """
    Serializer for assignment.models.AssignmentOption objects.
    """
    votes = AssignmentVoteSerializer(many=True, read_only=True)
    is_elected = SerializerMethodField()

    class Meta:
        model = AssignmentOption
        fields = ('id', 'candidate', 'is_elected', 'votes', 'poll', 'weight')

    def get_is_elected(self, obj):
        """
        Returns the election status of the candidate of this option.
        """
        return obj.poll.assignment.is_elected(obj.candidate)


class FilterPollListSerializer(ListSerializer):
    """
    Customized serializer to filter polls (exclude unpublished).
    """
    def to_representation(self, data):
        """
        List of object instances -> List of dicts of primitive datatypes.

        This method is adapted to filter the data and exclude unpublished polls.
        """
        # Dealing with nested relationships, data can be a Manager,
        # so, first get a queryset from the Manager if needed
        iterable = data.filter(published=True) if isinstance(data, models.Manager) else data
        return [self.child.to_representation(item) for item in iterable]


class AssignmentAllPollSerializer(ModelSerializer):
    """
    Serializer for assignment.models.AssignmentPoll objects.

    Serializes all polls.
    """
    options = AssignmentOptionSerializer(many=True, read_only=True)
    votes = ListField(
        child=DictField(
            child=DecimalField(max_digits=15, decimal_places=6, min_value=-2)),
        write_only=True,
        required=False)
    has_votes = SerializerMethodField()

    class Meta:
        model = AssignmentPoll
        fields = (
            'id',
            'pollmethod',
            'description',
            'published',
            'options',
            'votesabstain',
            'votesno',
            'votesvalid',
            'votesinvalid',
            'votescast',
            'votes',
            'has_votes',
            'assignment')  # js-data needs the assignment-id in the nested object to define relations.
        read_only_fields = ('pollmethod',)
        validators = (default_votes_validator,)

    def get_has_votes(self, obj):
        """
        Returns True if this poll has some votes.
        """
        return obj.has_votes()

    @transaction.atomic
    def update(self, instance, validated_data):
        """
        Customized update method for polls. To update votes use the write
        only field 'votes'.

        Example data for a 'pollmethod'='yna' poll with two candidates:

            "votes": [{"Yes": 10, "No": 4, "Abstain": -2},
                      {"Yes": -1, "No": 0, "Abstain": -2}]

        Example data for a 'pollmethod' ='yn' poll with two candidates:
            "votes": [{"Votes": 10}, {"Votes": 0}]
        """
        # Update votes.
        votes = validated_data.get('votes')
        if votes:
            options = list(instance.get_options())
            if len(votes) != len(options):
                raise ValidationError({
                    'detail': _('You have to submit data for %d candidates.') % len(options)})
            for index, option in enumerate(options):
                if len(votes[index]) != len(instance.get_vote_values()):
                    raise ValidationError({
                        'detail': _('You have to submit data for %d vote values.') % len(instance.get_vote_values())})
                for vote_value, vote_weight in votes[index].items():
                    if vote_value not in instance.get_vote_values():
                        raise ValidationError({
                            'detail': _('Vote value %s is invalid.') % vote_value})
                instance.set_vote_objects_with_values(option, votes[index], skip_autoupdate=True)

        # Update remaining writeable fields.
        instance.description = validated_data.get('description', instance.description)
        instance.published = validated_data.get('published', instance.published)
        instance.votesabstain = validated_data.get('votesabstain', instance.votesabstain)
        instance.votesno = validated_data.get('votesno', instance.votesno)
        instance.votesvalid = validated_data.get('votesvalid', instance.votesvalid)
        instance.votesinvalid = validated_data.get('votesinvalid', instance.votesinvalid)
        instance.votescast = validated_data.get('votescast', instance.votescast)
        instance.save()
        return instance


class AssignmentShortPollSerializer(AssignmentAllPollSerializer):
    """
    Serializer for assignment.models.AssignmentPoll objects.

    Serializes only short polls (excluded unpublished polls).
    """
    class Meta:
        list_serializer_class = FilterPollListSerializer
        model = AssignmentPoll
        fields = (
            'id',
            'pollmethod',
            'description',
            'published',
            'options',
            'votesabstain',
            'votesno',
            'votesvalid',
            'votesinvalid',
            'votescast',
            'has_votes',)


class AssignmentFullSerializer(ModelSerializer):
    """
    Serializer for assignment.models.Assignment objects. With all polls.
    """
    assignment_related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
    polls = AssignmentAllPollSerializer(many=True, read_only=True)
    agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=3)
    agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)

    class Meta:
        model = Assignment
        fields = (
            'id',
            'title',
            'description',
            'open_posts',
            'phase',
            'assignment_related_users',
            'poll_description_default',
            'polls',
            'agenda_item_id',
            'agenda_type',
            'agenda_parent_id',
            'tags',)
        validators = (posts_validator,)

    def validate(self, data):
        if 'description' in data:
            data['description'] = validate_html(data['description'])
        return data

    def create(self, validated_data):
        """
        Customized create method. Set information about related agenda item
        into agenda_item_update_information container.
        """
        agenda_type = validated_data.pop('agenda_type', None)
        agenda_parent_id = validated_data.pop('agenda_parent_id', None)
        assignment = Assignment(**validated_data)
        assignment.agenda_item_update_information['type'] = agenda_type
        assignment.agenda_item_update_information['parent_id'] = agenda_parent_id
        assignment.save()
        return assignment


class AssignmentShortSerializer(AssignmentFullSerializer):
    """
    Serializer for assignment.models.Assignment objects. Without unpublished poll.
    """
    assignment_related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
    polls = AssignmentShortPollSerializer(many=True, read_only=True)

    class Meta:
        model = Assignment
        fields = (
            'id',
            'title',
            'description',
            'open_posts',
            'phase',
            'assignment_related_users',
            'poll_description_default',
            'polls',
            'agenda_item_id',
            'tags',)
        validators = (posts_validator,)

    def validate(self, data):
        if 'description' in data:
            data['description'] = validate_html(data['description'])
        return data
