from datetime import datetime

from django.db import models


class WithScrobbleCountsManager(models.Manager):
    """
    Adds a with_scrobble_counts() method.
    """

    # Can we filter these (things) by Album?
    is_filterable_by_album = True

    # Can we filter these (things) by Artist?
    is_filterable_by_artist = True

    # Can we filter these (things) by Track?
    is_filterable_by_track = True

    def with_scrobble_counts(self, **kwargs):
        """
        Adds a `scrobble_count` field to the Queryset's objects, and
        orders the results by that, descending.

        eg:
            # All Tracks, each with a scrobble_count:
            Track.objects.with_scrobble_counts()

            # All Albums, each with a total scrobble_count:
            Album.objects.with_scrobble_counts()

            # All Artists, each with a total scrobble_count:
            Artist.objects.with_scrobble_counts()

            # Tracks by artist_obj:
            Track.objects.with_scrobble_counts(artist=artist_obj)

            # Tracks appearing on album_obj:
            Track.objects.with_scrobble_counts(album=album_obj)

            # Albums on which track_obj appears:
            Album.objects.with_scrobble_counts(track=track_obj)

        or combine filters:

            # Tracks by artist_obj, scrobbled by account_obj between
            # datetime_obj_1 and datetime_obj2:
            Track.objects.with_scrobble_counts(
                account         = account_obj,
                artist          = artist_obj,
                min_post_time   = datetime_obj_1,
                max_post_time   = datetime_obj_2,
            )

        Include an `account` to only include Scrobbles by that Account.
        Include an `album` to only include Scrobbles on that Album.
        Include an `artist` to only include Scrobbles by that Artist.
        Include a `track` to only include Scrobbles including that Track.
        Include a `min_post_time` to only include Scrobbles after then.
        Include a `max_post_time` to only include Scrobbles before then.
        """
        account = kwargs.get("account")
        min_post_time = kwargs.get("min_post_time")
        max_post_time = kwargs.get("max_post_time")
        album = kwargs.get("album")
        artist = kwargs.get("artist")
        track = kwargs.get("track")

        if album and not self.is_filterable_by_album:
            msg = "This is not filterable by album"
            raise ValueError(msg)

        if artist and not self.is_filterable_by_artist:
            msg = "This is not filterable by artist"
            raise ValueError(msg)

        if track and not self.is_filterable_by_track:
            msg = "This is not filterable by track"
            raise ValueError(msg)

        if account is not None and account.__class__.__name__ != "Account":
            msg = f"account must be an Account instance, not a {type(account)}"
            raise TypeError(msg)

        if album is not None and album.__class__.__name__ != "Album":
            msg = f"album must be an Album instance, not a {type(album)}"
            raise TypeError(msg)

        if artist is not None and artist.__class__.__name__ != "Artist":
            msg = f"artist must be an Artist instance, not a {type(account)}"
            raise TypeError(msg)

        if min_post_time is not None and type(min_post_time) is not datetime:
            msg = (
                "min_post_time must be a datetime.datetime, "
                f"not a {type(min_post_time)}"
            )
            raise TypeError(msg)

        if max_post_time is not None and type(max_post_time) is not datetime:
            msg = (
                "max_post_time must be a datetime.datetime, "
                f"not a {type(max_post_time)}"
            )
            raise TypeError(msg)

        filter_kwargs = {}

        if account:
            filter_kwargs["scrobbles__account"] = account

        if album:
            filter_kwargs["scrobbles__album"] = album

        if artist:
            filter_kwargs["scrobbles__artist"] = artist

        if track:
            filter_kwargs["scrobbles__track"] = track

        if min_post_time and max_post_time:
            filter_kwargs["scrobbles__post_time__gte"] = min_post_time
            filter_kwargs["scrobbles__post_time__lte"] = max_post_time

        elif min_post_time:
            filter_kwargs["scrobbles__post_time__gte"] = min_post_time

        elif max_post_time:
            filter_kwargs["scrobbles__post_time__lte"] = max_post_time

        qs = self.filter(**filter_kwargs)

        return qs.annotate(
            scrobble_count=models.Count("scrobbles", distinct=True)
        ).order_by("-scrobble_count")


class TracksManager(WithScrobbleCountsManager):
    """
    Adds a `scrobble_count` field to the Track objects.
    See WithScrobbleCountsManager for docs.
    """

    # We can't filter a list of Tracks by Tracks.
    is_filterable_by_track = False

    def with_scrobble_counts(self, **kwargs):
        "Pre-fetch all the Tracks' Artists."
        qs = super().with_scrobble_counts(**kwargs).prefetch_related("artist")
        return qs


class AlbumsManager(WithScrobbleCountsManager):
    """
    Adds a `scrobble_count` field to the Album objects.
    See WithScrobbleCountsManager for docs.
    """

    # We can't filter a list of Albums by Album.
    is_filterable_by_album = False

    def with_scrobble_counts(self, **kwargs):
        "Pre-fetch all the Albums' Artists."
        qs = super().with_scrobble_counts(**kwargs).prefetch_related("artist")
        return qs


class ArtistsManager(WithScrobbleCountsManager):
    """
    Adds a `scrobble_count` field to the Artist objects.
    See WithScrobbleCountsManager for docs.
    """

    # We can't filter a list of Artists by Artist.
    is_filterable_by_artist = False
