import pytest
from django import db

from funkwhale_api.federation import models
from funkwhale_api.music import models as music_models


def test_cannot_duplicate_actor(factories):
    actor = factories["federation.Actor"]()

    with pytest.raises(db.IntegrityError):
        factories["federation.Actor"](
            domain=actor.domain, preferred_username=actor.preferred_username
        )


def test_cannot_duplicate_follow(factories):
    follow = factories["federation.Follow"]()

    with pytest.raises(db.IntegrityError):
        factories["federation.Follow"](target=follow.target, actor=follow.actor)


def test_follow_federation_url(factories):
    follow = factories["federation.Follow"](local=True)
    expected = f"{follow.actor.fid}#follows/{follow.uuid}"

    assert follow.get_federation_id() == expected


def test_actor_get_quota(factories):
    library = factories["music.Library"]()
    factories["music.Upload"](
        library=library,
        import_status="pending",
        audio_file__from_path=None,
        audio_file__data=b"a",
    )
    factories["music.Upload"](
        library=library,
        import_status="skipped",
        audio_file__from_path=None,
        audio_file__data=b"aa",
    )
    factories["music.Upload"](
        library=library,
        import_status="errored",
        audio_file__from_path=None,
        audio_file__data=b"aaa",
    )
    factories["music.Upload"](
        library=library,
        import_status="finished",
        audio_file__from_path=None,
        audio_file__data=b"aaaa",
    )
    factories["music.Upload"](
        library=library,
        import_status="draft",
        audio_file__from_path=None,
        audio_file__data=b"aaaaa",
    )

    # this one is imported in place and don't count
    factories["music.Upload"](
        library=library,
        import_status="finished",
        source="file://test",
        audio_file=None,
        size=42,
    )
    # this one is imported in place but count because there is a mapped file
    factories["music.Upload"](
        library=library,
        import_status="finished",
        source="file://test2",
        audio_file__from_path=None,
        audio_file__data=b"aaaa",
    )

    # this one is in a channel
    channel = factories["audio.Channel"](attributed_to=library.actor)
    factories["music.Upload"](
        library=channel.library,
        import_status="finished",
        audio_file__from_path=None,
        audio_file__data=b"aaaaa",
    )

    expected = {
        "total": 24,
        "pending": 1,
        "skipped": 2,
        "errored": 3,
        "finished": 13,
        "draft": 5,
    }

    assert library.actor.get_current_usage() == expected


@pytest.mark.parametrize(
    "value, expected",
    [
        ("Domain.com", "domain.com"),
        ("hello-WORLD.com", "hello-world.com"),
        ("posés.com", "posés.com"),
    ],
)
def test_domain_name_saved_properly(value, expected, factories):
    domain = factories["federation.Domain"](name=value)
    assert domain.name == expected


def test_external_domains(factories, settings):
    d1 = factories["federation.Domain"]()
    d2 = factories["federation.Domain"]()
    settings.FEDERATION_HOSTNAME = d1.pk

    assert list(models.Domain.objects.external()) == [d2]


def test_domain_stats(factories):
    expected = {
        "actors": 0,
        "libraries": 0,
        "tracks": 0,
        "albums": 0,
        "channels": 0,
        "uploads": 0,
        "artists": 0,
        "outbox_activities": 0,
        "received_library_follows": 0,
        "emitted_library_follows": 0,
        "media_total_size": 0,
        "media_downloaded_size": 0,
    }

    domain = factories["federation.Domain"]()

    assert domain.get_stats() == expected


def test_actor_stats(factories):
    expected = {
        "libraries": 0,
        "tracks": 0,
        "albums": 0,
        "uploads": 0,
        "artists": 0,
        "reports": 0,
        "channels": 0,
        "requests": 0,
        "outbox_activities": 0,
        "received_library_follows": 0,
        "emitted_library_follows": 0,
        "media_total_size": 0,
        "media_downloaded_size": 0,
    }

    actor = factories["federation.Actor"]()

    assert actor.get_stats() == expected


def test_actor_can_manage_false(mocker, factories):
    obj = mocker.Mock()
    actor = factories["federation.Actor"]()

    assert actor.can_manage(obj) is False


def test_actor_can_manage_attributed_to(mocker, factories):
    actor = factories["federation.Actor"]()
    obj = mocker.Mock(attributed_to_id=actor.pk)

    assert actor.can_manage(obj) is True


def test_actor_can_manage_domain_not_service_actor(mocker, factories):
    actor = factories["federation.Actor"]()
    obj = mocker.Mock(fid=f"https://{actor.domain_id}/hello")

    assert actor.can_manage(obj) is False


def test_actor_can_manage_domain_service_actor(mocker, factories):
    actor = factories["federation.Actor"]()
    actor.domain.service_actor = actor
    actor.domain.save()
    obj = mocker.Mock(fid=f"https://{actor.domain_id}/hello")

    assert actor.can_manage(obj) is True


def test_can_create_fetch_for_object(factories):
    track = factories["music.Track"](fid="http://test.domain")
    fetch = factories["federation.Fetch"](object=track)
    assert fetch.url == "http://test.domain"
    assert fetch.status == "pending"
    assert fetch.detail == {}
    assert fetch.object == track


@pytest.mark.parametrize(
    "initial_approved, updated_approved, initial_playable_tracks, updated_playable_tracks",
    [
        (
            True,
            False,
            {"owner": [0], "follower": [0], "local_actor": [], None: []},
            {"owner": [0], "follower": [], "local_actor": [], None: []},
        ),
        (
            False,
            True,
            {"owner": [0], "follower": [], "local_actor": [], None: []},
            {"owner": [0], "follower": [0], "local_actor": [], None: []},
        ),
    ],
)
def test_update_library_follow_approved_create_entries(
    initial_approved,
    updated_approved,
    initial_playable_tracks,
    updated_playable_tracks,
    factories,
):
    actors = {
        "owner": factories["federation.Actor"](local=True),
        "follower": factories["federation.Actor"](local=True),
        "local_actor": factories["federation.Actor"](local=True),
        None: None,
    }
    library = factories["music.Library"](actor=actors["owner"], privacy_level="me")

    tracks = [
        factories["music.Upload"](playable=True, library=library).track,
        factories["music.Upload"](library=library, import_status="pending").track,
    ]

    follow = factories["federation.LibraryFollow"](
        target=library, actor=actors["follower"], approved=initial_approved
    )

    for actor_name, expected in initial_playable_tracks.items():
        actor = actors[actor_name]
        expected_tracks = [tracks[i] for i in expected]
        assert list(music_models.Track.objects.playable_by(actor)) == expected_tracks

    follow.approved = updated_approved
    follow.save()

    for actor_name, expected in updated_playable_tracks.items():
        actor = actors[actor_name]
        expected_tracks = [tracks[i] for i in expected]
        assert list(music_models.Track.objects.playable_by(actor)) == expected_tracks


def test_update_library_follow_delete_delete_denormalization_entries(
    factories,
):
    updated_playable_tracks = {"owner": [0], "follower": []}
    actors = {
        "owner": factories["federation.Actor"](local=True),
        "follower": factories["federation.Actor"](local=True),
    }
    library = factories["music.Library"](actor=actors["owner"], privacy_level="me")

    tracks = [
        factories["music.Upload"](playable=True, library=library).track,
        factories["music.Upload"](library=library, import_status="pending").track,
    ]

    follow = factories["federation.LibraryFollow"](
        target=library, actor=actors["follower"], approved=True
    )

    follow.delete()

    for actor_name, expected in updated_playable_tracks.items():
        actor = actors[actor_name]
        expected_tracks = [tracks[i] for i in expected]
        assert list(music_models.Track.objects.playable_by(actor)) == expected_tracks
