import logging
from typing import Dict
from uuid import uuid4

from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_extensions.db.models import TimeStampedModel
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
from sports.utils import get_players_from_event, get_round_name_from_event

logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}


class SportEventType(models.TextChoices):
    UNKNOWN = "UK", _("Event")
    GAME = "GA", _("Game")
    RACE = "RA", _("Race")
    MATCH = "MA", _("Match")


class TheSportsDbMixin(TimeStampedModel):
    name = models.CharField(max_length=255)
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    thesportsdb_id = models.CharField(max_length=255, **BNULL)

    class Meta:
        abstract = True

    def __str__(self):
        return self.name


class Sport(TheSportsDbMixin):
    default_event_run_time_seconds = models.IntegerField(**BNULL)
    default_event_type = models.CharField(
        max_length=2,
        choices=SportEventType.choices,
        default=SportEventType.UNKNOWN,
    )

    @property
    def default_event_run_time(self):
        default_run_time = getattr(settings, "DEFAULT_EVENT_RUNTIME_SECONDS", 14400)
        if self.default_event_run_time_seconds:
            default_run_time = self.default_event_run_time_seconds
        return default_run_time


class League(TheSportsDbMixin):
    logo = models.ImageField(upload_to="sports/league-logos/", **BNULL)
    abbreviation_str = models.CharField(max_length=10, **BNULL)
    sport = models.ForeignKey(Sport, on_delete=models.DO_NOTHING, **BNULL)

    @property
    def abbreviation(self):
        return self.abbreviation_str


class Season(TheSportsDbMixin):
    league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)

    def __str__(self):
        return f"{self.name} season of {self.league}"


class Team(TheSportsDbMixin):
    league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
    logo = models.ImageField(upload_to="sports/team-logos/", **BNULL)


class Player(TheSportsDbMixin):
    league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
    team = models.ForeignKey(Team, on_delete=models.DO_NOTHING, **BNULL)


class Round(TheSportsDbMixin):
    season = models.ForeignKey(Season, on_delete=models.DO_NOTHING, **BNULL)

    def __str__(self):
        value = f"{self.name} of {self.season}"
        if not self.name:
            value = f"{self.thesportsdb_id} of {self.season}"
        return value


class SportEvent(ScrobblableMixin):
    COMPLETION_PERCENT = getattr(settings, "SPORT_COMPLETION_PERCENT", 90)

    event_type = models.CharField(
        max_length=2,
        choices=SportEventType.choices,
        default=SportEventType.UNKNOWN,
    )
    league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)

    @property
    def logdata_cls(self):
        from scrobbles.dataclasses import SportEventLogData

        return SportEventLogData

    teams = models.ManyToManyField(Team, blank=True)
    players = models.ManyToManyField(Player, blank=True)

    # Deprecated - data migrated to scrobble.log via SportEventLogData
    thesportsdb_id = models.CharField(max_length=255, **BNULL)
    start = models.DateTimeField(**BNULL)
    round = models.ForeignKey(Round, on_delete=models.DO_NOTHING, **BNULL)

    def save(self, *args, **kwargs):
        old_instance = None
        try:
            old_instance = UserProfile.objects.get(pk=self.pk)
        except:
            pass

        if not self.title or (old_instance and old_instance.title != self.title):
            self.title = self.comp_str

        super(SportEvent, self).save(*args, **kwargs)

    def __str__(self) -> str:
        return f"{self.title} - {self.subtitle}"

    def get_absolute_url(self):
        return reverse("sports:event_detail", kwargs={"slug": self.uuid})

    @property
    def subtitle(self) -> str:
        league = self.league
        if self.league and self.league.abbreviation_str:
            league = self.league.abbreviation_str
        return f"{league} {self.get_event_type_display()}"

    @property
    def comp_str(self) -> str:
        if self.players.exists():
            return " v ".join(str(p) for p in self.players.all())

        if self.teams.exists():
            return " v ".join(str(t) for t in self.teams.all())

        return ""

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants(verb="Watching", tags="trophy")

    @property
    def sportsdb_link(self):
        return f"https://thesportsdb.com/event/{self.thesportsdb_id}"

    @property
    def info_link(self):
        return self.sportsdb_link

    @property
    def primary_image_url(self) -> str:
        if self.league and self.league.logo:
            return self.league.logo.url
        return ""

    @classmethod
    def find_or_create(cls, data_dict: Dict) -> tuple["Event", dict]:
        """Given a data dict from TheSportsDB, finds or creates a canonical
        SportEvent by teams, players or title, and returns (event, logdata).

        The logdata dict contains per-scrobble details (thesportsdb_id, start,
        round/season names) that should be stored in the scrobble's log field.

        """
        # Find or create our Sport
        sid = data_dict.get("Sport")
        sport, s_created = Sport.objects.get_or_create(thesportsdb_id=sid)
        if s_created:
            sport.name = sid
            sport.save(update_fields=["name"])

        # Find or create our League
        lid = data_dict.get("LeagueId")
        league, l_created = League.objects.get_or_create(
            thesportsdb_id=lid, sport=sport
        )
        if l_created:
            league.sport = sport
            league.name = data_dict.get("LeagueName", "")
            league.save(update_fields=["sport", "name"])

        # Find or create our Season
        seid = data_dict.get("Season")
        season, se_created = Season.objects.get_or_create(
            thesportsdb_id=seid, league=league
        )
        if se_created:
            season.name = seid
            season.save(update_fields=["name"])

        # Find or create our Round
        rid = data_dict.get("RoundId")
        round_obj, r_created = Round.objects.get_or_create(
            thesportsdb_id=rid,
            season=season,
            name=rid,
        )
        if r_created:
            round_obj.season = season
            round_obj.save(update_fields=["season"])

        # Build logdata with per-scrobble details
        logdata = {}
        logdata["thesportsdb_id"] = data_dict.get("EventId")
        start = data_dict.get("Start")
        if start:
            logdata["start"] = (
                start.isoformat() if hasattr(start, "isoformat") else str(start)
            )
        if round_obj:
            logdata["round_name"] = round_obj.name or str(round_obj)
            if round_obj.season:
                logdata["season_name"] = round_obj.season.name or str(round_obj.season)

        # Look up or create teams/players
        home_team = None
        away_team = None
        if data_dict.get("HomeTeamName"):
            home_team_dict = {
                "name": data_dict.get("HomeTeamName", ""),
                "thesportsdb_id": data_dict.get("HomeTeamId", ""),
                "league": league,
            }
            home_team, ht_created = Team.objects.get_or_create(**home_team_dict)
            if ht_created:
                from sports.thesportsdb import enrich_team_logo

                enrich_team_logo(home_team)

            away_team_dict = {
                "name": data_dict.get("AwayTeamName", ""),
                "thesportsdb_id": data_dict.get("AwayTeamId", ""),
                "league": league,
            }
            away_team, at_created = Team.objects.get_or_create(**away_team_dict)
            if at_created:
                from sports.thesportsdb import enrich_team_logo

                enrich_team_logo(away_team)

        players_list = []
        if data_dict.get("Sport") == "Tennis":
            event_name = data_dict.get("Name", "")
            if not round_obj.name:
                round_obj.name = get_round_name_from_event(event_name)
                round_obj.save(update_fields=["name"])

            players_list = get_players_from_event(event_name)

        # Find existing canonical event by teams, players, or title
        event = None
        if home_team and away_team:
            event = (
                cls.objects.filter(league=league, teams=home_team)
                .filter(teams=away_team)
                .first()
            )
        if not event and players_list:
            player_objs = []
            for player_name in players_list:
                player = Player.objects.filter(name__icontains=player_name).first()
                if not player:
                    player = Player.objects.create(name=player_name)
                player_objs.append(player)
            qs = cls.objects.filter(league=league, players=player_objs[0])
            for player in player_objs[1:]:
                qs = qs.filter(players=player)
            event = qs.first()

        if not event:
            title = data_dict.get("Name", "").strip()
            if title:
                event = cls.objects.filter(league=league, title=title).first()

        if not event:
            event = cls.objects.create(
                title=data_dict.get("Name"),
                event_type=sport.default_event_type,
                league=league,
                base_run_time_seconds=data_dict.get("RunTime"),
            )

        # Ensure M2M is populated on the canonical event
        if home_team and not event.teams.filter(id=home_team.id).exists():
            event.teams.add(home_team)
        if away_team and not event.teams.filter(id=away_team.id).exists():
            event.teams.add(away_team)
        for player_name in players_list:
            player = Player.objects.filter(name__icontains=player_name).first()
            if player and not event.players.filter(id=player.id).exists():
                event.players.add(player)

        return event, logdata
