from time import daylight
from zoneinfo import ZoneInfo
from django.db import models
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from tournaments.models import group_stage

from . import BaseModel, Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory, OnlineRegistrationStatus
import uuid
from django.utils import timezone, formats
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

from tournaments.utils.player_search import get_player_name_from_csv
from shared.cryptography import encryption_util
from ..utils.extensions import plural_format

class TeamSortingType(models.IntegerChoices):
    RANK = 1, 'Rank'
    INSCRIPTION_DATE = 2, 'Inscription Date'

class Tournament(BaseModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
    event = models.ForeignKey(Event, blank=True, null=True, on_delete=models.SET_NULL, related_name='tournaments')
    name = models.CharField(max_length=200, null=True, blank=True)
    start_date = models.DateTimeField()
    end_date = models.DateTimeField(null=True, blank=True)
    creation_date = models.DateTimeField()
    is_private = models.BooleanField(default=False)
    # format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
    round_format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
    group_stage_format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
    loser_round_format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True)
    group_stage_sort_mode = models.IntegerField(default=0)
    group_stage_count = models.IntegerField(default=0)
    rank_source_date = models.DateTimeField(null=True, blank=True)
    day_duration = models.IntegerField(default=0)
    team_count = models.IntegerField(default=0)
    team_sorting = models.IntegerField(default=TeamSortingType.INSCRIPTION_DATE, choices=TeamSortingType.choices)
    federal_category = models.IntegerField(default=FederalCategory.MEN, choices=FederalCategory.choices) # optional ?
    federal_level_category = models.IntegerField(default=FederalLevelCategory.P100, choices=FederalLevelCategory.choices)
    federal_age_category = models.IntegerField(default=FederalAgeCategory.SENIOR, choices=FederalAgeCategory.choices)
    #group_stage_court_count = models.IntegerField(null=True, blank=True)
    #seed_count = models.IntegerField(default=0)
    closed_registration_date = models.DateTimeField(null=True, blank=True)
    group_stage_additional_qualified = models.IntegerField(default=0)
    court_count = models.IntegerField(default=2)
    prioritize_club_members = models.BooleanField()
    qualified_per_group_stage = models.IntegerField(default=0)
    teams_per_group_stage = models.IntegerField(default=0)
    entry_fee = models.FloatField(default=20.0, null=True, blank=True)
    global_id = models.CharField(max_length=100, null=True, blank=True) #represent the payment crypted string
    is_deleted = models.BooleanField(default=False)
    local_id = models.CharField(max_length=100, null=True, blank=True) #represent the is_canceled crypted string
    additional_estimation_duration = models.IntegerField(default=0)
    publish_teams = models.BooleanField(default=False)
    hide_teams_weight = models.BooleanField(default=False)
    publish_summons = models.BooleanField(default=False)
    publish_group_stages = models.BooleanField(default=False)
    publish_brackets = models.BooleanField(default=False)
    should_verify_bracket = models.BooleanField(default=False)
    should_verify_group_stage = models.BooleanField(default=False)
    publish_tournament = models.BooleanField(default=False)
    hide_points_earned = models.BooleanField(default=False)
    publish_rankings = models.BooleanField(default=False)
    loser_bracket_mode = models.IntegerField(default=0)
    initial_seed_round = models.IntegerField(default=0)
    initial_seed_count = models.IntegerField(default=0)
    enable_online_registration = models.BooleanField(default=False)  # Equivalent to Bool = false
    registration_date_limit = models.DateTimeField(null=True, blank=True)  # Equivalent to Date? = nil
    opening_registration_date = models.DateTimeField(null=True, blank=True)  # Equivalent to Date? = nil
    waiting_list_limit = models.IntegerField(null=True, blank=True)  # Equivalent to Int? = nil
    account_is_required = models.BooleanField(default=True)
    license_is_required = models.BooleanField(default=True)
    minimum_player_per_team = models.IntegerField(default=2)
    maximum_player_per_team = models.IntegerField(default=2)
    information = models.CharField(max_length=4000, null=True, blank=True)
    umpire_custom_mail = models.EmailField(null=True, blank=True)
    umpire_custom_contact = models.CharField(max_length=200, null=True, blank=True)
    umpire_custom_phone = models.CharField(max_length=15, null=True, blank=True)
    hide_umpire_mail = models.BooleanField(default=False)
    hide_umpire_phone = models.BooleanField(default=True)
    disable_ranking_federal_ruling = models.BooleanField(default=False)

    def delete_dependencies(self):
        for team_registration in self.team_registrations.all():
            team_registration.delete_dependencies()
            team_registration.delete()
        for gs in self.group_stages.all():
            gs.delete_dependencies()
            gs.delete()
        for round in self.rounds.all():
            round.delete_dependencies()
            round.delete()
        for draw_log in self.draw_logs.all():
            # draw_log.delete_dependencies()
            draw_log.delete()

    def __str__(self):
        if self.name:
            return self.name
        else:
            return self.display_name()

    def is_canceled(self):
        if self.local_id:
            decrypted = encryption_util.decrypt_aes_gcm(self.local_id)
            value = int(decrypted[18])
            if 0 <= value <= 4:
                return True
            else:
                return False
        else:
            return False

    def payment(self):
        if self.global_id:
            decrypted = encryption_util.decrypt_aes_gcm(self.global_id)
            value = int(decrypted[18])
            return TournamentPayment(value)
        else:
            return None

    def display_name(self):
        if self.name:
            if self.federal_level_category == FederalLevelCategory.UNLISTED:
                return self.name
            return self.base_name() + " " + self.name
        else:
            return self.base_name()

    def broadcast_display_name(self):
        if self.name:
            if self.federal_level_category == FederalLevelCategory.UNLISTED:
                return self.name
            return self.short_base_name() + " " + self.name
        else:
            return self.base_name()

    def broadcast_event_display_name(self):
        if self.event is not None:
            return self.event.display_name()
        else:
            return " "

    def base_name(self):
        return f"{self.level()} {self.category()}"

    def short_base_name(self):
        category = self.category()
        if len(category) > 0:
            return f"{self.level()}{category[0]}"
        else:
            return self.level()

    def filter_name(self):
        components = [self.formatted_start_date(), self.short_base_name()]
        if self.event and self.event.club and self.event.club.name:
            components.append(self.event.club.name)
        elif self.event and self.event.name:
            components.append(self.event.name)
        elif self.name:
            components.append(self.name)
        return (' ').join(components)

    def timezone(self):
        tz = 'CET'
        if self.event and self.event.club:
            tz = self.event.club.timezone
        return ZoneInfo(tz)

    def local_start_date(self):
        timezone = self.timezone()
        return self.start_date.astimezone(timezone)

    def local_start_date_formatted(self):
        return formats.date_format(self.local_start_date(), format='l j F Y H:i').capitalize()

    def level(self):
        if self.federal_level_category == 0:
            return "Anim."
        return self.get_federal_level_category_display()

    def category(self):
        return self.get_federal_category_display()

    def age(self):
        if self.federal_age_category == 0:
            return None
        return self.get_federal_age_category_display()

    def formatted_start_date(self):
        return self.local_start_date().strftime("%d/%m/%y")

    def in_progress(self):
        return self.end_date is None

    def creator(self):
        if self.event and self.event.creator:
            return self.event.creator.username
        else:
            return None

    def private_label(self):
        if self.is_private:
            return "Privé"
        else:
            return "Public"

    def summon_count_display(self):
        teams = self.team_summons()
        if teams is not None and len(teams) > 0:
            return f"{len(teams)} équipes convoquées"
        else:
            return None

    def ranking_count_display(self):
        teams = self.rankings()
        if teams is not None and len(teams) > 0:
            return f"{len(teams)} équipes"
        else:
            return None

    def registration_count_display(self):
        teams = self.teams(True)
        if teams is not None and len(teams) == 1:
            return f"{len(teams)} équipe inscrite"
        elif teams is not None and len(teams) > 1:
            return f"{len(teams)} équipes inscrites"
        else:
            return None

    def tournament_status_display(self):
            if self.is_canceled() is True:
                return "Annulé"

            teams = self.teams(True)
            if self.supposedly_in_progress() or self.end_date is not None or self.should_be_over():
                teams = [t for t in teams if t.stage != "Attente"]
                if teams is not None and len(teams) > 0:
                    word = "équipe"
                    if len(teams) > 1:
                        word = word + "s"
                    return f"{len(teams)} {word}"
                else:
                    return None

            registration_status = None
            if self.enable_online_registration == True:
                registration_status = self.get_online_registration_status().status_localized()
            if teams is not None and len(teams) > 0:
                word = "inscription"
                if len(teams) > 1:
                    word = word + "s"
                if registration_status is not None:
                    return f"{registration_status}\n{len(teams)} {word}"
                else:
                    return f"{len(teams)} {word}"
            else:
                if registration_status is not None:
                    return f"{registration_status}"
                return None

    def name_and_event(self):
        event_name = None
        if self.event:
            event_name = self.event.name

        if event_name and self.name:
            return event_name + " : " + self.name
        elif event_name:
            return event_name
        elif self.name:
            return self.name
        else:
            return None

    def team_summons(self):
        summons = []
        if self.supposedly_in_progress() and self.end_date is None:
            print('>>> team_summons supposedly_in_progress')
            for team in self.teams(False):
                names = team.names
                stage = team.stage
                weight = team.weight
                summon = TeamSummon(team.team_registration.id, names, team.date, weight, stage, "", team.image, self.day_duration)
                summons.append(summon)
        else:
            print('>>> team_summons')
            for team_registration in self.team_registrations.all():
                if team_registration.is_valid_for_summon():
                    next_match = team_registration.next_match()
                    if next_match and next_match.start_date is not None:
                        names = team_registration.team_names()
                        stage = next_match.summon_stage_name()
                        weight = team_registration.weight
                        summon = TeamSummon(team_registration.id, names, next_match.local_start_date(), weight, stage, next_match.court_name(next_match.court_index), team_registration.logo,  self.day_duration)
                        summons.append(summon)

        summons.sort(key=lambda s: (s.date is None, s.date or datetime.min))
        return summons

    def has_summons(self):
        for team_registration in self.team_registrations.all():
            if team_registration.is_valid_for_summon():
                next_match = team_registration.next_match()
                if next_match and next_match.start_date is not None:
                    return True
        return False

    def rankings(self):
        rankings = []
        for team_registration in self.team_registrations.all():
            if team_registration.walk_out is False and team_registration.final_ranking is not None:
                names = team_registration.team_names()
                ranking = team_registration.final_ranking
                points = team_registration.points_earned
                team = TeamRanking(team_registration.id, names, ranking, points, team_registration.logo)
                rankings.append(team)

        rankings.sort(key=lambda r: r.ranking)
        return rankings

    def get_team_waiting_list_position(self, team_registration):
        # Use the teams method to get sorted list of teams
        all_teams = self.teams(True)
        index = -1
        # Find position of team in all teams list
        for i, team in enumerate(all_teams):
            if team.team_registration.id == team_registration.id:
                index = i

        # Check if team_count exists
        if self.team_count:
            # Team is not in list
            if index < self.team_count:
                print("Team is not in list", index, self.team_count)
                return -1
            # Return position in waiting list relative to target count
            print("Return position in waiting list relative to target count", index, self.team_count)
            return index - self.team_count
        else:
            print("else", index, self.team_count)
            return -1

    def group_stage_spots(self):
        """Returns the total number of spots in all group stages."""
        return sum(gs.size for gs in self.group_stages.all())

    def teams(self, include_waiting_list, un_walk_out_team=None):
        complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams = self.computed_teams(un_walk_out_team)
        all_teams = self.sort_teams(include_waiting_list, complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams)
        return all_teams

    def computed_teams(self, un_walk_out_team=None):
        # Initialize team categories
        complete_teams = []
        wildcard_bracket = []
        wildcard_group_stage = []
        waiting_teams = []

        # Get registration cutoff date
        closed_date = self.closed_registration_date

        # Process each team registration
        for db_team_reg in self.team_registrations.all():
            team_reg = db_team_reg
            if un_walk_out_team and team_reg.id == un_walk_out_team.id:
                team_reg = un_walk_out_team
            if team_reg.out_of_tournament():
                continue

            # Create team item
            team = TeamItem(team_reg)

            # Determine if registration is valid based on date
            is_valid = (
                closed_date is None or
                team_reg.registration_date is None or
                (team_reg.registration_date and team_reg.registration_date <= closed_date)
            )

            # Set initial stage
            if team_reg.group_stage_position is not None:
                team.set_stage("Poule")
            elif team_reg.bracket_position is not None:
                team.set_stage("Tableau")
            else:
                team.set_stage("Attente")

            # Categorize team
            if team_reg.wild_card_bracket:
                wildcard_bracket.append(team)
            elif team_reg.wild_card_group_stage:
                wildcard_group_stage.append(team)
            elif is_valid:
                complete_teams.append(team)
            else:
                waiting_teams.append(team)
                wildcard_bracket = []
        return complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams

    def sort_teams(self, include_waiting_list, complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams):
        # Initialize group stage spots
        group_stage_spots = self.group_stage_spots()
        bracket_seeds = self.team_count - group_stage_spots - len(wildcard_bracket)
        group_stage_team_count = group_stage_spots - len(wildcard_group_stage)
        if group_stage_team_count < 0:
            group_stage_team_count = 0
        if bracket_seeds < 0:
            bracket_seeds = 0

        # Sort teams based on tournament rules
        if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
            complete_teams.sort(key=lambda t: (
                t.registration_date is None,
                t.registration_date or datetime.min,
                t.initial_weight,
                t.team_registration.id
            ))
            waiting_teams.sort(key=lambda t: (
                t.registration_date is None,
                t.registration_date or datetime.min,
                t.initial_weight,
                t.team_registration.id
            ))
        else:
            complete_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
            waiting_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))

        wildcard_group_stage.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
        wildcard_bracket.sort(key=lambda t: (t.initial_weight, t.team_registration.id))

        # Split teams into main bracket and waiting list
        computed_team_count = self.team_count - len(wildcard_bracket) - len(wildcard_group_stage)
        if computed_team_count < 0:
            computed_team_count = 0
        qualified_teams = complete_teams[:computed_team_count]
        excess_teams = complete_teams[computed_team_count:]

        qualified_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
        excess_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))

        # Combine all waiting list teams
        waiting_list = excess_teams + waiting_teams
        if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
            waiting_list.sort(key=lambda t: (
                t.registration_date is None,
                t.registration_date or datetime.min,
                t.initial_weight,
                t.team_registration.id
            ))
        else:
            waiting_list.sort(key=lambda t: (t.initial_weight, t.team_registration.id))

        # Return final sorted list
        bracket_teams = qualified_teams[:bracket_seeds] + wildcard_bracket
        gs_teams = qualified_teams[bracket_seeds:(bracket_seeds+group_stage_team_count)] + wildcard_group_stage
        bracket_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
        gs_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
        all_teams = bracket_teams + gs_teams
        for team in bracket_teams:
            team.set_stage("Tableau")
        for team in gs_teams:
            team.set_stage("Poule")

        if include_waiting_list:
            all_teams.extend(waiting_list)
        return all_teams

    def match_groups(self, broadcasted, group_stage_id, round_id):

        display_brackets = self.display_matches()

        match_groups = []
        if group_stage_id:
            group_stage = self.group_stages.filter(id=group_stage_id).first()
            match_groups.append(self.group_stage_match_group(group_stage, broadcasted, hide_empty_matches=False))
        elif round_id:
            round = self.rounds.filter(id=round_id).first()
            if round and display_brackets is True:
                match_groups = self.round_match_groups(round, broadcasted, hide_empty_matches=False)
        else:
            match_groups = self.all_groups(broadcasted)

        return match_groups

    def all_groups(self, broadcasted):
        groups = []

        if self.display_matches():
            rounds = self.rounds.filter(parent=None, group_stage_loser_bracket=False).all().order_by('index')
            for round in rounds:
                groups.extend(self.round_match_groups(round, broadcasted, hide_empty_matches=True))

        if self.display_group_stages():
            for round in self.rounds.filter(parent=None, group_stage_loser_bracket=True).all().order_by('index'):
                groups.extend(self.round_match_groups(round, broadcasted, hide_empty_matches=True))

            group_stages = sorted(self.sorted_group_stages(), key=lambda s: (-s.step, s.index))
            for group_stage in group_stages:
                group = self.group_stage_match_group(group_stage, broadcasted, hide_empty_matches=True)
                if group:
                    groups.append(group)

        return groups

    def group_stage_match_group(self, group_stage, broadcasted, hide_empty_matches):

        if group_stage is None:
            return None
        matches = group_stage.matches.all()
        if hide_empty_matches:
            matches = [m for m in matches if m.should_appear()]
        else:
            matches = [m for m in matches if m.disabled is False]

        matches.sort(key=lambda m: (m.start_date is None, m.end_date is not None, m.start_date, m.index))

        if matches:
            return self.create_match_group(group_stage.display_name(), matches)
        else:
            return None

    def round_match_groups(self, round, broadcasted, hide_empty_matches):
        groups = []

        matches = round.matches.order_by('index').all()
        if hide_empty_matches:
            matches = [m for m in matches if m.should_appear()]
        else:
            matches = [m for m in matches if m.disabled is False]

        if round and matches:
            matches.sort(key=lambda m: m.index)
            group = self.create_match_group(round.name(), matches)
            groups.append(group)

            ranking_matches = round.ranking_matches(hide_empty_matches)
            if hide_empty_matches:
                ranking_matches = [m for m in ranking_matches if m.should_appear()]
            else:
                ranking_matches = [m for m in ranking_matches if m.disabled is False]

            if len(ranking_matches) > 0:
                ranking_matches.sort(
                    key=lambda m: (
                        m.round.index,       # Sort by Round index first
                        m.round.get_depth(),
                        m.name or '', # Then by Round depth, using empty string if name is None
                    )
                )
                group = self.create_match_group('Matchs de classement', ranking_matches)
                groups.append(group)

        return groups

    def create_match_group(self, name, matches, round_id=None):
        matches = list(matches)
        live_matches = [match.live_match() for match in matches]
        # Filter out matches that have a start_date of None
        valid_matches = [match for match in matches if match.start_date is not None]

        formatted_schedule = ''
        if valid_matches and self.day_duration >= 7:
            # Find the first match by start date
            first_match = min(valid_matches, key=lambda match: match.start_date)

            # Format the date
            timezone = first_match.tournament().timezone()
            local_start = first_match.start_date.astimezone(timezone)
            time_format = 'l d M'
            formatted_schedule = f" - {formats.date_format(local_start, format=time_format)}"

        return MatchGroup(name, live_matches, formatted_schedule, round_id)

    def live_group_stages(self):
        group_stages = self.sorted_group_stages()
        return [gs.live_group_stages() for gs in group_stages]

    def sorted_group_stages(self):
        # Get all group stages and sort by step (descending) and index (ascending)
        group_stages = self.group_stages.all().order_by('-step', 'index')

        # List to collect live group stages from finished steps
        filtered = []

        for group_stage in group_stages:
            if group_stage.step > 0:
                # Check the previous step's group stages
                previous_step_group_stages = self.group_stages.filter(step=group_stage.step - 1)

                # Check if all previous step group stages are completed
                if all(gs.is_completed() for gs in previous_step_group_stages):
                    filtered.append(group_stage)
            else:
                # Always include step 0
                filtered.append(group_stage)

        return filtered

    def get_previous_live_group_stages(self, step):
        previous_step_group_stages = self.group_stages.filter(step=step).order_by('index')
        return [gs.live_group_stages() for gs in previous_step_group_stages]

    def last_group_stage_step(self):
        live_group_stages = self.sorted_group_stages()

        # Filter to find the last running step
        last_running_step = max(gs.step for gs in live_group_stages) if live_group_stages else None

        if last_running_step is not None:
            # Get only group stages from the last running step
            group_stages_last_step = [gs for gs in live_group_stages if gs.step == last_running_step]
            return group_stages_last_step
        else:
            return []

    def broadcast_content(self):

        matches, group_stages = self.broadcasted_matches_and_group_stages()
        group_stages_dicts = [gs.to_dict() for gs in group_stages]

        # if now is before the first match, we want to show the summons + group stage or first matches
        # change timezone to datetime to avoid the bug RuntimeWarning: DateTimeField Tournament.start_date received a naive datetime (2024-05-16 00:00:00) while time zone support is active.
        current_time = timezone.now()
        tournament_start = self.local_start_date()
        one_hour_before_start = tournament_start - timedelta(hours=1)

        if current_time < one_hour_before_start:
            team_summons_dicts = [summon.to_dict() for summon in self.team_summons()]
            if group_stages:
                return {
                    'matches': [],
                    'group_stages': group_stages_dicts,
                    'summons': team_summons_dicts,
                    'event_title' : self.broadcast_event_display_name(),
                    'tournament_title' : self.broadcast_display_name(),
                    'rankings' : []
                }
            else:
                live_matches_dicts = [match.live_match().to_dict() for match in matches]
                return {
                    'matches': live_matches_dicts,
                    'group_stages': [],
                    'summons': team_summons_dicts,
                    'event_title' : self.broadcast_event_display_name(),
                    'tournament_title' : self.broadcast_display_name(),
                    'rankings' : []
                }
        elif self.end_date is not None:
            live_matches_dicts = [match.live_match().to_dict() for match in matches]
            team_rankings_dicts = [ranking.to_dict() for ranking in self.rankings()]
            return {
                'matches': live_matches_dicts,
                'group_stages': [],
                'summons': [],
                'event_title' : self.broadcast_event_display_name(),
                'tournament_title' : self.broadcast_display_name(),
                'rankings' : team_rankings_dicts
            }
        else: # we want to display the broadcasted content
            live_matches_dicts = [match.live_match().to_dict() for match in matches]
            return {
                'matches': live_matches_dicts,
                'group_stages': group_stages_dicts,
                'summons': [],
                'rankings' : [],
                'event_title' : self.broadcast_event_display_name(),
                'tournament_title' : self.broadcast_display_name(),
            }

    def broadcasted_matches_and_group_stages(self):
        matches = []
        group_stages = []

        if self.group_stages.count() > 0 and self.no_bracket_match_has_started():
            group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()]
            matches = self.broadcasted_group_stages_matches()
            first_round = self.first_round()
            if first_round and self.has_all_group_stages_started():
                matches.extend(first_round.get_matches_recursive(True))
        else:
            current_round = self.round_to_show()
            if current_round:
                print(f'current_round = {current_round.index} / parent = {current_round.parent}')
                all_upper_matches_are_over = current_round.all_matches_are_over()
                if all_upper_matches_are_over is False:
                    matches.extend(current_round.get_matches_recursive(True))

                # Add full matches from the next rounds
                next_round = self.round_for_index(current_round.index - 1)
                if next_round:
                    # print('next round')
                    matches.extend(next_round.get_matches_recursive(True))

                if all_upper_matches_are_over is True:
                    # print('all_upper_matches_are_over')
                    matches.extend(current_round.get_matches_recursive(True))

                # Add matches from the previous round or group_stages
                previous_round = self.round_for_index(current_round.index + 1)
                if previous_round:
                    # print('previous_round')
                    matches.extend(previous_round.get_matches_recursive(True))

                    previous_previous_round = self.round_for_index(current_round.index + 2)
                    if previous_previous_round:
                        previous_previous_matches = previous_previous_round.get_matches_recursive(True)
                        previous_previous_matches = [m for m in previous_previous_matches if m.end_date is None]
                        matches.extend(previous_previous_matches)
                else:
                    print('group_stages')
                    group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()]
            else:
                first_round = self.first_round()
                if first_round:
                    matches.extend(first_round.get_matches_recursive(True))

        return matches, group_stages

    def no_bracket_match_has_started(self):
        matches = []
        for round in self.rounds.all():
            for match in round.matches.all():
                if match.started():
                    return False
        return True

    def all_matches(self, hide_empty_matches):
        matches = []
        for round in self.rounds.all():
            matches.extend(round.all_matches(hide_empty_matches))
        for group_stage in self.group_stages.all():
            matches.extend(group_stage.matches.all())

        matches = [m for m in matches if m.should_appear()]

        return matches

    def group_stage_matches(self):
        matches = []
        for group_stage in self.group_stages.all():
            matches.extend(group_stage.matches.all())
        return matches

    def group_stages_running(self):
        if len(self.group_stages.all()) > 0:
            # check le debut des match de Round
            matches = self.group_stage_matches()
            running_group_stage_matches = [m for m in matches if m.end_date is None]
            return len(running_group_stage_matches) > 0
        else:
            return False

    def first_unfinished_match(self):
        matches = [m for m in self.all_matches(False) if m.start_date and m.end_date is None]
        # print(f'first_unfinished_match > match len: {len(matches)}')
        matches.sort(key=lambda m: m.start_date)
        main_bracket_matches = [m for m in matches if m.round and m.round.parent is None]
        if main_bracket_matches:
            return main_bracket_matches[0]

        if matches:
            return matches[0]
        else:
            return None

    def round_to_show(self):
        # print('===== round_to_show')
        last_started_match = self.first_unfinished_match()
        if last_started_match and last_started_match.round:
            # print(f'last_started_match = {last_started_match.name}')
            current_round = last_started_match.round.root_round()
            # print(f'round_to_show > current_round: {current_round.name()}')
            if current_round:
                return current_round

        # all started matches have ended, possibly
        last_finished_match = self.last_finished_match()
        if last_finished_match:
            round = last_finished_match.round
            if round is None: # when the last finished match is in the group stage
                round = self.rounds.filter(parent__isnull=True).order_by('-index').first()

            if round:
                # print(f'last_finished_match = {last_finished_match.name}')
                round_root_index = round.root_round().index
                # print(f'round_index = {round_root_index}')
                if round_root_index == 0:
                    return round
                else:
                    round = self.rounds.filter(parent=None,index=round_root_index-1).first()
                    if round:
                        return round
                    else:
                        return None
        return None

    def last_started_match(self):
        matches = [m for m in self.all_matches(False) if m.start_date]
        matches.sort(key=lambda m: m.start_date, reverse=True)
        return matches[0] if matches else None

    def last_finished_match(self):
        matches = [m for m in self.all_matches(False) if m.end_date]
        matches.sort(key=lambda m: m.end_date, reverse=True)
        return matches[0] if matches else None

    def round_for_index(self, index):
        return self.rounds.filter(index=index, parent=None).first()

    def first_round(self):
        main_rounds = list(self.rounds.filter(parent=None))
        main_rounds.sort(key=lambda r: r.index, reverse=True)
        return main_rounds[0] if main_rounds else None

    def broadcasted_group_stages_matches(self):
        matches = []
        group_stages = self.elected_broadcast_group_stages()
        group_stages.sort(key=lambda gs: (gs.index, gs.start_date is None, gs.start_date))
        for group_stage in group_stages:

            matches.extend(group_stage.matches.all())

        if len(matches) > 16:
            # if more than 16 groupstage matches
            now = timezone.now()
            future_threshold = now + timedelta(hours=1)
            past_threshold = now - timedelta(hours=1)
            matches = [m for m in matches if m.should_appear() and
                    (m.start_date is None or m.start_date <= future_threshold) and  # Not starting in more than 1h
                    (m.end_date is None or m.end_date >= past_threshold)]  # Not finished for more than 1h
            matches = matches[:16]
        matches.sort(key=lambda m: (m.start_date is None, m.end_date is not None, m.start_date, m.index))

        group_stage_loser_bracket = list(self.rounds.filter(parent=None, group_stage_loser_bracket=True).all())
        if len(group_stage_loser_bracket) > 0:
            loser_matches = group_stage_loser_bracket[0].all_matches(True)
            loser_matches = [m for m in loser_matches if m.should_appear()]
            loser_matches.sort(key=lambda m: (m.start_date is None, m.end_date is not None, m.start_date, m.index))
            matches.extend(loser_matches)

        return matches

    def elected_broadcast_group_stages(self):
        group_stages = list(self.group_stages.all())
        started = [gs for gs in group_stages if gs.starts_soon()]
        if len(started) > 0:
            return started
        else:
            return group_stages

    def display_rankings(self):
        if self.supposedly_in_progress():
            return True
        if self.end_date is not None:
            return True
        return False

    def display_tournament(self):
        if self.publish_tournament:
            return True

        is_build_and_not_empty = self.is_build_and_not_empty()

        if self.end_date is not None:
            return is_build_and_not_empty
        if timezone.now() >= self.local_start_date():
            return is_build_and_not_empty
        minimum_publish_date = self.creation_date.replace(hour=9, minute=0) + timedelta(days=1)
        return timezone.now() >= timezone.localtime(minimum_publish_date)

    def display_teams(self):
        if self.end_date is not None:
            return self.has_team_registrations()
        if self.publish_teams:
            return self.has_team_registrations()
        if timezone.now() >= self.local_start_date():
            return self.has_team_registrations()
        return False

    def has_team_registrations(self):
        return self.team_registrations.count() > 0

    def display_summons(self):
        if self.end_date is not None:
            return False
        if self.publish_summons:
            return self.has_summons()
        if timezone.now() >= self.local_start_date():
            return self.has_summons()
        return False

    def display_group_stages(self):
        if len(self.group_stages.all()) == 0:
            return False
        if self.end_date is not None:
            return True
        if self.publish_group_stages:
            return True

        first_group_stage_start_date = self.group_stage_start_date()
        if first_group_stage_start_date is None:
            return timezone.now() >= self.local_start_date()
        else:
            return timezone.now() >= first_group_stage_start_date

    def group_stage_start_date(self):
        group_stages = [gs for gs in self.group_stages.all() if gs.start_date is not None]
        if len(group_stages) == 0:
            return None

        timezone = self.timezone()
        return min(group_stages, key=lambda gs: gs.start_date).start_date.astimezone(timezone)

    def display_matches(self):
        if self.end_date is not None:
            return True
        bracket_matches = self.bracket_matches()
        if len(bracket_matches) == 0:
            return self.display_group_stages()
        if self.publish_brackets:
            return True

        first_match_start_date = self.first_match_start_date(bracket_matches)
        if first_match_start_date is None:
            return timezone.now() >= self.local_start_date()

        bracket_start_date = self.getEightAm(first_match_start_date)

        if bracket_start_date < self.local_start_date():
            bracket_start_date = self.local_start_date()

        group_stage_start_date = self.group_stage_start_date()
        if group_stage_start_date is not None:
            if bracket_start_date < group_stage_start_date:
                return timezone.now() >=first_match_start_date

        if timezone.now() >= bracket_start_date:
            return True

        return False


    def bracket_matches(self):
        matches = []
        for round in self.rounds.all():
            matches.extend(round.all_matches(False))
        return matches

    def first_match_start_date(self, bracket_matches):
        matches = [m for m in bracket_matches if m.start_date is not None]
        if len(matches) == 0:
            return None
        return min(matches, key=lambda m: m.start_date).local_start_date()

    def getEightAm(self, date):
        return date.replace(hour=8, minute=0, second=0, microsecond=0, tzinfo=date.tzinfo)

    def supposedly_in_progress(self):
        # end = self.start_date + timedelta(days=self.day_duration + 1)
        # return self.start_date.replace(hour=0, minute=0) <= timezone.now() <= end

        timezoned_datetime = self.local_start_date()
        end = timezoned_datetime + timedelta(days=self.day_duration + 1)
        now = timezone.now()

        start = timezoned_datetime.replace(hour=0, minute=0)

        # print(f"timezoned_datetime: {timezoned_datetime}")
        # print(f"tournament end date: {end}")
        # print(f"current time: {now}")
        # print(f"tournament start: {start}")
        # print(f"start <= now <= end: {start <= now <= end}")

        return start <= now <= end

    def starts_in_the_future(self):
        # tomorrow = datetime.now().date() + timedelta(days=1)

        timezoned_datetime = self.local_start_date()
        start = timezoned_datetime.replace(hour=0, minute=0)
        now = timezone.now()

        return start >= now

    def should_be_over(self):
        if self.end_date is not None:
            return True

        timezoned_datetime = self.local_start_date()
        end = timezoned_datetime + timedelta(days=self.day_duration + 1)
        now = timezone.now()
        return now >= end and self.is_build_and_not_empty() and self.nearly_over()

    def nearly_over(self):
        # First check group stages if they exist
        group_stages = list(self.group_stages.all())  # Use prefetched data
        if group_stages:
            # Check if all group stages are completed
            for group_stage in group_stages:
                # Use the is_completed method
                if group_stage.is_completed():
                    return True

        # If no group stages, check semi-finals
        semifinals = self.rounds.filter(index=1, parent=None).first()  # Use prefetched data
        if semifinals:
            # Check if any match in semi-finals has started
            for match in semifinals.matches.all():  # Use prefetched data
                if match.start_date is not None and match.is_ready():
                    return True

        return False

    def display_points_earned(self):
        return self.federal_level_category != FederalLevelCategory.UNLISTED and self.hide_points_earned is False

    def hide_weight(self):
        return self.hide_teams_weight

    def is_build_and_not_empty(self):
        if hasattr(self, '_prefetched_objects_cache'):
            # Use prefetched data if available
            has_group_stages = 'group_stages' in self._prefetched_objects_cache and len(self.group_stages.all()) > 0
            has_rounds = 'rounds' in self._prefetched_objects_cache and len(self.rounds.all()) > 0
            has_team_registrations = 'team_registrations' in self._prefetched_objects_cache and len(self.team_registrations.all()) >= 4
        else:
            # Fall back to database queries if not prefetched
            has_group_stages = self.group_stages.count() > 0
            has_rounds = self.rounds.count() > 0
            has_team_registrations = self.team_registrations.count() >= 4

        return (has_group_stages or has_rounds) and has_team_registrations

    def day_duration_formatted(self):
        return plural_format("jour", self.day_duration)

    def has_club_address(self):
        if self.event.club:
            return self.event.club.has_address()
        else:
            return False

    def has_all_group_stages_started(self):
        for group_stage in self.group_stages.all():
            if group_stage.has_at_least_one_started_match() is False:
                return False
        return True

    def options_online_registration(self):
        options = []

        # Date d'ouverture
        if self.opening_registration_date:
            date = formats.date_format(timezone.localtime(self.opening_registration_date), format='j F Y H:i')
            options.append(f"Ouverture des inscriptions le {date}")

        # Date limite
        if self.registration_date_limit:
            date = formats.date_format(timezone.localtime(self.registration_date_limit), format='j F Y H:i')
            options.append(f"Clôture des inscriptions le {date}")

        # Cible d'équipes
        if self.team_count:
            options.append(f"Maximum {self.team_count} équipes")

        # Liste d'attente
        if self.waiting_list_limit:
            options.append(f"Liste d'attente limitée à {self.waiting_list_limit} équipes")

        # Options d'inscription
        if self.account_is_required:
            options.append("Compte requis")
        if self.license_is_required:
            options.append("Licence requise")

        # Joueurs par équipe
        min_players = self.minimum_player_per_team
        max_players = self.maximum_player_per_team
        if min_players == max_players:
            options.append(f"{min_players} joueurs par équipe")
        else:
            options.append(f"Entre {min_players} et {max_players} joueurs par équipe")

        return options

    def online_register_is_enabled(self):
        if self.supposedly_in_progress():
            return False
        if self.closed_registration_date is not None:
            return False
        if self.end_date is not None:
            return False

        now = timezone.now()

        # Check if online registration is enabled
        if not self.enable_online_registration:
            return False

        # Check opening registration date
        if self.opening_registration_date is not None:
            timezoned_datetime = timezone.localtime(self.opening_registration_date)
            if now < timezoned_datetime:
                return False

        # Check registration date limit
        if self.registration_date_limit is not None:
            timezoned_datetime = timezone.localtime(self.registration_date_limit)
            if now > timezoned_datetime:
                return False

        # Check target team count and waiting list limit
        if self.team_count is not None:
            current_team_count = self.team_registrations.exclude(walk_out=True).count()
            if current_team_count >= self.team_count:
                if self.waiting_list_limit is not None:
                    waiting_list_count = current_team_count - self.team_count
                    if waiting_list_count >= self.waiting_list_limit:
                        return False
        return True

    def get_online_registration_status(self):
            if self.supposedly_in_progress():
                return OnlineRegistrationStatus.ENDED
            if self.closed_registration_date is not None:
                return OnlineRegistrationStatus.ENDED
            if self.end_date is not None:
                return OnlineRegistrationStatus.ENDED_WITH_RESULTS

            now = timezone.now()

            if self.opening_registration_date is not None:
                timezoned_datetime = timezone.localtime(self.opening_registration_date)
                if now < timezoned_datetime:
                    return OnlineRegistrationStatus.NOT_STARTED

            if self.registration_date_limit is not None:
                timezoned_datetime = timezone.localtime(self.registration_date_limit)
                if now > timezoned_datetime:
                    return OnlineRegistrationStatus.ENDED

            if self.team_count is not None:
                # Get all team registrations excluding walk_outs
                current_team_count = self.team_registrations.exclude(walk_out=True).count()
                if current_team_count >= self.team_count:
                    if self.waiting_list_limit is not None:
                        waiting_list_count = current_team_count - self.team_count
                        if waiting_list_count >= self.waiting_list_limit:
                            return OnlineRegistrationStatus.WAITING_LIST_FULL
                    return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE
            return OnlineRegistrationStatus.OPEN

    def is_unregistration_possible(self):
        # Check if tournament has started
        if self.supposedly_in_progress():
            return False

        if self.closed_registration_date is not None:
            return False

        # Check if tournament is finished
        if self.end_date is not None:
            return False

        # Check if registration is closed
        if self.registration_date_limit is not None:
            if timezone.now() > timezone.localtime(self.registration_date_limit):
                return False

        # Otherwise unregistration is allowed
        return True

    def get_waiting_list_position(self):
        # If no target team count exists, no one goes to waiting list
        if self.team_count is None:
            return -1

        # Get count of active teams (not walked out)
        current_team_count = self.team_registrations.exclude(walk_out=True).count()

        # If current count is less than target count, next team is not in waiting list
        if current_team_count < self.team_count:
            return -1

        # If we have a waiting list limit
        if self.waiting_list_limit is not None:
            waiting_list_count = current_team_count - self.team_count
            # If waiting list is full
            if waiting_list_count >= self.waiting_list_limit:
                return -1
            # Return waiting list position
            return waiting_list_count

        # In waiting list with no limit
        return current_team_count - self.team_count

    def build_tournament_type_array(self):
        tournament_details = []
        if self.federal_level_category > 0:
            tournament_details.append(self.level())
        if self.category():
            tournament_details.append(self.category())
        if self.age() and self.federal_age_category != FederalAgeCategory.SENIOR:
            tournament_details.append(self.age())
        return tournament_details

    def build_tournament_type_str(self):
        tournament_details = self.build_tournament_type_array()
        return " ".join(filter(None, tournament_details))

    def build_tournament_details_str(self):
        tournament_details = self.build_tournament_type_array()

        name_str = self.build_name_details_str()

        if len(name_str) > 0:
            tournament_details.append(name_str)

        return " ".join(filter(None, tournament_details))

    def build_name_details_str(self):
        name_details = []
        if self.name:
            name_details.append(self.name)
        if self.event.name:
            name_details.append(self.event.name)
        name_str = " - ".join(filter(None, name_details))
        if name_str:
            name_str = f"{name_str}"
        return name_str

    def player_register_check(self, licence_id):
        reasons = []
        if not licence_id:
            return None

        data, found = get_player_name_from_csv(self.federal_category, licence_id)
        if not found or not data:
            print("not found or not data")
            return None

        birth_year = data.get('birth_year', None)
        is_woman = data.get('is_woman', None)

        # Check gender category restrictions
        if is_woman is not None and self.federal_category == FederalCategory.WOMEN and is_woman is False:
            reasons.append("Ce tournoi est réservé aux femmes")

        if birth_year is None:
            return reasons if reasons else None

        current_year = timezone.now().year
        if timezone.now().month >= 9:  # Check if current month is September or later
            current_year += 1

        user_age = current_year - int(birth_year)
        print("user_age", user_age)
        # Check age category restrictions

        if self.federal_age_category == FederalAgeCategory.A11_12 and user_age > 12:
            reasons.append("Ce tournoi est réservé aux -12 ans")
        if self.federal_age_category == FederalAgeCategory.A13_14 and user_age > 14:
            reasons.append("Ce tournoi est réservé aux -14 ans")
        if self.federal_age_category == FederalAgeCategory.A15_16 and user_age > 16:
            reasons.append("Ce tournoi est réservé aux -16 ans")
        if self.federal_age_category == FederalAgeCategory.A17_18 and user_age > 18:
            reasons.append("Ce tournoi est réservé aux -18 ans")
        if self.federal_age_category == FederalAgeCategory.SENIOR and user_age < 11:
            reasons.append("Ce tournoi est réservé aux 11 ans et plus")
        if self.federal_age_category == FederalAgeCategory.A45 and user_age < 45:
            reasons.append("Ce tournoi est réservé aux +45 ans")
        if self.federal_age_category == FederalAgeCategory.A55 and user_age < 55:
            reasons.append("Ce tournoi est réservé aux +55 ans")

        addon = 0
        computedRank = int(data.get("rank", 0))
        if is_woman and self.federal_category == FederalCategory.MEN:
            addon = FederalCategory.female_in_male_assimilation_addition(computedRank)
        computedRank = computedRank + addon

        if computedRank <= self.min_player_rank():
            name = data['first_name'] + " " + data['last_name'].upper()
            reasons.append(f"{name} ({licence_id}): trop bien classé pour ce tournoi")

        return reasons if reasons else None

    def min_player_rank(self):
        return FederalLevelCategory.min_player_rank(self.federal_level_category, self.federal_category, self.federal_age_category)

    def first_waiting_list_team(self, teams):
        if len(teams)<=self.team_count:
            return None
        waiting_teams = [team for team in teams if team.stage == "Attente"]
        if len(waiting_teams) > 0:
            return waiting_teams[0].team_registration

    def broadcasted_prog(self):
        # Get matches from broadcasted_matches_and_group_stages
        matches, _ = self.broadcasted_matches_and_group_stages()

        if not matches:
            return []

        # Get all unfinished matches for courts
        active_matches = [
            m for m in matches
            if m.end_date is None  # Not finished
            and m.court_index is not None
        ]

        # Group matches by court
        matches_by_court = {}
        courts = set()
        for match in active_matches:
            if match.court_index not in matches_by_court:
                matches_by_court[match.court_index] = []
                courts.add(match.court_index)
            matches_by_court[match.court_index].append(match)

        # Sort matches within each court by start time
        for court in matches_by_court:
            matches_by_court[court].sort(key=lambda m: (
                m.start_date is None,  # None dates come last
                m.start_date if m.start_date else timezone.now()
            ))

        # Sort courts and organize them into groups of 4
        sorted_courts = sorted(list(courts))
        court_groups = [sorted_courts[i:i+4] for i in range(0, len(sorted_courts), 4)]

        ordered_matches = []
        # For each group of up to 4 courts
        for court_group in court_groups:
            # First row: earliest match for each court
            for court in court_group:
                if court in matches_by_court and matches_by_court[court]:
                    ordered_matches.append(matches_by_court[court][0])
                else:
                    ordered_matches.append({"empty": True})
            # Pad to 4 courts if needed
            while len(ordered_matches) % 4 != 0:
                ordered_matches.append({"empty": True})

            # Second row: next match for each court
            for court in court_group:
                if court in matches_by_court and len(matches_by_court[court]) > 1:
                    ordered_matches.append(matches_by_court[court][1])
                else:
                    ordered_matches.append({"empty": True})
            # Pad to 4 courts if needed
            while len(ordered_matches) % 4 != 0:
                ordered_matches.append({"empty": True})

        # Add unassigned matches at the end if needed
        unassigned_matches = [
            m for m in matches
            if m.end_date is None and m.court_index is None
        ]
        if unassigned_matches:
            ordered_matches.extend(unassigned_matches)

        return ordered_matches

    def get_butterfly_bracket_match_group(self, parent_round=None, double_butterfly_mode=False, display_loser_final=False):
        loser_final = None
        main_rounds_reversed = []

        # Get main bracket rounds (excluding children/ranking matches)
        main_rounds = self.rounds.filter(
            parent=parent_round,
            group_stage_loser_bracket=False
        ).order_by('-index')

        count = main_rounds.count()
        if display_loser_final and count > 1:
            semi = main_rounds[count - 2]
            loser_final = self.rounds.filter(
                parent=semi,
                group_stage_loser_bracket=False
            ).order_by('index').first()

        # Create serializable match groups data
        serializable_match_groups = []

        # Add first half of each round (from last to semi-finals)
        for round in main_rounds:
            next_round = main_rounds.filter(index=round.index - 1).first()
            match_group = round.prepare_match_group(next_round, parent_round, loser_final, double_butterfly_mode, False)
            if match_group:
                serializable_match_groups.append(match_group)

        if double_butterfly_mode:
            main_rounds_reversed = list(main_rounds)
            main_rounds_reversed.reverse()
            for round in main_rounds_reversed:
                if round.index > 0:
                    next_round = main_rounds.filter(index=round.index - 1).first()
                    match_group = round.prepare_match_group(next_round, parent_round, None, double_butterfly_mode, True)
                    if match_group:
                        serializable_match_groups.append(match_group)

        return serializable_match_groups

    def has_bracket(self):
        main_rounds = self.rounds.filter(
            parent=None,
            group_stage_loser_bracket=False
        )

        count = main_rounds.count()
        if count == 0:
            return False
        else:
            return True

    def  umpire_contact(self):
        if self.umpire_custom_contact is not None:
            print(self.umpire_custom_contact)
            return self.umpire_custom_contact
        return self.event.creator.full_name()

    def umpire_mail(self):
        if self.umpire_custom_mail is not None:
            print(self.umpire_custom_mail)
            return self.umpire_custom_mail
        return self.event.creator.email

    def umpire_phone(self):
        if self.umpire_custom_phone is not None:
            print(self.umpire_custom_phone)
            return self.umpire_custom_phone
        return self.event.creator.phone


class MatchGroup:
    def __init__(self, name, matches, formatted_schedule, round_id=None):
        self.name = name
        self.matches = matches
        self.formatted_schedule = formatted_schedule
        self.round_id = round_id

    def add_match(self, match):
        self.matches.append(match)

    def add_matches(self, matches):
        self.matches = matches

    def to_dict(self):
        return {
            'name': self.name,
            'round_id': self.round_id,
            'matches': [match.to_dict() for match in self.matches]
        }

class TeamSummon:
    def __init__(self, id, names, date, weight, stage, court, image, day_duration):
        self.id = str(id)
        self.names = names
        self.date = date
        self.weight = weight
        self.stage = stage
        self.court = court
        self.image = image
        self.day_duration = day_duration

    def formatted_date(self):
        if self.date:
            if self.day_duration >= 7:
                return formats.date_format(self.date, format='l d M H:i')
            else:
                return formats.date_format(self.date, format='l H:i')
        else:
            return ''

    def to_dict(self):
        return {
            "names": self.names,
            "date": self.formatted_date(),
            "weight": self.weight,
            "stage": self.stage,
            "court": self.court,
            "image": self.image,
        }

class TeamItem:
    def __init__(self, team_registration):
        self.names = team_registration.team_names()
        self.date = team_registration.local_call_date()
        self.registration_date = team_registration.registration_date
        if team_registration.player_registrations.count() == 0:
            weight = None
        else:
            weight = team_registration.weight
        self.weight = weight
        self.initial_weight = team_registration.initial_weight()
        self.image = team_registration.logo
        self.stage = ""
        self.team_registration = team_registration
        self.wildcard_bracket = team_registration.wild_card_bracket
        self.wildcard_groupstage = team_registration.wild_card_group_stage

    def set_stage(self, stage):
        self.stage = stage

    def to_dict(self):
        return {
            "names": self.names,
            "date": self.date,
            "registration_date": self.registration_date,
            "weight": self.weight,
            "initial_weight": self.initial_weight,
            "image": self.image,
            "stage": self.stage,
            "wildcard_bracket": self.wildcard_bracket,
            "wildcard_groupstage": self.wildcard_groupstage,
        }

class TeamRanking:
    def __init__(self, id, names, ranking, points, image):
        self.id = str(id)
        self.names = names
        self.ranking = ranking
        self.formatted_ranking = self.ordinal(ranking)
        self.points = self.points_earned_display(points)
        self.image = image

    # def ranking_display(self):
    #     return self.ordinal(self.ranking)

    def points_earned_display(self, points):
        if points is None:
            return ""
        return f"+{points} pt{self.plural_suffix(points)}"

    def plural_suffix(self, n):
        if n > 1:
            return 's'
        else:
            return ''

    def ordinal(self, n):
        if n == 1:
            suffix = 'er'
        else:
            suffix = 'ème'
        return str(n) + suffix

    def to_dict(self):
        return {
            "names": self.names,
            "ranking": self.ranking,
            "formatted_ranking": self.formatted_ranking,
            "points": self.points,
            "image": self.image,
        }
