import random
import string

from django.db.models.signals import pre_save, post_save, pre_delete, post_delete
from django.dispatch import receiver
from django.conf import settings
from django.utils import timezone

from .models import Club, Tournament, FailedApiCall, CustomUser, Log, TeamRegistration, PlayerRegistration, UnregisteredTeam, UnregisteredPlayer, TeamSortingType, PlayerDataSource
from tournaments.services.email_service import TournamentEmailService

from tournaments.services.email_service import TournamentEmailService, TeamEmailType
from tournaments.models import PlayerDataSource

from shared.discord import send_discord_log_message, send_discord_failed_calls_message
from datetime import datetime

from .utils.extensions import is_not_sqlite_backend

def generate_unique_code():
    characters = string.ascii_lowercase + string.digits
    while True:
        code = ''.join(random.sample(characters, 3))
        if not Club.objects.filter(broadcast_code=code).exists():
            return code

@receiver(post_save, sender=Club)
def assign_unique_code(sender, instance, **kwargs):
    if not instance.broadcast_code:
        instance.broadcast_code = generate_unique_code()
        instance.save()

@receiver(post_save, sender=FailedApiCall)
def notify_discord_on_create(sender, instance, created, **kwargs):
    notify_object_creation_on_discord(created, instance)

@receiver(post_save, sender=Log)
def notify_log_creation_on_discord(sender, instance, created, **kwargs):
    notify_object_creation_on_discord(created, instance)

# WARNING: using this method requires the instance to have a discord_string method
def notify_object_creation_on_discord(created, instance):
    if created:
        default_db_engine = settings.DATABASES['default']['ENGINE']
        if default_db_engine != 'django.db.backends.sqlite3':
            site_name = settings.SITE_NAME
            if hasattr(instance, 'discord_string'):
                message = f'{site_name} > {instance.__class__.__name__} created: {instance.discord_string()}'
            else:
                message = "no message. Please configure 'discord_string' on your instance"

            if isinstance(instance, FailedApiCall):
                send_discord_failed_calls_message(message)
            else:
                send_discord_log_message(message)


def notify_team(team, tournament, message_type):
    #print(team, message_type)
    if tournament.enable_online_registration is False:
        return
    if team.has_registered_online() is False:
        return
    if tournament.should_be_over():
        return
    if tournament.supposedly_in_progress():
        return

    if is_not_sqlite_backend():
        TournamentEmailService.notify_team(team, tournament, message_type)

@receiver(pre_delete, sender=TeamRegistration)
def unregister_team(sender, instance, **kwargs):
    if not instance.tournament:
        return
    if instance.tournament.is_deleted:
        return
    if instance.tournament.enable_online_registration is False:
        return

    notify_team(instance, instance.tournament, TeamEmailType.UNREGISTERED)
    teams = instance.tournament.teams(True)
    first_waiting_list_team = instance.tournament.first_waiting_list_team(teams)
    if first_waiting_list_team and first_waiting_list_team.id != instance.id:
        notify_team(first_waiting_list_team, instance.tournament, TeamEmailType.OUT_OF_WAITING_LIST)

@receiver(post_save, sender=Tournament)
def notify_players_of_tournament_cancellation(sender, instance, **kwargs):
    if not instance.is_deleted:
        return

    if instance.enable_online_registration is False:
        return

    for team_registration in instance.team_registrations.all():
        notify_team(team_registration, instance, TeamEmailType.TOURNAMENT_CANCELED)

@receiver(pre_save, sender=Tournament)
def check_waiting_list(sender, instance, **kwargs):
    if instance.id is None:
        return

    if instance.enable_online_registration is False:
        return

    try:
        previous_state = Tournament.objects.get(id=instance.id)
    except Tournament.DoesNotExist:
        previous_state = None

    if previous_state is None:
        return

    teams_out_to_warn = []
    teams_in_to_warn = []

    if previous_state.team_count > instance.team_count:
        teams_to_remove_count = previous_state.team_count - instance.team_count
        previous_state_teams = previous_state.teams(True)
        sorted_teams = sorted(
            [team for team in previous_state_teams if team.stage != "Attente" and not (team.wildcard_bracket or team.wildcard_groupstage)],
            key=lambda t: (
                t.registration_date is None, t.registration_date or datetime.min, t.initial_weight, t.team_registration.id
            ) if previous_state.team_sorting == TeamSortingType.INSCRIPTION_DATE else
            (t.initial_weight, t.team_registration.id)
        )
        teams_out_to_warn = sorted_teams[-teams_to_remove_count:]
    elif previous_state.team_count < instance.team_count:
        teams_in_to_warn = [
            team for team in previous_state.teams(True)[(instance.team_count - previous_state.team_count):]
            if team.stage == "Attente"
        ]

    for team in teams_in_to_warn:
        notify_team(team.team_registration, instance, TeamEmailType.IN_TOURNAMENT_STRUCTURE)

    for team in teams_out_to_warn:
        notify_team(team.team_registration, instance, TeamEmailType.OUT_OF_TOURNAMENT_STRUCTURE)

@receiver(pre_save, sender=TeamRegistration)
def warn_team_walkout_status_change(sender, instance, **kwargs):
    if instance.id is None or instance.tournament is None or instance.tournament.enable_online_registration is False:
        return

    previous_instance = None
    try:
        previous_instance = TeamRegistration.objects.get(id=instance.id)
    except TeamRegistration.DoesNotExist:
        return

    previous_teams = instance.tournament.teams(True)
    current_teams = instance.tournament.teams(True, instance)
    previous_retrieved_teams = [team for team in previous_teams if team.team_registration.id == previous_instance.id]
    current_retrieved_teams = [team for team in current_teams if team.team_registration.id == instance.id]
    was_out = previous_instance.out_of_tournament()
    is_out = instance.out_of_tournament()
    if len(previous_retrieved_teams) > 0 and previous_retrieved_teams[0].stage == "Attente":
        was_out = True

    if len(current_retrieved_teams) > 0 and current_retrieved_teams[0].stage == "Attente":
        is_out = True

    print(was_out, previous_instance.out_of_tournament(), is_out, instance.out_of_tournament())
    if not instance.out_of_tournament() and is_out and (previous_instance.out_of_tournament() or not was_out):
        notify_team(instance, instance.tournament, TeamEmailType.OUT_OF_WALKOUT_WAITING_LIST)
    elif was_out and not is_out:
        notify_team(instance, instance.tournament, TeamEmailType.OUT_OF_WALKOUT_IS_IN)
    elif not previous_instance.out_of_tournament() and instance.out_of_tournament():
        notify_team(instance, instance.tournament, TeamEmailType.WALKOUT)

    if was_out and not is_out:
        first_out_of_list = instance.tournament.first_waiting_list_team(current_teams)
        if first_out_of_list:
            notify_team(first_out_of_list, instance.tournament, TeamEmailType.UNEXPECTED_OUT_OF_TOURNAMENT)
    elif not was_out and is_out:
        first_waiting_list_team = instance.tournament.first_waiting_list_team(previous_teams)
        if first_waiting_list_team:
            notify_team(first_waiting_list_team, instance.tournament, TeamEmailType.OUT_OF_WAITING_LIST)
