from datetime import datetime, timedelta

from django.apps import apps
from django.db.models import Count, Q, QuerySet
from django.utils import timezone
from profiles.utils import now_user_timezone
from scrobbles.models import Scrobble
from videos.models import Video


def scrobble_counts(user=None):

    now = timezone.now()
    user_filter = Q()
    if user and user.is_authenticated:
        now = now_user_timezone(user.profile)
        user_filter = Q(user=user)

    start_of_today = datetime.combine(now.date(), datetime.min.time(), now.tzinfo)
    starting_day_of_current_week = now.date() - timedelta(
        days=now.today().isoweekday() % 7
    )
    starting_day_of_current_month = now.date().replace(day=1)
    starting_day_of_current_year = now.date().replace(month=1, day=1)

    finished_scrobbles_qs = Scrobble.objects.filter(
        user_filter,
        played_to_completion=True,
        media_type=Scrobble.MediaType.TRACK,
    )
    data = {}
    data["today"] = finished_scrobbles_qs.filter(timestamp__gte=start_of_today).count()
    data["week"] = finished_scrobbles_qs.filter(
        timestamp__gte=starting_day_of_current_week
    ).count()
    data["month"] = finished_scrobbles_qs.filter(
        timestamp__gte=starting_day_of_current_month
    ).count()
    data["year"] = finished_scrobbles_qs.filter(
        timestamp__gte=starting_day_of_current_year
    ).count()
    data["alltime"] = finished_scrobbles_qs.count()
    return data


def week_of_scrobbles(user=None, start=None, media: str = "tracks") -> dict[str, int]:

    now = timezone.now()
    user_filter = Q()
    if user and user.is_authenticated:
        now = now_user_timezone(user.profile)
        user_filter = Q(user=user)

    if not start:
        start = datetime.combine(now.date(), datetime.min.time(), now.tzinfo)

    scrobble_day_dict = {}
    base_qs = Scrobble.objects.filter(user_filter, played_to_completion=True)

    media_filter = Q(track__isnull=False)
    if media == "movies":
        media_filter = Q(video__video_type=Video.VideoType.MOVIE)
    if media == "series":
        media_filter = Q(video__video_type=Video.VideoType.TV_EPISODE)

    for day in range(6, -1, -1):
        start_day = start - timedelta(days=day)
        end = datetime.combine(start_day, datetime.max.time(), now.tzinfo)
        day_of_week = start_day.strftime("%A")

        scrobble_day_dict[day_of_week] = base_qs.filter(
            media_filter,
            timestamp__gte=start_day,
            timestamp__lte=end,
            played_to_completion=True,
        ).count()

    return scrobble_day_dict


def live_charts(
    user: "User",
    media_type: str = "Track",
    chart_period: str = "all",
    limit: int = 15,
    app_label: str = "music",
    as_of: datetime = None,
) -> QuerySet:
    if as_of is not None:
        now = as_of
        tzinfo = now.tzinfo
        now = now.date()
    else:
        now = timezone.now()
        tzinfo = now.tzinfo
        now = now.date()
        if user.is_authenticated:
            now = now_user_timezone(user.profile)
            tzinfo = now.tzinfo
            now = now.date()

    seven_days_ago = now - timedelta(days=7)
    thirty_days_ago = now - timedelta(days=30)
    start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
    start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
    start_day_of_month = now.replace(day=1)
    start_day_of_year = now.replace(month=1, day=1)

    media_model = apps.get_model(app_label=app_label, model_name=media_type)

    period_queries = {
        "today": {"scrobble__timestamp__gte": start_of_today},
        "week": {"scrobble__timestamp__gte": start_day_of_week},
        "last7": {"scrobble__timestamp__gte": seven_days_ago},
        "last30": {"scrobble__timestamp__gte": thirty_days_ago},
        "month": {"scrobble__timestamp__gte": start_day_of_month},
        "year": {"scrobble__timestamp__gte": start_day_of_year},
        "all": {},
    }

    time_filter = Q()
    completion_filter = Q(scrobble__user=user, scrobble__played_to_completion=True)
    user_filter = Q(scrobble__user=user)
    count_field = "scrobble"

    if media_type == "Artist":
        for period, query_dict in period_queries.items():
            period_queries[period] = {"track__" + k: v for k, v in query_dict.items()}
        completion_filter = Q(
            track__scrobble__user=user,
            track__scrobble__played_to_completion=True,
        )
        count_field = "track__scrobble"
        user_filter = Q(track__scrobble__user=user)

    time_filter = Q(**period_queries[chart_period])

    return (
        media_model.objects.filter(user_filter, time_filter)
        .annotate(
            num_scrobbles=Count(
                count_field,
                filter=completion_filter,
                distinct=True,
            )
        )
        .order_by("-num_scrobbles")[:limit]
    )


def artist_scrobble_count(artist_id: int, filter: str = "today") -> int:
    return Scrobble.objects.filter(track__artists=artist_id).count()


def live_tv_charts(
    user: "User",
    chart_period: str = "all",
    limit: int = 15,
) -> QuerySet:
    from django.db.models import OuterRef, Subquery
    from videos.models import Series, Video

    now = timezone.now()
    tzinfo = now.tzinfo
    now = now.date()
    if user.is_authenticated:
        now = now_user_timezone(user.profile)
        tzinfo = now.tzinfo

    seven_days_ago = now - timedelta(days=7)
    thirty_days_ago = now - timedelta(days=30)
    start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
    start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
    start_day_of_month = now.replace(day=1)
    start_day_of_year = now.replace(month=1, day=1)

    period_queries = {
        "today": Q(timestamp__gte=start_of_today),
        "week": Q(timestamp__gte=start_day_of_week),
        "last7": Q(timestamp__gte=seven_days_ago),
        "last30": Q(timestamp__gte=thirty_days_ago),
        "month": Q(timestamp__gte=start_day_of_month),
        "year": Q(timestamp__gte=start_day_of_year),
        "all": Q(),
    }

    time_filter = period_queries[chart_period]
    completion_filter = Q(
        user=user,
        played_to_completion=True,
        video__video_type=Video.VideoType.TV_EPISODE,
    )

    scrobble_counts = (
        Scrobble.objects.filter(
            completion_filter,
            time_filter,
            video__tv_series=OuterRef("pk"),
        )
        .values("video__tv_series")
        .annotate(c=Count("id"))
        .values("c")
    )

    return (
        Series.objects.annotate(num_scrobbles=Subquery(scrobble_counts))
        .filter(num_scrobbles__gt=0)
        .order_by("-num_scrobbles")[:limit]
    )


def live_youtube_channel_charts(
    user: "User",
    chart_period: str = "all",
    limit: int = 15,
) -> QuerySet:
    from django.db.models import OuterRef, Subquery
    from videos.models import Channel, Video

    now = timezone.now()
    tzinfo = now.tzinfo
    now = now_user_timezone(user.profile)
    tzinfo = now.tzinfo

    seven_days_ago = now - timedelta(days=7)
    thirty_days_ago = now - timedelta(days=30)
    start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
    start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
    start_day_of_year = now.replace(month=1, day=1)

    period_queries = {
        "today": Q(timestamp__gte=start_of_today),
        "week": Q(timestamp__gte=start_day_of_week),
        "last7": Q(timestamp__gte=seven_days_ago),
        "last30": Q(timestamp__gte=thirty_days_ago),
        "year": Q(timestamp__gte=start_day_of_year),
        "all": Q(),
    }

    time_filter = period_queries[chart_period]
    completion_filter = Q(
        user=user,
        played_to_completion=True,
        video__video_type=Video.VideoType.YOUTUBE,
    )

    scrobble_counts = (
        Scrobble.objects.filter(
            completion_filter,
            time_filter,
            video__channel=OuterRef("pk"),
        )
        .values("video__channel")
        .annotate(c=Count("id"))
        .values("c")
    )

    return (
        Channel.objects.annotate(num_scrobbles=Subquery(scrobble_counts))
        .filter(num_scrobbles__gt=0)
        .order_by("-num_scrobbles")[:limit]
    )


def batch_live_charts(
    user: "User",
    media_type: str = "Track",
    limit: int = 15,
    app_label: str = "music",
) -> dict[str, list]:
    from django.db.models import Count, Q

    now = timezone.now()
    tzinfo = now.tzinfo
    now = now.date()
    if user.is_authenticated:
        now = now_user_timezone(user.profile)
        tzinfo = now.tzinfo

    seven_days_ago = now - timedelta(days=7)
    thirty_days_ago = now - timedelta(days=30)
    start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
    start_day_of_year = now.replace(month=1, day=1)

    media_model = apps.get_model(app_label=app_label, model_name=media_type)

    if media_type == "Artist":
        base_filter = Q(
            track__scrobble__user=user, track__scrobble__played_to_completion=True
        )
        count_field = "track__scrobble"
    else:
        base_filter = Q(scrobble__user=user, scrobble__played_to_completion=True)
        count_field = "scrobble"

    scrobble_filter_prefix = (
        "track__scrobble__" if media_type == "Artist" else "scrobble__"
    )

    queryset = media_model.objects.filter(
        Q(track__scrobble__user=user)
        if media_type == "Artist"
        else Q(scrobble__user=user)
    ).annotate(
        num_scrobbles_today=Count(
            count_field,
            filter=Q(
                **{
                    f"{scrobble_filter_prefix}played_to_completion": True,
                    f"{scrobble_filter_prefix}timestamp__gte": start_of_today,
                }
            ),
            distinct=True,
        ),
        num_scrobbles_last7=Count(
            count_field,
            filter=Q(
                **{
                    f"{scrobble_filter_prefix}played_to_completion": True,
                    f"{scrobble_filter_prefix}timestamp__gte": seven_days_ago,
                }
            ),
            distinct=True,
        ),
        num_scrobbles_last30=Count(
            count_field,
            filter=Q(
                **{
                    f"{scrobble_filter_prefix}played_to_completion": True,
                    f"{scrobble_filter_prefix}timestamp__gte": thirty_days_ago,
                }
            ),
            distinct=True,
        ),
        num_scrobbles_year=Count(
            count_field,
            filter=Q(
                **{
                    f"{scrobble_filter_prefix}played_to_completion": True,
                    f"{scrobble_filter_prefix}timestamp__gte": start_day_of_year,
                }
            ),
            distinct=True,
        ),
        num_scrobbles=Count(
            count_field,
            filter=Q(
                **{
                    f"{scrobble_filter_prefix}played_to_completion": True,
                }
            ),
            distinct=True,
        ),
    )

    items = list(queryset)
    for item in items:
        item.count = item.num_scrobbles

    def filter_by_count_attr(attr_name):
        filtered = [item for item in items if getattr(item, attr_name) > 0]
        return sorted(filtered, key=lambda x: -getattr(x, attr_name))[:limit]

    return {
        "today": filter_by_count_attr("num_scrobbles_today"),
        "last7": filter_by_count_attr("num_scrobbles_last7"),
        "last30": filter_by_count_attr("num_scrobbles_last30"),
        "year": filter_by_count_attr("num_scrobbles_year"),
        "all": filter_by_count_attr("num_scrobbles"),
    }
