import json
import urllib.parse

import persisting_theory
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from rest_framework import serializers

from funkwhale_api.audio import models as audio_models
from funkwhale_api.common import fields as common_fields
from funkwhale_api.common import preferences
from funkwhale_api.federation import models as federation_models
from funkwhale_api.federation import utils as federation_utils
from funkwhale_api.music import models as music_models
from funkwhale_api.playlists import models as playlists_models

from . import models, tasks


class FilteredArtistSerializer(serializers.ModelSerializer):
    class Meta:
        model = music_models.Artist
        fields = ["id", "name"]


class ModerationTargetSerializer(serializers.Serializer):
    type = serializers.ChoiceField(choices=["artist"])
    id = serializers.CharField()

    def to_representation(self, value):
        if value["type"] == "artist":
            data = FilteredArtistSerializer(value["obj"]).data
            data.update({"type": "artist"})
            return data

    def to_internal_value(self, value):
        if value["type"] == "artist":
            field = serializers.PrimaryKeyRelatedField(
                queryset=music_models.Artist.objects.all()
            )
        value["obj"] = field.to_internal_value(value["id"])
        return value


class UserFilterSerializer(serializers.ModelSerializer):
    target = ModerationTargetSerializer()

    class Meta:
        model = models.UserFilter
        fields = ["uuid", "target", "creation_date"]
        read_only_fields = ["uuid", "creation_date"]

    def validate(self, data):
        target = data.pop("target")
        if target["type"] == "artist":
            data["target_artist"] = target["obj"]

        return data


state_serializers = persisting_theory.Registry()


class DescriptionStateMixin:
    def get_description(self, o):
        if o.description:
            return o.description.text


TAGS_FIELD = serializers.ListField(source="get_tags")


@state_serializers.register(name="music.Artist")
class ArtistStateSerializer(DescriptionStateMixin, serializers.ModelSerializer):
    tags = TAGS_FIELD

    class Meta:
        model = music_models.Artist
        fields = [
            "id",
            "name",
            "mbid",
            "fid",
            "creation_date",
            "uuid",
            "tags",
            "content_category",
            "description",
        ]


@state_serializers.register(name="music.ArtistCredit")
class ArtistCreditStateSerializer(DescriptionStateMixin, serializers.ModelSerializer):
    artist = ArtistStateSerializer()

    class Meta:
        model = music_models.ArtistCredit
        fields = [
            "id",
            "credit",
            "mbid",
            "fid",
            "creation_date",
            "uuid",
            "artist",
            "joinphrase",
            "index",
        ]


@state_serializers.register(name="music.Album")
class AlbumStateSerializer(DescriptionStateMixin, serializers.ModelSerializer):
    tags = TAGS_FIELD
    artist_credit = ArtistCreditStateSerializer(many=True)

    class Meta:
        model = music_models.Album
        fields = [
            "id",
            "title",
            "mbid",
            "fid",
            "creation_date",
            "uuid",
            "artist_credit",
            "release_date",
            "tags",
            "description",
        ]


@state_serializers.register(name="music.Track")
class TrackStateSerializer(DescriptionStateMixin, serializers.ModelSerializer):
    tags = TAGS_FIELD
    artist_credit = ArtistCreditStateSerializer(many=True)
    album = AlbumStateSerializer()

    class Meta:
        model = music_models.Track
        fields = [
            "id",
            "title",
            "mbid",
            "fid",
            "creation_date",
            "uuid",
            "artist_credit",
            "album",
            "disc_number",
            "position",
            "license",
            "copyright",
            "tags",
            "description",
        ]


@state_serializers.register(name="music.Library")
class LibraryStateSerializer(serializers.ModelSerializer):
    class Meta:
        model = music_models.Library
        fields = [
            "id",
            "uuid",
            "fid",
            "name",
            "creation_date",
            "privacy_level",
        ]


@state_serializers.register(name="playlists.Playlist")
class PlaylistStateSerializer(serializers.ModelSerializer):
    class Meta:
        model = playlists_models.Playlist
        fields = ["id", "name", "creation_date", "privacy_level"]


@state_serializers.register(name="federation.Actor")
class ActorStateSerializer(serializers.ModelSerializer):
    class Meta:
        model = federation_models.Actor
        fields = [
            "fid",
            "name",
            "preferred_username",
            "full_username",
            "summary",
            "domain",
            "type",
            "creation_date",
        ]


@state_serializers.register(name="audio.Channel")
class ChannelStateSerializer(serializers.ModelSerializer):
    rss_url = serializers.CharField(source="get_rss_url")
    name = serializers.CharField(source="artist.name")
    full_username = serializers.CharField(source="actor.full_username")
    domain = serializers.CharField(source="actor.domain_id")
    description = serializers.SerializerMethodField()
    tags = serializers.ListField(source="artist.get_tags")
    content_category = serializers.CharField(source="artist.content_category")

    class Meta:
        model = audio_models.Channel
        fields = [
            "uuid",
            "name",
            "rss_url",
            "metadata",
            "full_username",
            "description",
            "domain",
            "creation_date",
            "tags",
            "content_category",
        ]

    def get_description(self, o):
        if o.artist.description:
            return o.artist.description.text


def get_actor_query(attr, value):
    data = federation_utils.get_actor_data_from_username(value)
    return federation_utils.get_actor_from_username_data_query(None, data)


def get_target_owner(target):
    mapping = {
        audio_models.Channel: lambda t: t.attributed_to,
        music_models.Artist: lambda t: t.attributed_to,
        music_models.Album: lambda t: t.attributed_to,
        music_models.Track: lambda t: t.attributed_to,
        music_models.Library: lambda t: t.actor,
        playlists_models.Playlist: lambda t: t.actor,
        federation_models.Actor: lambda t: t,
    }

    return mapping[target.__class__](target)


TARGET_CONFIG = {
    "channel": {
        "queryset": audio_models.Channel.objects.all(),
        "id_attr": "uuid",
        "id_field": serializers.UUIDField(),
    },
    "artist": {"queryset": music_models.Artist.objects.all()},
    "artist_credit": {"queryset": music_models.ArtistCredit.objects.all()},
    "album": {"queryset": music_models.Album.objects.all()},
    "track": {"queryset": music_models.Track.objects.all()},
    "library": {
        "queryset": music_models.Library.objects.all(),
        "id_attr": "uuid",
        "id_field": serializers.UUIDField(),
    },
    "playlist": {"queryset": playlists_models.Playlist.objects.all()},
    "account": {
        "queryset": federation_models.Actor.objects.all(),
        "id_attr": "full_username",
        "id_field": serializers.EmailField(),
        "get_query": get_actor_query,
    },
}
TARGET_FIELD = common_fields.GenericRelation(TARGET_CONFIG)


def get_target_state(target):
    state = {}
    target_state_serializer = state_serializers[target._meta.label]

    state = target_state_serializer(target).data
    # freeze target type/id in JSON so even if the corresponding object is deleted
    # we can have the info and display it in the frontend
    target_data = TARGET_FIELD.to_representation(target)
    state["_target"] = json.loads(json.dumps(target_data, cls=DjangoJSONEncoder))

    if "fid" in state:
        state["domain"] = urllib.parse.urlparse(state["fid"]).hostname

    state["is_local"] = (
        state.get("domain", settings.FEDERATION_HOSTNAME)
        == settings.FEDERATION_HOSTNAME
    )

    return state


class ReportSerializer(serializers.ModelSerializer):
    target = TARGET_FIELD

    class Meta:
        model = models.Report
        fields = [
            "uuid",
            "summary",
            "creation_date",
            "handled_date",
            "is_handled",
            "submitter_email",
            "target",
            "type",
        ]
        read_only_fields = ["uuid", "is_handled", "creation_date", "handled_date"]

    def validate(self, validated_data):
        validated_data = super().validate(validated_data)
        submitter = self.context.get("submitter")
        if submitter:
            # we have an authenticated actor so no need to check further
            return validated_data

        unauthenticated_report_types = preferences.get(
            "moderation__unauthenticated_report_types"
        )
        if validated_data["type"] not in unauthenticated_report_types:
            raise serializers.ValidationError(
                "You need an account to submit this report"
            )

        if not validated_data.get("submitter_email"):
            raise serializers.ValidationError(
                "You need to provide an e-mail address to submit this report"
            )

        return validated_data

    def create(self, validated_data):
        validated_data["target_state"] = get_target_state(validated_data["target"])
        validated_data["target_owner"] = get_target_owner(validated_data["target"])
        r = super().create(validated_data)
        tasks.signals.report_created.send(sender=None, report=r)
        return r
