"""ORM Models for the Mai-Star ranking"""

import logging

from django.db import models
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.urls import reverse
from django.utils.translation import ugettext as _

from events.models import Event
from . import settings, managers


class Game(models.Model):
    """to record a complete game with 6 different players."""

    _player_list = list()
    event = models.ForeignKey(Event, on_delete=models.CASCADE,
                              related_name='maistargame_set')
    comment = models.TextField(_('Comment'), blank=True)
    player1 = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        verbose_name=_("Player 1"), related_name='+'
    )
    player1_score = models.SmallIntegerField(_("Score"))
    player1_placement = models.PositiveSmallIntegerField(editable=False)

    player2 = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        verbose_name=_("Player 2"), related_name='+'
    )
    player2_score = models.SmallIntegerField(_("Score"))
    player2_placement = models.PositiveSmallIntegerField(editable=False)

    player3 = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        verbose_name=_("Player 3"), related_name='+'
    )
    player3_score = models.SmallIntegerField(_("Score"))
    player3_placement = models.PositiveSmallIntegerField(editable=False)

    player4 = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        verbose_name=_("Player 4"), related_name='+'
    )
    player4_score = models.SmallIntegerField(_("Score"))
    player4_placement = models.PositiveSmallIntegerField(editable=False)

    player5 = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        verbose_name=_("Player 5"), related_name='+'
    )
    player5_score = models.SmallIntegerField(_("Score"))
    player5_placement = models.PositiveSmallIntegerField(editable=False)

    player6 = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        verbose_name=_("Player 6"), related_name='+'
    )
    player6_score = models.SmallIntegerField(_("Score"))
    player6_placement = models.PositiveSmallIntegerField(editable=False)

    confirmed = models.BooleanField(
        _('Has been confirmed'),
        default=True,
        help_text=_('the game only counts whe it has been confirmed')
    )
    player_names = models.CharField(max_length=255, editable=False)
    season = models.PositiveSmallIntegerField(_('Season'), editable=False,
                                              db_index=True)

    objects = managers.GameManager()

    class Meta(object):
        """Display rankings by placement, best players first."""
        ordering = ('-event__start', '-id')

    def __str__(self):
        return _("Mai-Star Game with {0} from {1:%Y-%m-%d}").format(
            self.player_names, self.event.start
        )

    def get_absolute_url(self):
        """URL to the event subpage that lists the Mai-Star games."""
        url = reverse('maistar-game-list',
                      kwargs={'event': self.event.pk})
        return u'%(url)s#%(pk)d' % {'url': url, 'pk': self.pk}

    @property
    def player_list(self):
        """return a dict() with all 6 player names, scores and placements.
        It will be cached and reused for more perfomance."""
        if self._player_list:
            return self._player_list
        for player_no in range(1, 7):
            self._player_list.append(dict(
                user=getattr(self, 'player%d' % player_no),
                score=getattr(self, 'player%d_score' % player_no),
                placement=getattr(self, 'player%d_placement' % player_no)
            ))
        return self._player_list

    def save(self, **kwargs):
        """recalculate the rankings before saving.
         Also self.player_names will be filled with an comma sperated list of
         all 6 player names."""
        game_date = self.event.start.date()
        player_tuples = [
            (self.player1.id, self.player1.username, self.player1_score),
            (self.player2.id, self.player2.username, self.player2_score),
            (self.player3.id, self.player3.username, self.player3_score),
            (self.player4.id, self.player4.username, self.player4_score),
            (self.player5.id, self.player5.username, self.player5_score),
            (self.player6.id, self.player6.username, self.player6_score),
        ]
        season_start = settings.MAISTAR_SEASON_START.replace(
            year=game_date.year)

        # sort player by Score:
        player_tuples = sorted(player_tuples, key=lambda player: player[2],
                               reverse=True)
        logging.debug(player_tuples)
        other_player_ranking = 1
        other_player_score = 0
        player_names = []
        ranking = 1
        player_nr = 1
        for player_id, player_name, player_score in player_tuples:
            if player_score == other_player_score:
                player_ranking = other_player_ranking
            else:
                player_ranking = ranking
            setattr(self, "player%d_id" % player_nr, player_id)
            setattr(self, "player%d_score" % player_nr, player_score)
            setattr(self, "player%d_placement" % player_nr, player_ranking)
            other_player_ranking = player_ranking
            other_player_score = player_score
            player_names.append(player_name)
            player_nr += 1
            ranking += 1
        self.player_names = ', '.join(player_names)
        if game_date >= season_start:
            self.season = season_start.year
        else:
            self.season = season_start.year - 1
            super(Game, self).save(**kwargs)


class Ranking(models.Model):
    """the player scores in the ladder for one season. """
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    season = models.PositiveSmallIntegerField(_("Season"))
    placement = models.PositiveIntegerField(blank=True, null=True)
    avg_placement = models.PositiveSmallIntegerField(blank=True, null=True)
    avg_score = models.SmallIntegerField(blank=True, null=True)
    games_count = models.PositiveSmallIntegerField(default=0)
    games_good = models.PositiveSmallIntegerField(default=0)
    games_won = models.PositiveSmallIntegerField(default=0)
    objects = managers.LadderManager()

    class Meta(object):
        """Display rankings by placement, best players first."""
        ordering = ('-season', 'placement', 'avg_placement', '-avg_score',)

    def __str__(self):
        return "Mai-Star Ranking: {user!s}, Season: {season!d}".format(
            user=self.user,
            season=int(self.season)
        )

    def get_absolute_url(self):
        """URL to the subpage of the user profile, listing all Games."""
        return reverse('maistar-player-games', kwargs={
            'username': self.user.username,
            'season': self.season
        })

    def recalculate(self):
        """Recalculate the Mai-Star Ranking for this Player in this Season."""
        self.placement = None
        self.avg_placement = None
        self.avg_score = None
        self.games_count = 0
        self.games_good = 0
        self.games_won = 0
        player_score = 0
        player_placement = 0
        for game in Game.objects.player_games(self.user, self.season):
            placement = 0
            score = 0
            for player in ('player1', 'player2', 'player3', 'player4',
                           'player5', 'player6'):
                if getattr(game, player) == self.user:
                    placement = getattr(game, "%s_placement" % player)
                    score = getattr(game, "%s_score" % player)
            player_placement += placement
            player_score += score
            self.games_count += 1
            self.games_good += 1 if placement <= 3 else 0
            self.games_won += 1 if placement == 1 else 0
        if self.games_count > 0:
            self.avg_placement = round(player_placement / self.games_count)
            self.avg_score = round(player_score / self.games_count)
        self.save()


@receiver([post_delete, post_save], sender=Game)
def update_maistar_ranking(sender, **kwargs):
    """
    A Django signal hook to trigger a recalculation of the rankings as soon
    as a Mai-Star game has been added, deleted, or modified.
    :param sender: The callback function which will be connected to this signal. See Receiver functions for more information.
    :param kwargs: """
    instance = kwargs['instance']
    for player in instance.player_list:
        ranking, created = Ranking.objects.get_or_create(
            user=player['user'],
            season=instance.season
        )
        if created:
            logging.debug('Created ranking for %s in Season %d',
                          player['user'].username, instance.season)
        else:
            logging.debug('Updating ranking for %s in Season %d',
                          player['user'].username, instance.season)
        ranking.recalculate()
    Ranking.objects.calculate_rankings(instance.season)
