"""ObjectManagers for the Django Models used in the Mahjong-Ranking."""
from datetime import date
from . import LOGGER
from django.db import models
from django.conf import settings


class HanchanManager(models.Manager):
    """
    The ObjectManager for models.Hanchan QuerySets. 
    
    It adds many specific filters that makes many queries much easier.
    """
    use_for_related_fields = True

    def confirmed(self, user=None, since=None, until=None, **filter_args):
        """ Return all valid and confirmed Hanchans. 
        
        :param user: Only return Hanchans where this user participated.
        :param since: only return Hanchans played since the given datetime
        :param until: only return Hanchans played until the given datetime
        :param filter_args: To add specific arguments to the Django filter.
        :return: QuerySet Object
        """
        if user:
            return self.user_hanchans(user, confirmed=True, until=until,
                                      **filter_args)
        hanchans = self.filter(confirmed=True, **filter_args)
        if since:
            hanchans = hanchans.filter(start__gt=since)
        if until:
            hanchans = hanchans.filter(start__lte=until)
        return hanchans

    def dan_hanchans(self, user, since = None, **filter_args):
        """ Return all Hanchans where a specific user has participated and had 
        gain dan points and make his gamestats availabale.

        :param user: Only return Hanchans where this user participated.  
        :param since: only return Hanchans played since the given datetime
        :param filter_args: To add specific arguments to the Django filter.
        :return: QuerySet Object
        """
        queryset = self.filter(
            models.Q(player1=user, player1_dan_points__isnull=False) |
            models.Q(player2=user, player2_dan_points__isnull=False) |
            models.Q(player3=user, player3_dan_points__isnull=False) |
            models.Q(player4=user, player4_dan_points__isnull=False)
        ).filter(confirmed=True, **filter_args)
        if since:
            queryset = queryset.filter(start__gt=since)
        queryset = queryset.select_related().order_by('-start')
        [hanchan.get_playerdata(user) for hanchan in queryset]
        return queryset

    def kyu_hanchans(self, user, since = None, **filter_args):
        """ Return all Hanchans where a specific user has participated and had 
        gain kyū points and make his gamestats availabale.

        :param user: Only return Hanchans where this user participated.  
        :param since: only return Hanchans played since the given datetime
        :param filter_args: To add specific arguments to the Django filter.
        :return: QuerySet Object
        """
        queryset = self.filter(
            models.Q(player1=user, player1_kyu_points__isnull=False) |
            models.Q(player2=user, player2_kyu_points__isnull=False) |
            models.Q(player3=user, player3_kyu_points__isnull=False) |
            models.Q(player4=user, player4_kyu_points__isnull=False)
        ).filter(confirmed=True, **filter_args)
        if since:
            queryset = queryset.filter(start__gt=since)
        queryset = queryset.select_related().order_by('-start')
        [hanchan.get_playerdata(user) for hanchan in queryset]
        return queryset

    def season_hanchans(self, user=None, season=None, until=None):
        """Return all Hanchans that belong to a given or the current season.

        :param user: Only return Hanchans where this user participated.  
        :param season: the year of the wanted season, current year if None. 
        :return: QuerySet Object 
        """
        try:
            season = season or until.year
        except AttributeError:
            season = date.today().year
        return self.confirmed(user=user, season=season, until=until)

    def user_hanchans(self, user, since=None, until=None, **filter_args):
        """Return all Hanchans where a specific user has participated. 

        :param user: Return Hanchans where this user participated.  
        :param since: only return Hanchans played since the given datetime
        :param filter_args: To add specific arguments to the Django filter.
        :return: a QuerySet Object
        """
        queryset = self.filter(
            models.Q(player1=user) | models.Q(player2=user) |
            models.Q(player3=user) | models.Q(player4=user)
        )
        queryset = queryset.filter(**filter_args)
        if since:
            queryset = queryset.filter(start__gte=since)
        if until:
            queryset = queryset.filter(start__lte=until)
        queryset = queryset.select_related().order_by('-start')
        [hanchan.get_playerdata(user) for hanchan in queryset]
        return queryset

    def unconfirmed(self, user=None, **filter_args):
        """ Return all Hanchans that have been set to unconfirmed. 

        :param user: Only return Hanchans where this user participated.  
        :param filter_args: To add specific arguments to the Django filter.
        :return: QuerySet Object
        """
        if user:
            return self.user_hanchans(user, confirmed=False, **filter_args)
        else:
            return self.filter(confirmed=False, **filter_args)


class SeasonRankingManager(models.Manager):
    """
    The ObjectManager for models.SeasonRanking QuerySets to handle the 
    Ladderrankings. 

    It adds many specific filters that makes many queries much easier.
    """

    def current_rankings(self):
        """ Returns the Rankings for the current year/season. 

        :return: models.QuerySet
        """
        current_season = date.today().year
        return self.filter(season=current_season)

    @property
    def season_list(self):
        """ Return a list of all availables years/seasons.

        It's useful for select boxes and choice fields.
        :return: list()
        """
        values_list = self.model.objects.values_list('season', flat=True)
        return values_list.order_by('season').distinct()

    def json_data(self, season=None):
        """ Get all Rankings for a given Season and return them as a list of
        dict objects, suitable for JSON exports and other processings.

        :param season: Season that should be exported, current season if empty 
        :return: a list() of dict() objects suiteable for JSON export. 
        """
        season = season or date.today().year
        json_data = list()
        values = self.filter(season=season, placement__isnull=False)
        values = values.values('placement', 'user_id', 'user__username',
                               'user__first_name', 'user__last_name',
                               'avg_placement', 'avg_score',
                               'hanchan_count', 'good_hanchans', 'won_hanchans')
        for user in values:
            json_data.append({
                'placement': user['placement'],
                'user_id': user['user_id'],
                'username': user['user__username'],
                'first_name': user['user__first_name'],
                'last_name': user['user__last_name'],
                'avg_placement': user['avg_placement'],
                'avg_score': user['avg_score'],
                'hanchan_count': user['hanchan_count'],
                'good_hanchans': user['good_hanchans'],
                'won_hanchans': user['won_hanchans']
            })
        return json_data

    def update(self, season=None, until=None, force_recalc=False):
        try:
            season = season or until.year
        except AttributeError:
            season = date.today().year
        if until or force_recalc:
            for ranking in self.filter(season=season):
                ranking.recalculate(until=until)
        for placement, ranking in enumerate(self.season_rankings(season), start=1):
            ranking.placement = placement
            ranking.save(force_update=True, update_fields=['placement'])

    def season_rankings(self, season=None):
        season = season or date.today().year
        rankings = self.filter(
            season=season,
            hanchan_count__gt=settings.MIN_HANCHANS_FOR_LADDER)
        return rankings.order_by('avg_placement', '-avg_score')


class KyuDanRankingManager(models.Manager):
    def json_data(self):
        """ Get all Rankings for a given Season and return them as a list of
        dict objects, suitable for JSON exports and other processings.

        :param season: Season that should be exported, current season if empty 
        :return: a list() of dict() objects suiteable for JSON export. 
        """
        json_data = list()
        values = self.all()
        values = values.values('user_id', 'user__username',
                               'user__first_name', 'user__last_name',
                               'dan', 'dan_points', 'kyu', 'kyu_points',
                               'hanchan_count', 'won_hanchans', 'good_hanchans',
                               'last_hanchan_date')
        for user in values:
            if user['hanchan_count'] == 0:
                continue
            elif user['dan']:
                rank = '{}. Dan'.format(user['dan'])
                points = user['dan_points']
            else:
                rank = '{}. Kyū'.format(user['kyu'])
                points = user['kyu_points']
            json_data.append({
                'user_id': user['user_id'],
                'username': user['user__username'],
                'full_name': " ".join(
                    [user['user__last_name'], user['user__first_name']]),
                'rank': rank,
                'points': points,
                'hanchan_count': user['hanchan_count'],
                'good_hanchans': user['good_hanchans'],
                'won_hanchans': user['won_hanchans'],
                'last_hanchan_date': user['last_hanchan_date']

            })
        return json_data

    def update(self, since=None, until=None, force_recalc=False):
        old_attr = {'dan': None, 'dan_points': None,
                    'kyu': None, 'kyu_points': None, 'won_hanchans': None,
                    'good_hanchans': None, 'hanchan_count': None}
        for ranking in self.all():
            old_attr = {attr: getattr(ranking, attr) for attr in
                        old_attr.keys()}
            ranking.calculate(since=since, until=until,
                              force_recalc=force_recalc)
            for attr, old_value in old_attr.items():
                if getattr(ranking, attr) != old_value:
                    LOGGER.warning(
                        "%(user)s recalc shows differences in %(attr)s! old: %(old)d, new: %(new)d",
                        {'user': ranking.user, 'attr': attr,
                         'old': old_value or 0, 'new': getattr(ranking, attr) or 0}
                    )
