#!/usr/bin/env python
# coding: utf-8
#
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2017-2018 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.

import uuid

from pony.orm import db_session

from supysonic.db import Folder, Artist, Album, Track, Playlist, User

from .apitestbase import ApiTestBase


class PlaylistTestCase(ApiTestBase):
    def setUp(self):
        super(PlaylistTestCase, self).setUp()

        with db_session:
            root = Folder(root=True, name="Root folder", path="tests/assets")
            artist = Artist(name="Artist")
            album = Album(name="Album", artist=artist)

            songs = {}
            for num, song in enumerate(["One", "Two", "Three", "Four"]):
                track = Track(
                    disc=1,
                    number=num,
                    title=song,
                    duration=2,
                    album=album,
                    artist=artist,
                    bitrate=320,
                    path="tests/assets/" + song,
                    last_modification=0,
                    root_folder=root,
                    folder=root,
                )
                songs[song] = track

            users = {u.name: u for u in User.select()}

            playlist = Playlist(user=users["alice"], name="Alice's")
            playlist.add(songs["One"])
            playlist.add(songs["Three"])

            playlist = Playlist(user=users["alice"], public=True, name="Alice's public")
            playlist.add(songs["One"])
            playlist.add(songs["Two"])

            playlist = Playlist(user=users["bob"], name="Bob's")
            playlist.add(songs["Two"])
            playlist.add(songs["Four"])

    def test_get_playlists(self):
        # get own playlists
        rv, child = self._make_request("getPlaylists", tag="playlists")
        self.assertEqual(len(child), 2)
        self.assertEqual(child[0].get("owner"), "alice")
        self.assertEqual(child[1].get("owner"), "alice")

        # get own and public
        rv, child = self._make_request(
            "getPlaylists", {"u": "bob", "p": "B0b"}, tag="playlists"
        )
        self.assertEqual(len(child), 2)
        self.assertTrue(
            child[0].get("owner") == "alice" or child[1].get("owner") == "alice"
        )
        self.assertTrue(
            child[0].get("owner") == "bob" or child[1].get("owner") == "bob"
        )
        self.assertIsNotNone(
            self._find(child, "./playlist[@owner='alice'][@public='true']")
        )

        # get other
        rv, child = self._make_request(
            "getPlaylists", {"username": "bob"}, tag="playlists"
        )
        self.assertEqual(len(child), 1)
        self.assertEqual(child[0].get("owner"), "bob")

        # get other when not admin
        self._make_request(
            "getPlaylists", {"u": "bob", "p": "B0b", "username": "alice"}, error=50
        )

        # get from unknown user
        self._make_request("getPlaylists", {"username": "johndoe"}, error=70)

    def test_get_playlist(self):
        # missing param
        self._make_request("getPlaylist", error=10)

        # invalid id
        self._make_request("getPlaylist", {"id": 1234}, error=0)

        # unknown
        self._make_request("getPlaylist", {"id": str(uuid.uuid4())}, error=70)

        # other's private from non admin
        with db_session:
            playlist = Playlist.get(lambda p: not p.public and p.user.name == "alice")
        self._make_request(
            "getPlaylist", {"u": "bob", "p": "B0b", "id": str(playlist.id)}, error=50
        )

        # standard
        rv, child = self._make_request("getPlaylists", tag="playlists")
        self._make_request("getPlaylist", {"id": child[0].get("id")}, tag="playlist")
        rv, child = self._make_request(
            "getPlaylist", {"id": child[1].get("id")}, tag="playlist"
        )
        self.assertEqual(child.get("songCount"), "2")
        self.assertEqual(
            self._xpath(child, "count(./entry)"), 2
        )  # don't count children, there may be 'allowedUser's (even though not supported by supysonic)
        self.assertEqual(child.get("duration"), "4")
        self.assertEqual(child[0].get("title"), "One")
        self.assertTrue(
            child[1].get("title") == "Two" or child[1].get("title") == "Three"
        )  # depending on 'getPlaylists' result ordering

    def test_create_playlist(self):
        self._make_request("createPlaylist", error=10)
        self._make_request(
            "createPlaylist", {"name": "wrongId", "songId": "abc"}, error=0
        )
        self._make_request(
            "createPlaylist",
            {"name": "unknownId", "songId": str(uuid.uuid4())},
            error=70,
        )
        self._make_request("createPlaylist", {"playlistId": "abc"}, error=0)
        self._make_request(
            "createPlaylist", {"playlistId": str(uuid.uuid4())}, error=70
        )

        # create
        self._make_request("createPlaylist", {"name": "new playlist"}, skip_post=True)
        rv, child = self._make_request("getPlaylists", tag="playlists")
        self.assertEqual(len(child), 3)
        playlist = self._find(child, "./playlist[@name='new playlist']")
        self.assertEqual(len(playlist), 0)

        # "update" newly created
        self._make_request("createPlaylist", {"playlistId": playlist.get("id")})
        rv, child = self._make_request("getPlaylists", tag="playlists")
        self.assertEqual(len(child), 3)

        # renaming
        self._make_request(
            "createPlaylist", {"playlistId": playlist.get("id"), "name": "renamed"}
        )
        rv, child = self._make_request("getPlaylists", tag="playlists")
        self.assertEqual(len(child), 3)
        self.assertIsNone(self._find(child, "./playlist[@name='new playlist']"))
        playlist = self._find(child, "./playlist[@name='renamed']")
        self.assertIsNotNone(playlist)

        # update from other user
        self._make_request(
            "createPlaylist",
            {"u": "bob", "p": "B0b", "playlistId": playlist.get("id")},
            error=50,
        )

        # create more useful playlist
        with db_session:
            songs = {s.title: str(s.id) for s in Track.select()}
        self._make_request(
            "createPlaylist",
            {
                "name": "songs",
                "songId": list(map(lambda s: songs[s], ["Three", "One", "Two"])),
            },
            skip_post=True,
        )
        with db_session:
            playlist = Playlist.get(name="songs")
        self.assertIsNotNone(playlist)
        rv, child = self._make_request(
            "getPlaylist", {"id": str(playlist.id)}, tag="playlist"
        )
        self.assertEqual(child.get("songCount"), "3")
        self.assertEqual(self._xpath(child, "count(./entry)"), 3)
        self.assertEqual(child[0].get("title"), "Three")
        self.assertEqual(child[1].get("title"), "One")
        self.assertEqual(child[2].get("title"), "Two")

        # update
        self._make_request(
            "createPlaylist",
            {"playlistId": str(playlist.id), "songId": songs["Two"]},
            skip_post=True,
        )
        rv, child = self._make_request(
            "getPlaylist", {"id": str(playlist.id)}, tag="playlist"
        )
        self.assertEqual(child.get("songCount"), "1")
        self.assertEqual(self._xpath(child, "count(./entry)"), 1)
        self.assertEqual(child[0].get("title"), "Two")

    @db_session
    def assertPlaylistCountEqual(self, count):
        self.assertEqual(Playlist.select().count(), count)

    def test_delete_playlist(self):
        # check params
        self._make_request("deletePlaylist", error=10)
        self._make_request("deletePlaylist", {"id": "string"}, error=0)
        self._make_request("deletePlaylist", {"id": str(uuid.uuid4())}, error=70)

        # delete unowned when not admin
        with db_session:
            playlist = Playlist.select(lambda p: p.user.name == "alice").first()
        self._make_request(
            "deletePlaylist", {"u": "bob", "p": "B0b", "id": str(playlist.id)}, error=50
        )
        self.assertPlaylistCountEqual(3)

        # delete owned
        self._make_request("deletePlaylist", {"id": str(playlist.id)}, skip_post=True)
        self.assertPlaylistCountEqual(2)
        self._make_request("deletePlaylist", {"id": str(playlist.id)}, error=70)
        self.assertPlaylistCountEqual(2)

        # delete unowned when admin
        with db_session:
            playlist = Playlist.get(lambda p: p.user.name == "bob")
        self._make_request("deletePlaylist", {"id": str(playlist.id)}, skip_post=True)
        self.assertPlaylistCountEqual(1)

    def test_update_playlist(self):
        self._make_request("updatePlaylist", error=10)
        self._make_request("updatePlaylist", {"playlistId": 1234}, error=0)
        self._make_request(
            "updatePlaylist", {"playlistId": str(uuid.uuid4())}, error=70
        )

        with db_session:
            playlist = (
                Playlist.select(lambda p: p.user.name == "alice")
                .order_by(Playlist.created)
                .first()
            )
        pid = str(playlist.id)
        self._make_request(
            "updatePlaylist", {"playlistId": pid, "songIdToAdd": "string"}, error=0
        )
        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIndexToRemove": "string"},
            error=0,
        )

        name = str(playlist.name)
        self._make_request(
            "updatePlaylist",
            {"u": "bob", "p": "B0b", "playlistId": pid, "name": "new name"},
            error=50,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertEqual(child.get("name"), name)
        self.assertEqual(self._xpath(child, "count(./entry)"), 2)

        self._make_request(
            "updatePlaylist", {"playlistId": pid, "name": "new name"}, skip_post=True
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertEqual(child.get("name"), "new name")
        self.assertEqual(self._xpath(child, "count(./entry)"), 2)

        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIndexToRemove": [-1, 2]},
            skip_post=True,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertEqual(self._xpath(child, "count(./entry)"), 2)

        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIndexToRemove": [0, 2]},
            skip_post=True,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertEqual(self._xpath(child, "count(./entry)"), 1)
        self.assertEqual(self._find(child, "./entry").get("title"), "Three")

        with db_session:
            songs = {s.title: str(s.id) for s in Track.select()}

        self._make_request(
            "updatePlaylist",
            {
                "playlistId": pid,
                "songIdToAdd": [songs["One"], songs["Two"], songs["Two"]],
            },
            skip_post=True,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertSequenceEqual(
            self._xpath(child, "./entry/@title"), ["Three", "One", "Two", "Two"]
        )

        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIndexToRemove": [2, 1]},
            skip_post=True,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertSequenceEqual(self._xpath(child, "./entry/@title"), ["Three", "Two"])

        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIdToAdd": songs["One"]},
            skip_post=True,
        )
        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIndexToRemove": [1, 1]},
            skip_post=True,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertSequenceEqual(self._xpath(child, "./entry/@title"), ["Three", "One"])

        self._make_request(
            "updatePlaylist",
            {"playlistId": pid, "songIdToAdd": str(uuid.uuid4())},
            error=70,
        )
        rv, child = self._make_request("getPlaylist", {"id": pid}, tag="playlist")
        self.assertEqual(self._xpath(child, "count(./entry)"), 2)


if __name__ == "__main__":
    unittest.main()
