from unittest import mock

from mopidy.models import Playlist, Track
from mopidy_mpd.protocol import stored_playlists

from tests import protocol


class PlaylistsHandlerTest(protocol.BaseTestCase):
    def test_listplaylist(self):
        self.backend.playlists.set_dummy_playlists(
            [
                Playlist(
                    name="name", uri="dummy:name", tracks=[Track(uri="dummy:a")]
                )
            ]
        )

        self.send_request('listplaylist "name"')

        self.assertInResponse("file: dummy:a")
        self.assertInResponse("OK")

    def test_listplaylist_unicode(self):
        self.backend.playlists.set_dummy_playlists(
            [
                Playlist(
                    name="nàmé", uri="dummy:nàmé", tracks=[Track(uri="dummy:à")]
                )
            ]
        )

        self.send_request('listplaylist "nàmé"')

        self.assertInResponse("file: dummy:à")
        self.assertInResponse("OK")

    def test_listplaylist_without_quotes(self):
        self.backend.playlists.set_dummy_playlists(
            [
                Playlist(
                    name="name", uri="dummy:name", tracks=[Track(uri="dummy:a")]
                )
            ]
        )

        self.send_request("listplaylist name")

        self.assertInResponse("file: dummy:a")
        self.assertInResponse("OK")

    def test_listplaylist_fails_if_no_playlist_is_found(self):
        self.send_request('listplaylist "name"')

        self.assertEqualResponse("ACK [50@0] {listplaylist} No such playlist")

    def test_listplaylist_duplicate(self):
        playlist1 = Playlist(name="a", uri="dummy:a1", tracks=[Track(uri="b")])
        playlist2 = Playlist(name="a", uri="dummy:a2", tracks=[Track(uri="c")])
        self.backend.playlists.set_dummy_playlists([playlist1, playlist2])

        self.send_request('listplaylist "a [2]"')

        self.assertInResponse("file: c")
        self.assertInResponse("OK")

    def test_listplaylistinfo(self):
        tracks = [
            Track(uri="dummy:a", name="Track A", length=5000),
            Track(uri="dummy:é", name="Track é", length=5000),
        ]
        self.backend.library.dummy_library = tracks
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="name", uri="dummy:name", tracks=tracks)]
        )

        self.send_request('listplaylistinfo "name"')

        self.assertInResponse("file: dummy:a")
        self.assertInResponse("Title: Track A")
        self.assertInResponse("Time: 5")
        self.assertInResponse("file: dummy:é")
        self.assertInResponse("Title: Track é")
        self.assertNotInResponse("Track: 0")
        self.assertNotInResponse("Pos: 0")
        self.assertInResponse("OK")

    def test_listplaylistinfo_without_quotes(self):
        tracks = [
            Track(uri="dummy:a"),
        ]
        self.backend.library.dummy_library = tracks
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="name", uri="dummy:name", tracks=tracks)]
        )

        self.send_request("listplaylistinfo name")

        self.assertInResponse("file: dummy:a")
        self.assertNotInResponse("Track: 0")
        self.assertNotInResponse("Pos: 0")
        self.assertInResponse("OK")

    def test_listplaylistinfo_fails_if_no_playlist_is_found(self):
        self.send_request('listplaylistinfo "name"')

        self.assertEqualResponse(
            "ACK [50@0] {listplaylistinfo} No such playlist"
        )

    def test_listplaylistinfo_duplicate(self):
        tracks = [
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),
        ]
        self.backend.library.dummy_library = tracks
        playlist1 = Playlist(name="a", uri="dummy:a1", tracks=tracks[:1])
        playlist2 = Playlist(name="a", uri="dummy:a2", tracks=tracks[1:])
        self.backend.playlists.set_dummy_playlists([playlist1, playlist2])

        self.send_request('listplaylistinfo "a [2]"')

        self.assertInResponse("file: dummy:c")
        self.assertNotInResponse("Track: 0")
        self.assertNotInResponse("Pos: 0")
        self.assertInResponse("OK")

    @mock.patch.object(stored_playlists, "_get_last_modified")
    def test_listplaylists(self, last_modified_mock):
        last_modified_mock.return_value = "2015-08-05T22:51:06Z"
        self.backend.playlists.set_dummy_playlists(
            [
                Playlist(name="a", uri="dummy:a"),
                Playlist(name="é", uri="dummy:é"),
            ]
        )

        self.send_request("listplaylists")

        self.assertInResponse("playlist: a")
        self.assertInResponse("playlist: é")
        # Date without milliseconds and with time zone information
        self.assertInResponse("Last-Modified: 2015-08-05T22:51:06Z")
        self.assertInResponse("OK")

    def test_listplaylists_duplicate(self):
        playlist1 = Playlist(name="a", uri="dummy:a1")
        playlist2 = Playlist(name="a", uri="dummy:a2")
        self.backend.playlists.set_dummy_playlists([playlist1, playlist2])

        self.send_request("listplaylists")

        self.assertInResponse("playlist: a")
        self.assertInResponse("playlist: a [2]")
        self.assertInResponse("OK")

    def test_listplaylists_ignores_playlists_without_name(self):
        last_modified = 1390942873222
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="", uri="dummy:", last_modified=last_modified)]
        )

        self.send_request("listplaylists")

        self.assertNotInResponse("playlist: ")
        self.assertInResponse("OK")

    def test_listplaylists_replaces_newline_with_space(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="a\n", uri="dummy:")]
        )

        self.send_request("listplaylists")

        self.assertInResponse("playlist: a ")
        self.assertNotInResponse("playlist: a\n")
        self.assertInResponse("OK")

    def test_listplaylists_replaces_carriage_return_with_space(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="a\r", uri="dummy:")]
        )

        self.send_request("listplaylists")

        self.assertInResponse("playlist: a ")
        self.assertNotInResponse("playlist: a\r")
        self.assertInResponse("OK")

    def test_listplaylists_replaces_forward_slash_with_pipe(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="a/b", uri="dummy:")]
        )

        self.send_request("listplaylists")

        self.assertInResponse("playlist: a|b")
        self.assertNotInResponse("playlist: a/b")
        self.assertInResponse("OK")

    def test_load_appends_to_tracklist(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),
            Track(uri="dummy:d"),
            Track(uri="dummy:ǫ"),
        ]
        self.backend.library.dummy_library = tracks
        self.core.tracklist.add(uris=["dummy:a", "dummy:b"]).get()

        assert len(self.core.tracklist.get_tracks().get()) == 2
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="A-list", uri="dummy:A-list", tracks=tracks[2:])]
        )

        self.send_request('load "A-list"')

        tracks = self.core.tracklist.get_tracks().get()
        assert 5 == len(tracks)
        assert "dummy:a" == tracks[0].uri
        assert "dummy:b" == tracks[1].uri
        assert "dummy:c" == tracks[2].uri
        assert "dummy:d" == tracks[3].uri
        assert "dummy:ǫ" == tracks[4].uri
        self.assertInResponse("OK")

    def test_load_with_range_loads_part_of_playlist(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),
            Track(uri="dummy:d"),
            Track(uri="dummy:e"),
        ]
        self.backend.library.dummy_library = tracks
        self.core.tracklist.add(uris=["dummy:a", "dummy:b"]).get()

        assert len(self.core.tracklist.get_tracks().get()) == 2
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="A-list", uri="dummy:A-list", tracks=tracks[2:])]
        )

        self.send_request('load "A-list" "1:2"')

        tracks = self.core.tracklist.get_tracks().get()
        assert 3 == len(tracks)
        assert "dummy:a" == tracks[0].uri
        assert "dummy:b" == tracks[1].uri
        assert "dummy:d" == tracks[2].uri
        self.assertInResponse("OK")

    def test_load_with_range_without_end_loads_rest_of_playlist(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),
            Track(uri="dummy:d"),
            Track(uri="dummy:e"),
        ]
        self.backend.library.dummy_library = tracks
        self.core.tracklist.add(uris=["dummy:a", "dummy:b"]).get()

        assert len(self.core.tracklist.get_tracks().get()) == 2
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="A-list", uri="dummy:A-list", tracks=tracks[2:])]
        )

        self.send_request('load "A-list" "1:"')

        tracks = self.core.tracklist.get_tracks().get()
        assert 4 == len(tracks)
        assert "dummy:a" == tracks[0].uri
        assert "dummy:b" == tracks[1].uri
        assert "dummy:d" == tracks[2].uri
        assert "dummy:e" == tracks[3].uri
        self.assertInResponse("OK")

    def test_load_unknown_playlist_acks(self):
        self.send_request('load "unknown playlist"')

        assert 0 == len(self.core.tracklist.get_tracks().get())
        self.assertEqualResponse("ACK [50@0] {load} No such playlist")

        # No invalid name check for load.
        self.send_request('load "unknown/playlist"')
        self.assertEqualResponse("ACK [50@0] {load} No such playlist")

    def test_load_full_track_metadata(self):
        tracks = [
            Track(uri="dummy:a", name="Track A", length=5000),
        ]
        self.backend.library.dummy_library = tracks
        self.backend.playlists.set_dummy_playlists(
            [
                Playlist(
                    name="A-list", uri="dummy:a1", tracks=[Track(uri="dummy:a")]
                )
            ]
        )

        self.send_request('load "A-list"')

        tracks = self.core.tracklist.get_tracks().get()
        assert len(tracks) == 1
        assert tracks[0].uri == "dummy:a"
        assert tracks[0].name == "Track A"
        assert tracks[0].length == 5000

        self.assertInResponse("OK")

    def test_playlistadd(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
        ]
        self.backend.library.dummy_library = tracks
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="name", uri="dummy:a1", tracks=[tracks[0]])]
        )

        self.send_request('playlistadd "name" "dummy:b"')

        self.assertInResponse("OK")
        assert 2 == len(self.backend.playlists.get_items("dummy:a1").get())

    def test_playlistadd_creates_playlist(self):
        tracks = [
            Track(uri="dummy:é"),
        ]
        self.backend.library.dummy_library = tracks

        self.send_request('playlistadd "namé" "dummy:é"')

        self.assertInResponse("OK")
        assert self.backend.playlists.lookup("dummy:namé").get() is not None

    def test_playlistadd_invalid_name_acks(self):
        self.send_request('playlistadd "foo/bar" "dummy:a"')
        self.assertInResponse(
            "ACK [2@0] {playlistadd} playlist name is "
            "invalid: playlist names may not contain "
            "slashes, newlines or carriage returns"
        )

    def test_playlistclear(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="namé", uri="dummy:a1", tracks=[Track(uri="b")])]
        )

        self.send_request('playlistclear "namé"')

        self.assertInResponse("OK")
        assert 0 == len(self.backend.playlists.get_items("dummy:a1").get())

    def test_playlistclear_creates_playlist(self):
        self.send_request('playlistclear "name"')

        self.assertInResponse("OK")
        assert self.backend.playlists.lookup("dummy:name").get() is not None

    def test_playlistclear_creates_playlist_save_fails(self):
        self.backend.playlists.set_allow_save(False)
        self.send_request('playlistclear "name"')

        self.assertInResponse(
            "ACK [0@0] {playlistclear} Backend with "
            'scheme "dummy" failed to save playlist'
        )

    def test_playlistclear_invalid_name_acks(self):
        self.send_request('playlistclear "foo/bar"')
        self.assertInResponse(
            "ACK [2@0] {playlistclear} playlist name is "
            "invalid: playlist names may not contain "
            "slashes, newlines or carriage returns"
        )

    def test_playlistdelete(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),
        ]  # len() == 3
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="namé", uri="dummy:a1", tracks=tracks)]
        )

        self.send_request('playlistdelete "namé" "2"')

        self.assertInResponse("OK")
        assert 2 == len(self.backend.playlists.get_items("dummy:a1").get())

    def test_playlistdelete_save_fails(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),
        ]  # len() == 3
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="name", uri="dummy:a1", tracks=tracks)]
        )
        self.backend.playlists.set_allow_save(False)

        self.send_request('playlistdelete "name" "2"')

        self.assertInResponse(
            "ACK [0@0] {playlistdelete} Backend with "
            'scheme "dummy" failed to save playlist'
        )

    def test_playlistdelete_invalid_name_acks(self):
        self.send_request('playlistdelete "foo/bar" "0"')
        self.assertInResponse(
            "ACK [2@0] {playlistdelete} playlist name is "
            "invalid: playlist names may not contain "
            "slashes, newlines or carriage returns"
        )

    def test_playlistdelete_unknown_playlist_acks(self):
        self.send_request('playlistdelete "foobar" "0"')
        self.assertInResponse("ACK [50@0] {playlistdelete} No such playlist")

    def test_playlistdelete_unknown_index_acks(self):
        self.send_request('save "foobar"')
        self.send_request('playlistdelete "foobar" "0"')
        self.assertInResponse("ACK [2@0] {playlistdelete} Bad song index")

    def test_playlistmove(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),  # this one is getting moved to top
        ]
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="namé", uri="dummy:a1", tracks=tracks)]
        )

        self.send_request('playlistmove "namé" "2" "0"')

        self.assertInResponse("OK")
        assert (
            "dummy:c"
            == self.backend.playlists.get_items("dummy:a1").get()[0].uri
        )

    def test_playlistmove_save_fails(self):
        tracks = [
            Track(uri="dummy:a"),
            Track(uri="dummy:b"),
            Track(uri="dummy:c"),  # this one is getting moved to top
        ]
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="name", uri="dummy:a1", tracks=tracks)]
        )
        self.backend.playlists.set_allow_save(False)

        self.send_request('playlistmove "name" "2" "0"')

        self.assertInResponse(
            "ACK [0@0] {playlistmove} Backend with "
            'scheme "dummy" failed to save playlist'
        )

    def test_playlistmove_invalid_name_acks(self):
        self.send_request('playlistmove "foo/bar" "0" "1"')
        self.assertInResponse(
            "ACK [2@0] {playlistmove} playlist name is "
            "invalid: playlist names may not contain "
            "slashes, newlines or carriage returns"
        )

    def test_playlistmove_unknown_playlist_acks(self):
        self.send_request('playlistmove "foobar" "0" "1"')
        self.assertInResponse("ACK [50@0] {playlistmove} No such playlist")

    def test_playlistmove_unknown_position_acks(self):
        self.send_request('save "foobar"')
        self.send_request('playlistmove "foobar" "0" "1"')
        self.assertInResponse("ACK [2@0] {playlistmove} Bad song index")

    def test_playlistmove_same_index_shortcircuits_everything(self):
        # Bad indexes on unknown playlist:
        self.send_request('playlistmove "foobar" "0" "0"')
        self.assertInResponse("OK")

        self.send_request('playlistmove "foobar" "100000" "100000"')
        self.assertInResponse("OK")

        # Bad indexes on known playlist:
        self.send_request('save "foobar"')

        self.send_request('playlistmove "foobar" "0" "0"')
        self.assertInResponse("OK")

        self.send_request('playlistmove "foobar" "10" "10"')
        self.assertInResponse("OK")

        # Invalid playlist name:
        self.send_request('playlistmove "foo/bar" "0" "0"')
        self.assertInResponse("OK")

    def test_rename(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="old_name", uri="dummy:a1", tracks=[Track(uri="b")])]
        )

        self.send_request('rename "old_name" "new_namé"')

        self.assertInResponse("OK")
        assert self.backend.playlists.lookup("dummy:new_namé").get() is not None

    def test_rename_save_fails(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="old_name", uri="dummy:a1", tracks=[Track(uri="b")])]
        )
        self.backend.playlists.set_allow_save(False)

        self.send_request('rename "old_name" "new_name"')

        self.assertInResponse(
            "ACK [0@0] {rename} Backend with "
            'scheme "dummy" failed to save playlist'
        )

    def test_rename_unknown_playlist_acks(self):
        self.send_request('rename "foo" "bar"')
        self.assertInResponse("ACK [50@0] {rename} No such playlist")

    def test_rename_to_existing_acks(self):
        self.send_request('save "foo"')
        self.send_request('save "bar"')

        self.send_request('rename "foo" "bar"')
        self.assertInResponse("ACK [56@0] {rename} Playlist already exists")

    def test_rename_invalid_name_acks(self):
        expected = (
            "ACK [2@0] {rename} playlist name is invalid: playlist "
            "names may not contain slashes, newlines or carriage "
            "returns"
        )

        self.send_request('rename "foo/bar" "bar"')
        self.assertInResponse(expected)

        self.send_request('rename "foo" "foo/bar"')
        self.assertInResponse(expected)

        self.send_request('rename "bar/foo" "foo/bar"')
        self.assertInResponse(expected)

    def test_rm(self):
        self.backend.playlists.set_dummy_playlists(
            [Playlist(name="namé", uri="dummy:à1", tracks=[Track(uri="b")])]
        )

        self.send_request('rm "namé"')

        self.assertInResponse("OK")
        assert self.backend.playlists.lookup("dummy:à1").get() is None

    def test_rm_unknown_playlist_acks(self):
        self.send_request('rm "name"')
        self.assertInResponse("ACK [50@0] {rm} No such playlist")

    def test_rm_invalid_name_acks(self):
        self.send_request('rm "foo/bar"')
        self.assertInResponse(
            "ACK [2@0] {rm} playlist name is invalid: "
            "playlist names may not contain slashes, "
            "newlines or carriage returns"
        )

    def test_save(self):
        self.send_request('save "namé"')

        self.assertInResponse("OK")
        assert self.backend.playlists.lookup("dummy:namé").get() is not None

    def test_save_fails(self):
        self.backend.playlists.set_allow_save(False)
        self.send_request('save "name"')

        self.assertInResponse(
            "ACK [0@0] {save} Backend with "
            'scheme "dummy" failed to save playlist'
        )

    def test_save_invalid_name_acks(self):
        self.send_request('save "foo/bar"')
        self.assertInResponse(
            "ACK [2@0] {save} playlist name is invalid: "
            "playlist names may not contain slashes, "
            "newlines or carriage returns"
        )
