"""
    Forum polls abstract models
    ===========================

    This module defines abstract models provided by the ``forum_polls`` application.

"""

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _

from machina.apps.forum_conversation.forum_polls import validators
from machina.models.abstract_models import DatedModel


class AbstractTopicPoll(DatedModel):
    """ Represents a poll embedded in a forum topic. """

    topic = models.OneToOneField(
        'forum_conversation.Topic', related_name='poll', on_delete=models.CASCADE,
        verbose_name=_('Temat'),
    )

    # A poll is defined by a single question
    question = models.CharField(max_length=255, verbose_name=_('Pytanie ankiety'))

    # A poll can have a duration
    duration = models.PositiveIntegerField(
        verbose_name=_('Czas trwania ankiety w dniach'), blank=True, null=True,
    )

    # Users can possibly select more than one option associated with a poll
    max_options = models.PositiveSmallIntegerField(
        verbose_name=_('Maksymalna liczba opcji ankiety dla użytkownika'),
        validators=validators.poll_max_options, default=1,
    )

    # Are users allowed to change their votes ?
    user_changes = models.BooleanField(verbose_name=_('Pozwól zmieniać odpowiedzi'), default=False)

    class Meta:
        abstract = True
        app_label = 'forum_polls'
        ordering = ['-updated', ]
        get_latest_by = 'updated'
        verbose_name = _('Temat z ankietą')
        verbose_name_plural = _('Tematy z ankietą')

    def __str__(self):
        return '{}'.format(self.topic.subject)

    @cached_property
    def votes(self):
        """ Returns all the votes related to this topic poll. """
        votes = []
        for option in self.options.all():
            votes += option.votes.all()
        return votes


class AbstractTopicPollOption(models.Model):
    """ Represents a poll option. """

    poll = models.ForeignKey(
        'forum_polls.TopicPoll', related_name='options', on_delete=models.CASCADE,
        verbose_name=_('Ankieta'),
    )
    text = models.CharField(max_length=255, verbose_name=_('Treść odpowiedzi'))

    class Meta:
        abstract = True
        app_label = 'forum_polls'
        verbose_name = _('Opcja odpowiedzi')
        verbose_name_plural = _('Opcje odpowiedzi')

    def __str__(self):
        return '{} - {}'.format(self.poll, self.text)

    @cached_property
    def percentage(self):
        """ Returns the percentages of votes associated with the considered option. """
        return (self.votes.count() / (len(self.poll.votes) or 1)) * 100


class AbstractTopicPollVote(models.Model):
    """ Represents a poll vote. """

    voter = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='poll_votes', blank=True, null=True,
        on_delete=models.CASCADE, verbose_name=_('Głosujący'),
    )
    anonymous_key = models.CharField(
        max_length=100, verbose_name=_('Identyfikator anonimowego użytkownika'), blank=True, null=True,
    )

    poll_option = models.ForeignKey(
        'forum_polls.TopicPollOption', related_name='votes', on_delete=models.CASCADE,
        verbose_name=_('Odpowiedź'),
    )
    timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_('Data oddania głosu'))

    class Meta:
        abstract = True
        app_label = 'forum_polls'
        verbose_name = _('Głos użytkownika')
        verbose_name_plural = _('Głosy użytkowników')

    def __str__(self):
        return '{} - {}'.format(self.poll_option, self.voter)

    def clean(self):
        """ Validates the considered instance. """
        super().clean()

        # At least a poster (user) or a session key must be associated with
        # the vote instance.
        if self.voter is None and self.anonymous_key is None:
            raise ValidationError(_('ID użytkownika lub identyfikator anonimowego uzytkownika musi być użyty.'))
        if self.voter and self.anonymous_key:
            raise ValidationError(_('ID użytkownika lub identyfikator anonimowego uzytkownika musi być użyty, ale nie oba.'))
