import calendar

from django.contrib.auth import get_user_model
from django.db import models
from django.db.models import Q
from django.urls import reverse
from django_extensions.db.models import TimeStampedModel

User = get_user_model()
BNULL = {"blank": True, "null": True}


class ChartRecord(TimeStampedModel):
    """Stores rankings for a user/media/time period combination.

    Example entries:
    - Bjork was #3 artist in 2022
    - Homogenic was #10 album in April 2025
    - The Office was #1 TV series in Week #34 2024
    """

    user = models.ForeignKey(User, on_delete=models.CASCADE, **BNULL)

    year = models.IntegerField()
    month = models.IntegerField(**BNULL)
    week = models.IntegerField(**BNULL)
    day = models.IntegerField(**BNULL)

    artist = models.ForeignKey("music.Artist", on_delete=models.CASCADE, **BNULL)
    album = models.ForeignKey("music.Album", on_delete=models.CASCADE, **BNULL)
    track = models.ForeignKey("music.Track", on_delete=models.CASCADE, **BNULL)
    tv_series = models.ForeignKey("videos.Series", on_delete=models.CASCADE, **BNULL)
    video = models.ForeignKey("videos.Video", on_delete=models.CASCADE, **BNULL)
    podcast = models.ForeignKey("podcasts.Podcast", on_delete=models.CASCADE, **BNULL)
    podcast_episode = models.ForeignKey(
        "podcasts.PodcastEpisode", on_delete=models.CASCADE, **BNULL
    )
    board_game = models.ForeignKey(
        "boardgames.BoardGame", on_delete=models.CASCADE, **BNULL
    )
    trail = models.ForeignKey("trails.Trail", on_delete=models.CASCADE, **BNULL)
    geo_location = models.ForeignKey(
        "locations.GeoLocation", on_delete=models.CASCADE, **BNULL
    )
    food = models.ForeignKey("foods.Food", on_delete=models.CASCADE, **BNULL)
    book = models.ForeignKey("books.Book", on_delete=models.CASCADE, **BNULL)

    rank = models.IntegerField()
    count = models.IntegerField(default=0)

    class Meta:
        indexes = [
            models.Index(fields=["user", "year", "artist", "rank"]),
            models.Index(fields=["user", "year", "album", "rank"]),
            models.Index(fields=["user", "year", "track", "rank"]),
            models.Index(fields=["user", "year", "tv_series", "rank"]),
            models.Index(fields=["user", "year", "video", "rank"]),
            models.Index(fields=["user", "year", "podcast", "rank"]),
            models.Index(fields=["user", "year", "board_game", "rank"]),
            models.Index(fields=["user", "year", "trail", "rank"]),
            models.Index(fields=["user", "year", "geo_location", "rank"]),
            models.Index(fields=["user", "year", "food", "rank"]),
            models.Index(fields=["user", "year", "book", "rank"]),
            models.Index(fields=["user", "year", "month", "artist", "rank"]),
            models.Index(fields=["user", "year", "month", "album", "rank"]),
            models.Index(fields=["user", "year", "month", "track", "rank"]),
            models.Index(fields=["user", "year", "month", "tv_series", "rank"]),
            models.Index(fields=["user", "year", "month", "video", "rank"]),
            models.Index(fields=["user", "year", "month", "board_game", "rank"]),
            models.Index(fields=["user", "year", "month", "book", "rank"]),
            models.Index(fields=["user", "year", "month", "food", "rank"]),
            models.Index(fields=["user", "year", "month", "podcast", "rank"]),
            models.Index(fields=["user", "year", "month", "trail", "rank"]),
            models.Index(fields=["user", "year", "week", "artist", "rank"]),
            models.Index(fields=["user", "year", "week", "album", "rank"]),
            models.Index(fields=["user", "year", "week", "track", "rank"]),
            models.Index(fields=["user", "year", "week", "tv_series", "rank"]),
            models.Index(fields=["user", "year", "week", "video", "rank"]),
            models.Index(fields=["user", "year", "week", "board_game", "rank"]),
            models.Index(fields=["user", "year", "week", "book", "rank"]),
            models.Index(fields=["user", "year", "week", "food", "rank"]),
            models.Index(fields=["user", "year", "week", "podcast", "rank"]),
            models.Index(fields=["user", "year", "week", "trail", "rank"]),
            models.Index(fields=["user", "year", "month", "day", "artist", "rank"]),
            models.Index(fields=["user", "year", "month", "day", "album", "rank"]),
            models.Index(fields=["user", "year", "month", "day", "tv_series", "rank"]),
        ]
        constraints = [
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "artist"],
                condition=Q(artist__isnull=False),
                name="unique_chart_artist_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "album"],
                condition=Q(album__isnull=False),
                name="unique_chart_album_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "track"],
                condition=Q(track__isnull=False),
                name="unique_chart_track_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "tv_series"],
                condition=Q(tv_series__isnull=False),
                name="unique_chart_tv_series_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "video"],
                condition=Q(video__isnull=False),
                name="unique_chart_video_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "podcast"],
                condition=Q(podcast__isnull=False),
                name="unique_chart_podcast_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "podcast_episode"],
                condition=Q(podcast_episode__isnull=False),
                name="unique_chart_podcast_episode_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "board_game"],
                condition=Q(board_game__isnull=False),
                name="unique_chart_board_game_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "trail"],
                condition=Q(trail__isnull=False),
                name="unique_chart_trail_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "geo_location"],
                condition=Q(geo_location__isnull=False),
                name="unique_chart_geo_location_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "food"],
                condition=Q(food__isnull=False),
                name="unique_chart_food_period",
            ),
            models.UniqueConstraint(
                fields=["user", "year", "month", "week", "day", "book"],
                condition=Q(book__isnull=False),
                name="unique_chart_book_period",
            ),
        ]

    @property
    def media_obj(self):
        for attr in [
            "artist",
            "album",
            "track",
            "tv_series",
            "video",
            "podcast",
            "podcast_episode",
            "board_game",
            "trail",
            "geo_location",
            "food",
            "book",
        ]:
            obj = getattr(self, attr)
            if obj:
                return obj
        return None

    @property
    def media_type(self):
        mapping = {
            "artist": "Artist",
            "album": "Album",
            "track": "Track",
            "tv_series": "TV Series",
            "video": "Video",
            "podcast": "Podcast",
            "podcast_episode": "Podcast Episode",
            "board_game": "Board Game",
            "trail": "Trail",
            "geo_location": "Location",
            "food": "Food",
            "book": "Book",
        }
        for attr, name in mapping.items():
            if getattr(self, attr) is not None:
                return name
        return None

    @property
    def period_str(self) -> str:
        if self.day:
            return f"{calendar.month_name[self.month]} {self.day}, {self.year}"
        if self.week:
            return f"Week {self.week}, {self.year}"
        if self.month:
            return f"{calendar.month_name[self.month]} {self.year}"
        return str(self.year)

    @property
    def period_type(self) -> str:
        if self.day:
            return "day"
        if self.week:
            return "week"
        if self.month:
            return "month"
        return "year"

    def __str__(self):
        obj = self.media_obj
        obj_name = str(obj) if obj else "Unknown"
        return f"#{self.rank} {obj_name} in {self.period_str}"

    def get_absolute_url(self):
        return reverse("charts:charts-home")

    @property
    def chart_url(self) -> str:
        url = reverse("charts:charts-home")
        date_str = str(self.year)
        if self.week:
            date_str = f"{self.year}-W{self.week}"
        elif self.month:
            date_str = f"{self.year}-{self.month:02d}"
        return f"{url}?date={date_str}"

    @property
    def rank_emoji(self) -> str:
        emojis = {1: "🥇", 2: "🥈", 3: "🥉"}
        return emojis.get(self.rank, f"#{self.rank}")
