import django_filters
from django.db.models import Q
from django_filters import rest_framework as filters

from funkwhale_api.audio import filters as audio_filters
from funkwhale_api.audio import models as audio_models
from funkwhale_api.common import fields
from funkwhale_api.common import filters as common_filters
from funkwhale_api.common import search
from funkwhale_api.moderation import filters as moderation_filters
from funkwhale_api.tags import filters as tags_filters

from . import models, utils


def filter_tags(queryset, name, value):
    non_empty_tags = [v.lower() for v in value if v]
    for tag in non_empty_tags:
        queryset = queryset.filter(tagged_items__tag__name=tag).distinct()
    return queryset


TAG_FILTER = common_filters.MultipleQueryFilter(method=filter_tags)


class RelatedFilterSet(filters.FilterSet):
    related_type = int
    related_field = "pk"
    related = filters.CharFilter(field_name="_", method="filter_related")

    def filter_related(self, queryset, name, value):
        if not value:
            return queryset.none()
        try:
            pk = self.related_type(value)
        except (TypeError, ValueError):
            return queryset.none()

        try:
            obj = queryset.model.objects.get(**{self.related_field: pk})
        except queryset.model.DoesNotExist:
            return queryset.none()

        queryset = queryset.exclude(pk=obj.pk)
        return tags_filters.get_by_similar_tags(queryset, obj.get_tags())


class ChannelFilterSet(filters.FilterSet):
    channel = filters.CharFilter(field_name="_", method="filter_channel")

    def filter_channel(self, queryset, name, value):
        if not value:
            return queryset

        channel = (
            audio_models.Channel.objects.filter(uuid=value)
            .select_related("library")
            .first()
        )

        if not channel:
            return queryset.none()

        uploads = models.Upload.objects.filter(library=channel.library)
        actor = utils.get_actor_from_request(self.request)
        uploads = uploads.playable_by(actor)
        ids = uploads.values_list(self.Meta.channel_filter_field, flat=True)
        return queryset.filter(pk__in=ids).distinct()


class LibraryFilterSet(filters.FilterSet):
    library = filters.CharFilter(field_name="_", method="filter_library")

    def filter_library(self, queryset, name, value):
        if not value:
            return queryset

        actor = utils.get_actor_from_request(self.request)
        library = models.Library.objects.filter(uuid=value).viewable_by(actor).first()

        if not library:
            return queryset.none()

        uploads = models.Upload.objects.filter(library=library)
        uploads = uploads.playable_by(actor)
        ids = uploads.values_list(self.Meta.library_filter_field, flat=True)
        qs = queryset.filter(pk__in=ids).distinct()
        return qs


class ArtistFilter(
    RelatedFilterSet,
    LibraryFilterSet,
    audio_filters.IncludeChannelsFilterSet,
    moderation_filters.HiddenContentFilterSet,
):
    q = fields.SearchFilter(search_fields=["name"], fts_search_fields=["body_text"])
    playable = filters.BooleanFilter(field_name="_", method="filter_playable")
    has_albums = filters.BooleanFilter(field_name="_", method="filter_has_albums")
    tag = TAG_FILTER
    content_category = filters.CharFilter("content_category")
    scope = common_filters.ActorScopeFilter(
        actor_field="artist_credit__tracks__uploads__library__actor",
        distinct=True,
        library_field="artist_credit__tracks__uploads__library",
    )
    ordering = common_filters.CaseInsensitiveNameOrderingFilter(
        fields=(
            ("id", "id"),
            ("name", "name"),
            ("creation_date", "creation_date"),
            ("modification_date", "modification_date"),
            ("?", "random"),
            ("tag_matches", "related"),
        )
    )

    has_mbid = filters.BooleanFilter(
        field_name="_",
        method="filter_has_mbid",
    )

    class Meta:
        model = models.Artist
        fields = {
            "name": ["exact", "iexact", "startswith", "icontains"],
            "mbid": ["exact"],
        }
        hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ARTIST"]
        include_channels_field = "channel"
        library_filter_field = "track__artist_credit__artist"

    def filter_playable(self, queryset, name, value):
        actor = utils.get_actor_from_request(self.request)
        return queryset.playable_by(actor, value).distinct()

    def filter_has_albums(self, queryset, name, value):
        return queryset.filter(artist_credit__albums__isnull=not value)

    def filter_has_mbid(self, queryset, name, value):
        return queryset.filter(mbid__isnull=(not value))


class TrackFilter(
    RelatedFilterSet,
    ChannelFilterSet,
    LibraryFilterSet,
    audio_filters.IncludeChannelsFilterSet,
    moderation_filters.HiddenContentFilterSet,
):
    q = fields.SearchFilter(
        search_fields=[
            "title",
            "album__title",
            "artist_credit__artist__name",
        ],
        fts_search_fields=[
            "body_text",
            "artist_credit__artist__body_text",
            "album__body_text",
        ],
    )
    playable = filters.BooleanFilter(field_name="_", method="filter_playable")
    tag = TAG_FILTER
    id = common_filters.MultipleQueryFilter(coerce=int)
    scope = common_filters.ActorScopeFilter(
        actor_field="uploads__library__actor",
        library_field="uploads__library",
        distinct=True,
    )
    artist = filters.ModelChoiceFilter(
        field_name="_", method="filter_artist", queryset=models.Artist.objects.all()
    )

    ordering = django_filters.OrderingFilter(
        fields=(
            ("creation_date", "creation_date"),
            ("title", "title"),
            ("album__title", "album__title"),
            ("album__release_date", "album__release_date"),
            ("size", "size"),
            ("position", "position"),
            ("disc_number", "disc_number"),
            ("artist_credit__artist__name", "artist_credit__artist__name"),
            (
                "artist_credit__artist__modification_date",
                "artist_credit__artist__modification_date",
            ),
            ("?", "random"),
            ("tag_matches", "related"),
        )
    )
    format = filters.CharFilter(
        field_name="_",
        method="filter_format",
    )

    has_mbid = filters.BooleanFilter(
        field_name="_",
        method="filter_has_mbid",
    )

    quality_choices = [(0, "low"), (1, "medium"), (2, "high"), (3, "very_high")]
    quality = filters.ChoiceFilter(
        choices=quality_choices,
        method="filter_quality",
    )

    class Meta:
        model = models.Track
        fields = {
            "title": ["exact", "iexact", "startswith", "icontains"],
            "id": ["exact"],
            "album": ["exact"],
            "license": ["exact"],
            "mbid": ["exact"],
        }
        hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["TRACK"]
        include_channels_field = "artist_credit__artist__channel"
        channel_filter_field = "track"
        library_filter_field = "track"
        artist_credit_filter_field = "artist__credit__artist"

    def filter_playable(self, queryset, name, value):
        actor = utils.get_actor_from_request(self.request)
        return queryset.playable_by(actor, value).distinct()

    def filter_artist(self, queryset, name, value):
        return queryset.filter(
            Q(artist_credit__artist=value) | Q(album__artist_credit__artist=value)
        )

    def filter_format(self, queryset, name, value):
        mimetypes = [utils.get_type_from_ext(e) for e in value.split(",")]
        return queryset.filter(uploads__mimetype__in=mimetypes)

    def filter_has_mbid(self, queryset, name, value):
        return queryset.filter(mbid__isnull=(not value))

    def filter_quality(self, queryset, name, value):
        if value == "low":
            return queryset.filter(upload__quality__gte=0)
        if value == "medium":
            return queryset.filter(upload__quality__gte=1)
        if value == "high":
            return queryset.filter(upload__quality__gte=2)
        if value == "very-high":
            return queryset.filter(upload__quality=3)


class UploadFilter(audio_filters.IncludeChannelsFilterSet):
    library = filters.CharFilter("library__uuid")
    channel = filters.CharFilter("library__channel__uuid")
    track = filters.UUIDFilter("track__uuid")
    track_artist = filters.UUIDFilter("track__artist_credit__artist__uuid")
    album_artist = filters.UUIDFilter("track__album__artist_credit__artist__uuid")
    library = filters.UUIDFilter("library__uuid")
    playable = filters.BooleanFilter(field_name="_", method="filter_playable")
    scope = common_filters.ActorScopeFilter(
        actor_field="library__actor",
        distinct=True,
        library_field="library",
    )
    import_status = common_filters.MultipleQueryFilter(coerce=str, distinct=False)
    q = fields.SmartSearchFilter(
        config=search.SearchConfig(
            search_fields={
                "track_artist": {"to": "track__artist__name"},
                "album_artist": {"to": "track__album__artist__name"},
                "album": {"to": "track__album__title"},
                "title": {"to": "track__title"},
            },
            filter_fields={
                "artist": {"to": "track__artist__name__iexact"},
                "mimetype": {"to": "mimetype"},
                "album": {"to": "track__album__title__iexact"},
                "title": {"to": "track__title__iexact"},
                "status": {"to": "import_status"},
            },
        )
    )

    class Meta:
        model = models.Upload
        fields = [
            "import_status",
            "mimetype",
            "import_reference",
        ]
        include_channels_field = "track__artist_credit__artist__channel"

    def filter_playable(self, queryset, name, value):
        actor = utils.get_actor_from_request(self.request)
        return queryset.playable_by(actor, value)


class AlbumFilter(
    RelatedFilterSet,
    ChannelFilterSet,
    LibraryFilterSet,
    audio_filters.IncludeChannelsFilterSet,
    moderation_filters.HiddenContentFilterSet,
):
    playable = filters.BooleanFilter(field_name="_", method="filter_playable")
    q = fields.SearchFilter(
        search_fields=["title", "artist_credit__artist__name"],
        fts_search_fields=["body_text", "artist_credit__artist__body_text"],
    )
    content_category = filters.CharFilter("artist_credit__artist__content_category")
    tag = TAG_FILTER
    scope = common_filters.ActorScopeFilter(
        actor_field="tracks__uploads__library__actor",
        distinct=True,
        library_field="tracks__uploads__library",
    )

    ordering = django_filters.OrderingFilter(
        fields=(
            ("creation_date", "creation_date"),
            ("release_date", "release_date"),
            ("title", "title"),
            (
                "artist_credit__artist__modification_date",
                "artist_credit__artist__modification_date",
            ),
            ("?", "random"),
            ("tag_matches", "related"),
        )
    )
    has_tags = filters.BooleanFilter(
        field_name="_",
        method="filter_has_tags",
    )

    has_mbid = filters.BooleanFilter(
        field_name="_",
        method="filter_has_mbid",
    )

    has_cover = filters.BooleanFilter(
        field_name="_",
        method="filter_has_cover",
    )

    has_release_date = filters.BooleanFilter(
        field_name="_", method="filter_has_release_date"
    )

    artist = filters.ModelChoiceFilter(
        field_name="_", method="filter_artist", queryset=models.Artist.objects.all()
    )

    class Meta:
        model = models.Album
        fields = ["artist_credit", "mbid"]
        hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ALBUM"]
        include_channels_field = "artist_credit__artist__channel"
        channel_filter_field = "track__album"
        library_filter_field = "track__album"

    def filter_playable(self, queryset, name, value):
        actor = utils.get_actor_from_request(self.request)
        return queryset.playable_by(actor, value)

    def filter_has_tags(self, queryset, name, value):
        return queryset.filter(tagged_items__isnull=(not value))

    def filter_has_mbid(self, queryset, name, value):
        return queryset.filter(mbid__isnull=(not value))

    def filter_has_cover(self, queryset, name, value):
        return queryset.filter(attachment_cover__isnull=(not value))

    def filter_has_release_date(self, queryset, name, value):
        return queryset.filter(release_date__isnull=(not value))

    def filter_artist(self, queryset, name, value):
        return queryset.filter(artist_credit__artist=value)


class LibraryFilter(filters.FilterSet):
    q = fields.SearchFilter(
        search_fields=["name"],
    )
    scope = common_filters.ActorScopeFilter(
        actor_field="actor",
        distinct=True,
        library_field="pk",
    )

    class Meta:
        model = models.Library
        fields = ["privacy_level"]
