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 Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory
import uuid
from django.utils import timezone, formats
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

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(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
    event = models.ForeignKey(Event, blank=True, null=True, on_delete=models.CASCADE)
    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)

    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.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='j F Y H:i')

    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.start_date.strftime("%d/%m/%y")

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

    def creator(self):
        return self.event.creator.username

    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) > 0:
            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:
                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
            if teams is not None and len(teams) > 0:
                word = "inscription"
                if len(teams) > 1:
                    word = word + "s"
                return f"{len(teams)} {word}"
            else:
                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.teamregistration_set.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.teamregistration_set.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.teamregistration_set.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 teams(self, includeWaitingList):
        # print("Starting teams method")
        bracket_teams = []
        group_stage_teams = []
        waiting_teams = []
        teams = []
        wildcard_bracket = []
        wildcard_group_stage = []
        complete_teams = []
        closed_registration_date = self.closed_registration_date
        # print(f"Closed registration date: {closed_registration_date}")

        for team_registration in self.teamregistration_set.all():
            # print(f"Processing team registration: {team_registration}")
            is_valid = False
            if closed_registration_date is not None and team_registration.registration_date is not None and team_registration.registration_date <= closed_registration_date:
                is_valid = True
            if closed_registration_date is None:
                is_valid = True
            if team_registration.registration_date is None:
                is_valid = True
            # print(f"Is valid: {is_valid}")

            if team_registration.walk_out is False:
                team = TeamItem(team_registration)
                # print(f"Created team: {team}")
                if team_registration.group_stage_position is not None:
                    team.set_stage("Poule")
                elif team_registration.bracket_position is not None:
                    team.set_stage("Tableau")
                else:
                    team.set_stage("Attente")
                # print(f"Team stage: {team.stage}")

                teams.append(team)
                if team_registration.wild_card_bracket:
                    wildcard_bracket.append(team)
                elif team_registration.wild_card_group_stage:
                    wildcard_group_stage.append(team)
                elif is_valid is True:
                    complete_teams.append(team)
                else:
                    waiting_teams.append(team)

        # print(f"Total teams: {len(teams)}")
        # print(f"Wildcard bracket: {len(wildcard_bracket)}")
        # print(f"Wildcard group stage: {len(wildcard_group_stage)}")
        # print(f"Complete teams: {len(complete_teams)}")
        # print(f"Waiting teams: {len(waiting_teams)}")

        if len(teams) < self.team_count:
            teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))
            return teams

        seeds_count = min(self.team_count, len(teams)) - self.group_stage_count * self.teams_per_group_stage - len(wildcard_bracket)
        group_stage_members_count = self.group_stage_count * self.teams_per_group_stage - len(wildcard_group_stage)
        if group_stage_members_count < 0:
            group_stage_members_count = 0
        if seeds_count < 0:
            seeds_count = 0
        # print(f"Seeds count: {seeds_count}")
        # print(f"Group stage members count: {group_stage_members_count}")

        if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
            complete_teams.sort(key=lambda s: (s.registration_date is None, s.registration_date or datetime.min, s.initial_weight, s.team_registration.id))
        else:
            complete_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))

        selected_teams = complete_teams[:self.team_count]
        selected_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))

        if seeds_count > 0:
            bracket_teams = selected_teams[:seeds_count] + wildcard_bracket
        else:
            bracket_teams = []
        # print(f"Bracket teams: {len(bracket_teams)}")

        if group_stage_members_count:
            group_stage_end = seeds_count + group_stage_members_count
            group_stage_teams = selected_teams[seeds_count:group_stage_end] + wildcard_group_stage
        else:
            group_stage_teams = []
        # print(f"Group stage teams: {len(group_stage_teams)}")

        waiting_list_count = len(teams) - self.team_count
        if waiting_list_count < 0:
            waiting_list_count = 0
        # print(f"Waiting list count: {waiting_list_count}")

        if waiting_list_count > 0 or len(waiting_teams) > 0:
            if waiting_list_count > 0:
                waiting_teams = waiting_teams + complete_teams[-waiting_list_count:]
            if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
                waiting_teams.sort(key=lambda s: (s.registration_date is None, s.registration_date or datetime.min, s.initial_weight, s.team_registration.id))
            else:
                waiting_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))
        else:
             waiting_teams = []
        # print(f"Final waiting teams: {len(waiting_teams)}")

        bracket_teams.sort(key=lambda s: (s.weight, s.team_registration.id))
        group_stage_teams.sort(key=lambda s: (s.weight, s.team_registration.id))

        for team in bracket_teams:
            if team.stage == "Attente":
                team.set_stage("Tableau")

        for team in group_stage_teams:
            if team.stage == "Attente":
                team.set_stage("Poule")

        for team in waiting_teams:
            team.set_stage("Attente")

        if includeWaitingList is True:
            final_teams = bracket_teams + group_stage_teams + waiting_teams
            # print(f"Final teams with waiting list: {len(final_teams)}")
        else:
            final_teams = bracket_teams + group_stage_teams
            # print(f"Final teams without waiting list: {len(final_teams)}")
        return final_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.groupstage_set.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.round_set.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():
            for round in self.round_set.filter(parent=None, group_stage_loser_bracket=False).all().order_by('index'):
                groups.extend(self.round_match_groups(round, broadcasted, hide_empty_matches=True))

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

            ordered = sorted(self.get_computed_group_stage(), key=lambda s: (-s.step, s.index))
            for group_stage in ordered:
                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.match_set.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.match_set.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 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):
        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)

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

    def get_computed_group_stage(self):
        # Get all group stages and sort by step (descending) and index (ascending)
        group_stages = self.groupstage_set.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.groupstage_set.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.groupstage_set.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.get_computed_group_stage()

        # 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.
        if timezone.now() < self.start_date:
            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 len(self.groupstage_set.all()) > 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()
            # print(f'current_round = {current_round.index} / parent = {current_round.parent}')
            if current_round:
                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))
                else:
                    # print('group_stages')
                    group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()]

        return matches, group_stages

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

    def all_matches(self, hide_empty_matches):
        matches = []
        for round in self.round_set.all():
            matches.extend(round.all_matches(hide_empty_matches))
        for group_stage in self.groupstage_set.all():
            matches.extend(group_stage.match_set.all())

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

        return matches

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

    def group_stages_running(self):
        if len(self.groupstage_set.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)
        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:
            # print(f'last_finished_match = {last_finished_match.name}')
            round_root_index = last_finished_match.round.root_round().index
            # print(f'round_index = {round_root_index}')
            if round_root_index == 0:
                return last_finished_match.round
            else:
                round = self.round_set.filter(parent=None,index=round_root_index-1).first()
                if round:
                    return round
                else:
                    return None
        else:
            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.round_set.filter(index=index, parent=None).first()

    def first_round(self):
        main_rounds = list(self.round_set.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.match_set.all())
        matches = [m for m in matches if m.should_appear()]
        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.round_set.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.last_group_stage_step())
        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):
        return True


    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 datetime.now().date() >= self.start_date.date():
            return is_build_and_not_empty
        minimum_publish_date = self.creation_date.replace(hour=9, minute=0) + timedelta(days=1)
        return timezone.now() >= 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().date() >= self.start_date.date():
            return self.has_team_registrations()
        return False

    def has_team_registrations(self):
        return len(self.teamregistration_set.all()) > 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.start_date:
            return self.has_summons()
        return False

    def display_group_stages(self):
        if self.end_date is not None:
            return True
        if len(self.groupstage_set.all()) == 0:
            return False
        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.start_date
        else:
            return timezone.now() >= first_group_stage_start_date

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

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

    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.start_date

        bracket_start_date = self.getEightAm(first_match_start_date)

        if bracket_start_date < self.start_date:
            bracket_start_date = self.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.round_set.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).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 = timezone.localtime(self.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 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):
        return (len(self.groupstage_set.all()) > 0 or len(self.round_set.all()) > 0) and len(self.teamregistration_set.all()) >= 4

    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.groupstage_set.all():
            if group_stage.has_at_least_one_started_match() is False:
                return False
        return True

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

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

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

class TeamSummon:
    def __init__(self, id, names, date, weight, stage, court, image, day_duration):
        self.id = 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 {
            "id": self.id,
            "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
        self.weight = team_registration.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 = 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):
        suffixes = {1: 'er', 2: 'ème', 3: 'rd'}
        if n == 1:
            suffix = 'er'
        else:
            suffix = 'ème'
        return str(n) + suffix

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