import pytest
from rest_framework import exceptions


def test_can_insert_plt(factories):
    plt = factories["playlists.PlaylistTrack"](index=None)
    modification_date = plt.playlist.modification_date

    assert plt.index is None

    plt.playlist.insert(plt)
    plt.refresh_from_db()

    assert plt.index == 0
    assert plt.playlist.modification_date > modification_date


def test_insert_use_last_idx_by_default(factories):
    playlist = factories["playlists.Playlist"]()
    plts = factories["playlists.PlaylistTrack"].create_batch(size=3, playlist=playlist)

    for i, plt in enumerate(plts):
        index = playlist.insert(plt)
        plt.refresh_from_db()

        assert index == i
        assert plt.index == i


def test_can_insert_at_index(factories):
    playlist = factories["playlists.Playlist"]()
    first = factories["playlists.PlaylistTrack"](playlist=playlist)
    playlist.insert(first)
    new_first = factories["playlists.PlaylistTrack"](playlist=playlist)
    index = playlist.insert(new_first, index=0)
    first.refresh_from_db()
    new_first.refresh_from_db()

    assert index == 0
    assert first.index == 1
    assert new_first.index == 0


def test_can_insert_and_move(factories):
    playlist = factories["playlists.Playlist"]()
    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
    third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2)

    playlist.insert(second, index=0)

    first.refresh_from_db()
    second.refresh_from_db()
    third.refresh_from_db()

    assert third.index == 2
    assert second.index == 0
    assert first.index == 1


def test_can_insert_and_move_last_to_0(factories):
    playlist = factories["playlists.Playlist"]()
    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
    third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2)

    playlist.insert(third, index=0)

    first.refresh_from_db()
    second.refresh_from_db()
    third.refresh_from_db()

    assert third.index == 0
    assert first.index == 1
    assert second.index == 2


def test_cannot_insert_at_wrong_index(factories):
    plt = factories["playlists.PlaylistTrack"]()
    new = factories["playlists.PlaylistTrack"](playlist=plt.playlist)
    with pytest.raises(exceptions.ValidationError):
        plt.playlist.insert(new, 2)


def test_cannot_insert_at_negative_index(factories):
    plt = factories["playlists.PlaylistTrack"]()
    new = factories["playlists.PlaylistTrack"](playlist=plt.playlist)
    with pytest.raises(exceptions.ValidationError):
        plt.playlist.insert(new, -1)


def test_remove_update_indexes(factories):
    playlist = factories["playlists.Playlist"]()
    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
    third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2)

    second.delete(update_indexes=True)

    first.refresh_from_db()
    third.refresh_from_db()

    assert first.index == 0
    assert third.index == 1


def test_can_insert_many(factories):
    playlist = factories["playlists.Playlist"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
    tracks = factories["music.Track"].create_batch(size=3)
    plts = playlist.insert_many(tracks)
    for i, plt in enumerate(plts):
        assert plt.index == i + 1
        assert plt.track == tracks[i]
        assert plt.playlist == playlist


def test_insert_many_honor_max_tracks(preferences, factories):
    preferences["playlists__max_tracks"] = 4
    playlist = factories["playlists.Playlist"]()
    factories["playlists.PlaylistTrack"].create_batch(size=2, playlist=playlist)
    track = factories["music.Track"]()
    with pytest.raises(exceptions.ValidationError):
        playlist.insert_many([track, track, track])


def test_can_insert_duplicate_by_default(factories):
    playlist = factories["playlists.Playlist"]()
    track = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=track)

    new = factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=track)
    playlist.insert(new)

    new.refresh_from_db()
    assert new.index == 1


def test_cannot_insert_duplicate(factories):
    playlist = factories["playlists.Playlist"](name="playlist")
    track = factories["music.Track"]()

    factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=track)

    with pytest.raises(exceptions.ValidationError) as e:
        new = factories["playlists.PlaylistTrack"](
            playlist=playlist, index=1, track=track
        )
        playlist.insert(new, allow_duplicates=False)

    errors = e.value.detail["non_field_errors"]
    assert len(errors) == 1

    err = errors[0]
    assert err["code"] == "tracks_already_exist_in_playlist"
    assert err["playlist_name"] == "playlist"
    assert err["tracks"] == [track.title]


def test_can_insert_track_to_playlist_with_existing_duplicates(factories):
    playlist = factories["playlists.Playlist"]()
    existing_duplicate = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](
        playlist=playlist, index=0, track=existing_duplicate
    )
    factories["playlists.PlaylistTrack"](
        playlist=playlist, index=1, track=existing_duplicate
    )
    factories["playlists.PlaylistTrack"](
        playlist=playlist, index=2, track=existing_duplicate
    )

    new_track = factories["music.Track"]()
    new_plt = factories["playlists.PlaylistTrack"](
        playlist=playlist, index=3, track=new_track
    )

    # no error
    playlist.insert(new_plt, allow_duplicates=False)


def test_can_insert_duplicate_with_override(factories):
    playlist = factories["playlists.Playlist"]()
    track = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=track)

    new = factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=track)
    playlist.insert(new, allow_duplicates=True)

    new.refresh_from_db()
    assert new.index == 1


def test_can_insert_many_duplicates_by_default(factories):
    playlist = factories["playlists.Playlist"]()

    t1 = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=t1)

    t2 = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=t2)

    t4 = factories["music.Track"]()

    tracks = [t1, t4, t2]

    plts = playlist.insert_many(tracks)

    assert len(plts) == 3
    assert plts[0].track == t1
    assert plts[1].track == t4
    assert plts[2].track == t2


def test_cannot_insert_many_duplicates(factories):
    playlist = factories["playlists.Playlist"](name="playlist")

    t1 = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=t1)

    t2 = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=t2)

    t4 = factories["music.Track"]()

    with pytest.raises(exceptions.ValidationError) as e:
        tracks = [t1, t4, t2]
        playlist.insert_many(tracks, allow_duplicates=False)

    errors = e.value.detail["non_field_errors"]
    assert len(errors) == 1

    err = errors[0]
    assert err["code"] == "tracks_already_exist_in_playlist"
    assert err["playlist_name"] == "playlist"
    assert err["tracks"] == [t1.title, t2.title]


def test_can_insert_many_duplicates_with_override(factories):
    playlist = factories["playlists.Playlist"]()

    t1 = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=t1)

    t2 = factories["music.Track"]()
    factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=t2)

    t4 = factories["music.Track"]()

    tracks = [t1, t4, t2]

    plts = playlist.insert_many(tracks, allow_duplicates=True)

    assert len(plts) == 3
    assert plts[0].track == t1
    assert plts[1].track == t4
    assert plts[2].track == t2


@pytest.mark.parametrize(
    "privacy_level,expected", [("me", False), ("instance", False), ("everyone", True)]
)
def test_playlist_track_playable_by_anonymous(privacy_level, expected, factories):
    plt = factories["playlists.PlaylistTrack"]()
    track = plt.track
    factories["music.Upload"](
        track=track, library__privacy_level=privacy_level, import_status="finished"
    )
    queryset = plt.__class__.objects.playable_by(None).annotate_playable_by_actor(None)
    match = plt in list(queryset)
    assert match is expected
    if expected:
        assert bool(queryset.first().is_playable_by_actor) is expected


@pytest.mark.parametrize(
    "privacy_level,expected", [("me", False), ("instance", False), ("everyone", True)]
)
def test_playlist_playable_by_anonymous(privacy_level, expected, factories):
    plt = factories["playlists.PlaylistTrack"]()
    playlist = plt.playlist
    track = plt.track
    factories["music.Upload"](
        track=track, library__privacy_level=privacy_level, import_status="finished"
    )
    queryset = playlist.__class__.objects.playable_by(None).with_playable_plts(None)
    match = playlist in list(queryset)
    assert match is expected
