import os

import django_filters
import PIL
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse

from funkwhale_api.common import serializers, utils
from funkwhale_api.federation import utils as federation_utils
from funkwhale_api.users import models


class FilterSetFixture(django_filters.FilterSet):
    class Meta:
        model = models.User
        fields = ["is_active"]


class SerializerFixture(serializers.ActionSerializer):
    actions = [serializers.Action("test", allow_all=True)]
    filterset_class = FilterSetFixture

    def handle_test(self, objects):
        return {"hello": "world"}


class DangerousSerializerFixture(serializers.ActionSerializer):
    actions = [
        serializers.Action("test", allow_all=True),
        serializers.Action("test_dangerous"),
    ]

    def handle_test(self, objects):
        pass

    def handle_test_dangerous(self, objects):
        pass


class DeleteOnlyInactiveSerializerFixture(serializers.ActionSerializer):
    actions = [
        serializers.Action(
            "test", allow_all=True, qs_filter=lambda qs: qs.filter(is_active=False)
        )
    ]
    filterset_class = FilterSetFixture

    def handle_test(self, objects):
        pass


def test_action_serializer_validates_action():
    data = {"objects": "all", "action": "nope"}
    serializer = SerializerFixture(data, queryset=models.User.objects.none())

    assert serializer.is_valid() is False
    assert "action" in serializer.errors


def test_action_serializer_validates_objects():
    data = {"objects": "nope", "action": "test"}
    serializer = SerializerFixture(data, queryset=models.User.objects.none())

    assert serializer.is_valid() is False
    assert "objects" in serializer.errors


def test_action_serializers_objects_clean_ids(factories):
    user1 = factories["users.User"]()
    factories["users.User"]()

    data = {"objects": [user1.pk], "action": "test"}
    serializer = SerializerFixture(data, queryset=models.User.objects.all())

    assert serializer.is_valid(raise_exception=True) is True
    assert list(serializer.validated_data["objects"]) == [user1]


def test_action_serializers_objects_clean_all(factories):
    user1 = factories["users.User"]()
    user2 = factories["users.User"]()

    data = {"objects": "all", "action": "test"}
    serializer = SerializerFixture(data, queryset=models.User.objects.all())

    assert serializer.is_valid(raise_exception=True) is True
    assert list(serializer.validated_data["objects"]) == [user1, user2]


def test_action_serializers_save(factories, mocker):
    handler = mocker.spy(SerializerFixture, "handle_test")
    factories["users.User"]()
    factories["users.User"]()

    data = {"objects": "all", "action": "test"}
    serializer = SerializerFixture(data, queryset=models.User.objects.all())

    assert serializer.is_valid(raise_exception=True) is True
    result = serializer.save()
    assert result == {"updated": 2, "action": "test", "result": {"hello": "world"}}
    handler.assert_called_once()


def test_action_serializers_filterset(factories):
    factories["users.User"](is_active=False)
    user2 = factories["users.User"](is_active=True)

    data = {"objects": "all", "action": "test", "filters": {"is_active": True}}
    serializer = SerializerFixture(data, queryset=models.User.objects.all())

    assert serializer.is_valid(raise_exception=True) is True
    assert list(serializer.validated_data["objects"]) == [user2]


def test_action_serializers_validates_at_least_one_object():
    data = {"objects": "all", "action": "test"}
    serializer = SerializerFixture(data, queryset=models.User.objects.none())

    assert serializer.is_valid() is False
    assert "non_field_errors" in serializer.errors


def test_dangerous_actions_refuses_all(factories):
    factories["users.User"]()
    data = {"objects": "all", "action": "test_dangerous"}
    serializer = DangerousSerializerFixture(data, queryset=models.User.objects.all())

    assert serializer.is_valid() is False
    assert "non_field_errors" in serializer.errors


def test_action_serializers_can_require_filter(factories):
    user1 = factories["users.User"](is_active=False)
    factories["users.User"](is_active=True)

    data = {"objects": "all", "action": "test"}
    serializer = DeleteOnlyInactiveSerializerFixture(
        data, queryset=models.User.objects.all()
    )

    assert serializer.is_valid(raise_exception=True) is True
    assert list(serializer.validated_data["objects"]) == [user1]


def test_track_fields_for_update(mocker):
    @serializers.track_fields_for_update("field1", "field2")
    class S(serializers.serializers.Serializer):
        field1 = serializers.serializers.CharField()
        field2 = serializers.serializers.CharField()

        def update(self, obj, validated_data):
            for key, value in validated_data.items():
                setattr(obj, key, value)
            return obj

        on_updated_fields = mocker.stub()

    class Obj:
        field1 = "value1"
        field2 = "value2"

    obj = Obj()
    serializer = S(obj, data={"field1": "newvalue1", "field2": "newvalue2"})
    assert serializer.is_valid(raise_exception=True)
    serializer.save()

    serializer.on_updated_fields.assert_called_once_with(
        obj,
        {"field1": "value1", "field2": "value2"},
        {"field1": "newvalue1", "field2": "newvalue2"},
    )


def test_strip_exif_field():
    source_path = os.path.join(os.path.dirname(__file__), "exif.jpg")
    source = PIL.Image.open(source_path)

    assert bool(source._getexif())

    with open(source_path, "rb") as f:
        uploaded = SimpleUploadedFile("source.jpg", f.read(), content_type="image/jpeg")
    field = serializers.StripExifImageField()

    cleaned = PIL.Image.open(field.to_internal_value(uploaded))
    assert cleaned._getexif() is None


def test_attachment_serializer_existing_file(factories, to_api_date):
    attachment = factories["common.Attachment"]()
    expected = {
        "uuid": str(attachment.uuid),
        "size": attachment.size,
        "mimetype": attachment.mimetype,
        "creation_date": to_api_date(attachment.creation_date),
        "urls": {
            "source": attachment.url,
            "original": federation_utils.full_url(attachment.file.url),
            "medium_square_crop": federation_utils.full_url(
                attachment.file.crop["200x200"].url
            ),
            "large_square_crop": federation_utils.full_url(
                attachment.file.crop["600x600"].url
            ),
        },
    }

    serializer = serializers.AttachmentSerializer(attachment)

    assert serializer.data == expected


def test_attachment_serializer_remote_file(factories, to_api_date):
    attachment = factories["common.Attachment"](file=None)
    proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": attachment.uuid})
    expected = {
        "uuid": str(attachment.uuid),
        "size": attachment.size,
        "mimetype": attachment.mimetype,
        "creation_date": to_api_date(attachment.creation_date),
        # everything is the same, except for the urls field because:
        #  - the file isn't available on the local pod
        #  - we need to return different URLs so that the client can trigger
        #    a fetch and get redirected to the desired version
        #
        "urls": {
            "source": attachment.url,
            "original": federation_utils.full_url(proxy_url + "?next=original"),
            "medium_square_crop": federation_utils.full_url(
                proxy_url + "?next=medium_square_crop"
            ),
            "large_square_crop": federation_utils.full_url(
                proxy_url + "?next=large_square_crop"
            ),
        },
    }

    serializer = serializers.AttachmentSerializer(attachment)

    assert serializer.data == expected


def test_content_serializer(factories):
    content = factories["common.Content"]()

    expected = {
        "text": content.text,
        "content_type": content.content_type,
        "html": utils.render_html(content.text, content.content_type),
    }

    serializer = serializers.ContentSerializer(content)

    assert serializer.data == expected
