#  VKMusix — VK Music API Client Library for Python
#  Copyright (C) 2024—present to4no4sv <https://github.com/to4no4sv/VKMusix>
#
#  This file is part of VKMusix.
#
#  VKMusix is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as published
#  by the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  VKMusix is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#  GNU Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with VKMusix. If not, see <http://www.gnu.org/licenses/>.

import os

import pytz
from datetime import datetime

from typing import Union, Optional, List, Tuple

from ..models import Track, Playlist
from ..config import moscowTz
from ..utils.another import checkFile


class Update:
    def upload(self, filename: str, title: str = None, artist: str = None, lyrics: str = None, genreId: int = None, removeFromSearchResults: bool = None, groupId: int = None) -> Union[Track, dict]:
        """
        Загружает новый аудиотрек во ВКонтакте.

        Пример загрузки файла с названием «prombl — zapreti.mp3»:\n
        result = client.upload(filename="prombl — zapreti", title="zapreti", artist="prombl", lyrics="yourLyrics", removeFromSearchResults=True, groupId="yourGroupId")\n
        print(result)

        :param filename: имя MP3-файла, содержащего аудиотрек, который необходимо загрузить (без расширения). (str)
        :param title: название аудиотрека. (str, необязательно)
        :param artist: артист(ы) аудиотрека. (str, необязательно)
        :param lyrics: текст аудиотрека. (str, необязательно, временно не работает)
        :param genreId: жанр аудиотрека (в виде идентификатора). (int, необязательно, временно не работает)
        :param removeFromSearchResults: флаг, указывающий, будет ли аудиотрек скрыт из поисковой выдачи. (bool, необязательно, временно не работает)
        :param groupId: идентификатор группы, в музыку которой необходимо загрузить аудиотрек (int, необязательно, временно не работает)
        :return: загруженный аудиотрек в виде объекта модели `Track` или словаря.
        """

        if filename.endswith(".mp3"):
            filename = filename[:-4]

        filename = checkFile(filename + ".mp3")
        if not filename:
            self._raiseError("MP3FileNotFound")

        fileSizeInBytes = os.path.getsize(filename)
        fileSizeInMB = fileSizeInBytes / (1024 * 1024)  # Преобразование размера файла в мегабайты

        if fileSizeInMB > 200:  # Проверка, меньше ли размер файла 200 MB
            self._raiseError("MP3FileTooBig")

        uploadUrl = self._VKReq("getUploadServer").get("upload_url")

        with open(filename, "rb") as file:
            fileContent = file.read()

        uploadingFileResponse = self._client.sendReq(uploadUrl, files={"file": (filename, fileContent, "audio/mpeg")}, method="POST")

        server = uploadingFileResponse.get("server")
        audio = uploadingFileResponse.get("audio")
        hash = uploadingFileResponse.get("hash")

        response = self._VKReq("save", {"server": server, "audio": audio, "hash": hash, "title": title, "artist": artist})

        return self._finalizeResponse(response, Track) if not response.get("error") else response


    def add(self, ownerIds: Union[int, List[int]], trackIds: Union[int, List[int]], playlistId: Optional[int] = None, groupId: Optional[int] = None) -> Tuple[Union[bool, dict]]:
        """
        Добавляет аудиотрек в музыку или плейлист пользователя или группы.

        Пример использования:\n
        result = client.add(ownerIds=474499244, trackIds=456638035, playlistId="yourPlaylistId", groupId="yourGroupId")\n
        print(result)

        :param ownerIds: идентификатор(ы) владельца аудиотрека(ов) (пользователь или группа). (int или list)
        :param trackIds: идентификатор(ы) аудиотрека(ов), который(е) необходимо добавить. (int или list)
        :param playlistId: идентификатор плейлиста, в который необходимо добавить аудиотрек. (int, необязательно)
        :param groupId: идентификатор группы, в музыку или плейлист которой необходимо добавить аудиотрек. (int, необязательно)
        :return: `True`, если аудиотрек успешно добавлен, `False` в противном случае.
        """

        if type(ownerIds) != type(trackIds):
            return self._raiseError("ownerIdsAndTrackIdsTypeDifferent")

        if isinstance(ownerIds, list) and isinstance(trackIds, list) and len(ownerIds) != len(trackIds):
            return self._raiseError("ownerIdsAndTrackIdsLenDifferent")

        if not (isinstance(ownerIds, list) and isinstance(trackIds, list)):
            ownerIds = [ownerIds]
            trackIds = [trackIds]

        if not groupId:
            groupId = self.getSelf().get("id")

        if playlistId:
            method = "addToPlaylist"

        else:
            method = "add"

        results = []
        for ownerId, trackId in zip(ownerIds, trackIds):
            response = self._VKReq(method, {**({"owner_id": ownerId, "audio_id": trackId, "group_id": groupId} if not playlistId else {"owner_id": groupId, "audio_ids": f"{ownerId}_{trackId}"}), "playlist_id": playlistId})
            results.append(bool(response) if not (isinstance(response, dict) and response.get("error")) else self._raiseError("playlistNotFound"))

        return tuple(results)


    def remove(self, ownerIds: Union[int, List[int]], trackIds: Union[int, List[int]], playlistId: Optional[int] = None, groupId: Optional[int] = None, reValidateIds: bool = True) -> Tuple[Union[bool, dict]]:
        """
        Удаляет аудиотрек из музыки или плейлиста пользователя или группы.

        Пример использования:\n
        result = client.remove(ownerIds=474499244, trackIds=456638035, playlistId="yourPlaylistId", groupId="yourGroupId", reValidateIds=False)\n
        print(result)

        :param ownerIds: идентификатор(ы) владельца аудиотрека(ов) (пользователь или группа). (int или list)
        :param trackIds: идентификатор(ы) аудиотрека(ов), который(е) необходимо удалить. (int или list)
        :param playlistId: идентификатор плейлиста, из которого необходимо удалить аудиотрек(и). (int, необязательно, метод временно не работает для плейлистов, привязанных к чату)
        :param groupId: идентификатор группы, из музыки или плейлиста которой необходимо удалить аудиотрек(и). (int, необязательно)
        :param reValidateIds: флаг, указывающий, необходимо ли перепроверить идентификатор(ы) аудитрека(ов) по находящимся в плейлисте. (bool, по умолчанию `True`)
        :return: `True`, если аудиотрек(и) успешно удалён, `False` в противном случае.
        """

        if type(ownerIds) != type(trackIds):
            return self._raiseError("ownerIdsAndTrackIdsTypeDifferent")

        if isinstance(ownerIds, list) and isinstance(trackIds, list) and len(ownerIds) != len(trackIds):
            return self._raiseError("ownerIdsAndTrackIdsLenDifferent")

        if not (isinstance(ownerIds, list) and isinstance(trackIds, list)):
            ownerIds = [ownerIds]
            trackIds = [trackIds]

        if not groupId:
            groupId = self.getSelf().get("id")

        if reValidateIds:
            if playlistId:
                playlist = self.getPlaylist(playlistId, groupId, includeTracks=True)
                if isinstance(playlist, dict) and playlist.get("error"):
                    return playlist

                if isinstance(playlist, dict):
                    existTracks = playlist.get("tracks")

                elif isinstance(playlist, Playlist):
                    existTracks = playlist.tracks if hasattr(playlist, "tracks") else None

            else:
                existTracks = self.getTracks(groupId)

            if not existTracks and playlistId:
                return (False,)

            for index, (ownerId, trackId) in enumerate(zip(ownerIds, trackIds)):
                if not playlistId:
                    continue

                track = self.get(ownerId, trackId)

                if isinstance(track, dict):
                    if track.get("error"):
                        continue

                    trackTitle = track.get("title")
                    trackArtists = track.get("artists")
                    if not trackArtists:
                        trackArtists = track.get("artist")

                elif isinstance(track, Track):
                    trackTitle = track.title
                    trackArtists = track.artists
                    if not trackArtists:
                        trackArtists = track.artist

                for existTrack in existTracks:
                    if isinstance(existTrack, dict):
                        existTrackTitle = existTrack.get("title")
                        existTrackArtists = existTrack.get("artists")
                        if not existTrackArtists:
                            existTrackArtists = existTrack.get("artist")

                        existTrackOwnerId = existTrack.get("ownerId")
                        existTrackId = existTrack.get("trackId")

                    elif isinstance(existTrack, Track):
                        existTrackTitle = existTrack.title
                        existTrackArtists = existTrack.artists
                        if not existTrackArtists:
                            existTrackArtists = existTrack.artist

                        existTrackOwnerId = existTrack.ownerId
                        existTrackId = existTrack.trackId

                    if existTrackTitle == trackTitle and existTrackArtists == trackArtists:
                        ownerIds[index] = existTrackOwnerId
                        trackIds[index] = existTrackId
                        break

        if playlistId:
            def removeTrackFromPlaylist(id: str) -> bool:
                response = self._VKReq("removeFromPlaylist", {"audio_ids": id, "owner_id": groupId if groupId > 0 else groupId - groupId * 2, "playlist_id": playlistId})
                return bool(response) if not (isinstance(response, dict) and response.get("error")) else response

            tasks = [removeTrackFromPlaylist(f"{ownerId}_{trackId}") for ownerId, trackId in zip(ownerIds, trackIds)]

        else:
            def removeTrack(ownerId: int, trackId: int) -> bool:
                response = self._VKReq("delete", {"owner_id": ownerId, "audio_id": trackId, "group_id": groupId})
                return bool(response) if not (isinstance(response, dict) and response.get("error")) else response

            tasks = [removeTrack(ownerId, trackId) for ownerId, trackId in zip(ownerIds, trackIds)]

        return tuple(tasks)


    def edit(self, ownerId: int, trackId: int, title: Union[str, int] = None, artist: Union[str, int] = None, lyrics: Union[str, int] = None, genreId: int = None, removeFromSearchResults: bool = None) -> Union[bool, dict]:
        """
        Изменяет информацию об аудиотреке.

        Пример использования:\n
        result = client.edit(ownerId="yourOwnerId", trackId="yourTrackId", title="zapreti", artist="prombl", "lyrics"="", "genreId"=3, removeFromSearchResults=True)\n
        print(result)

        :param ownerId: идентификатор владельца аудиотрека (пользователь или группа). (int)
        :param trackId: идентификатор аудиотрека, информацию которого необходимо изменить. (int)
        :param title: новое название аудиотрека. (str, необязательно)
        :param artist: новый(е) артист(ы) аудиотрека. (str, необязательно)
        :param lyrics: новый текст аудиотрека. (str, необязательно)
        :param genreId: новый жанр аудиотрека (в виде идентификатора). (int, необязательно)
        :param removeFromSearchResults: флаг, указывающий, будет ли аудиотрек скрыт из поисковой выдачи. (bool, необязательно)
        :return: `True`, если информация аудиотрека успешно обновлена, `False` в противном случае.
        """

        if not any((title, artist, lyrics is not None, genreId is not None, removeFromSearchResults is not None)):
            return False

        response = self._VKReq("edit", {"owner_id": ownerId, "audio_id": trackId,
            **({"title": title} if title else {}),
            **({"artist": artist} if artist else {}),
            **({"lyrics": lyrics} if lyrics is not None else {}),
            **({"genre_id": genreId} if genreId is not None else {}),
            **({"no_search": removeFromSearchResults} if removeFromSearchResults is not None else {})
        })

        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def restore(self, ownerId: int, trackId: int) -> Union[bool, dict]:
        """
        Восстанавливает удалённый аудиотрек.

        Пример использования:\n
        result = client.restore(ownerId="yourOwnerId", trackId="yourTrackId")\n
        print(result)

        :param ownerId: идентификатор владельца аудиотрека (пользователь или группа). (int)
        :param trackId: идентификатор аудиотрека, который необходимо восстановить. (int)
        :return: `True`, если аудиотрек успешно восстановлен, `False` в противном случае.
        """

        response = self._VKReq("restore", {"owner_id": ownerId, "audio_id": trackId})

        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def createPlaylist(self, title: Union[str, int], description: Union[str, int] = None, photo: str = None, groupId: int = None, chatId: int = None) -> Union[int, dict, None]:
        """
        Создаёт плейлист в музыке пользователя или группы.

        Пример использования:\n
        result = client.createPlaylist(title="prombl — npc", description="Release Date: December 24, 2021", "photo"="yourPhotoUrl", "groupId"="yourGroupId", chatId="yourChatId")\n
        print(result)

        :param title: название плейлиста. (str)
        :param description: описание плейлиста. (str, необязательно)
        :param photo: фото плейлиста. (str, необязательно, временно не работает)
        :param groupId: идентификатор группы, в которой необходимо создать плейлист. (int, необязательно)
        :param chatId: идентификатор чата, к которому привязать плейлист. (int, формат: `2000000000 + идентификатор чата`)
        :return: идентификатор плейлиста или словарь с ключами `ownerId` и `playlistId` (если создаётся плейлист, привязанный к чату), если плейлист успешно создан, `None` в противном случае.
        """

        if not groupId:
            groupId = self.getSelf().get("id")

        if chatId:
            method = "createChatPlaylist"

        else:
            method = "createPlaylist"

        playlist = self._VKReq(method, {"title": title, "description": description, "owner_id": groupId})
        playlistId = playlist.get("id")

        if playlistId:
            if chatId:
                playlistOwnerId = playlist.get("owner_id")
                return {"ownerId": playlistOwnerId, "playlistId": playlistId}

            return playlistId

            """if photo:
                self._editPlaylistPhoto(playlistId, photo, groupId)"""

        return playlist


    def addPlaylist(self, playlistId: int, ownerId: int = None, groupId: int = None) -> Union[int, None, dict]:
        """
        Добавляет плейлист в музыку пользователя или группы.

        Пример использования:\n
        result = client.addPlaylist(playlistId=1, ownerId=-215973356, groupId="yourGroupId")\n
        print(result)

        :param ownerId: идентификатор владельца плейлиста (пользователь или группа). (int, по умолчанию текущий пользователь)
        :param playlistId: идентификатор плейлиста, который необходимо добавить. (int)
        :param groupId: идентификатор группы, в которую необходимо добавить плейлист. (int, необязательно)
        :return: идентификатор плейлиста, если плейлист успешно добавлен, `None` в противном случае.
        """

        if not ownerId:
            if not groupId:
                return None

            ownerId = self.getSelf().get("id")

        response = self._VKReq("followPlaylist", {"owner_id": ownerId, "playlist_id": playlistId, **({"group_id": groupId} if groupId else {})})
        return response.get("id") if not (isinstance(response, dict) and response.get("error")) else response


    def removePlaylist(self, playlistId: int, groupId: int = None) -> Union[bool, dict]:
        """
        Удаляет плейлист из музыки пользователя или группы.

        Пример использования:\n
        result = client.removePlaylist(playlistId="yourPlaylistId", groupId="yourGroupId")\n
        print(result)

        :param playlistId: идентификатор плейлиста, который необходимо удалить. (int)
        :param groupId: идентификатор группы, из которой необходимо удалить плейлист. (int, необязательно)
        :return: `True`, если плейлист успешно удалён, `False` в противном случае.
        """

        if not groupId:
            groupId = self.getSelf().get("id")

        response = self._VKReq("deletePlaylist", {"owner_id": groupId, "playlist_id": playlistId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else (response if response.get("error").get("code") != 17 else self._raiseError("playlistNotFound"))


    def _editPlaylistPhoto(self, playlistId: int, photo: str = None, groupId: int = None) -> Union[bool, dict]:
        if photo is None:
            return False

        if not groupId:
            groupId = self.getSelf().get("id")

        params = {"playlist_id": playlistId, "owner_id": groupId}
        if photo:
            method = "setPlaylistCoverPhoto"
            params = {**params, **{"photo": photo}}

        else:
            method = "deletePlaylistCoverPhoto"

        response = self._VKReq(method, params)
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def editPlaylist(self, playlistId: int, title: Union[str, int] = None, description: Union[str, int] = None, photo: str = None, groupId: int = None) -> Union[bool, dict]:
        """
        Изменяет информацию плейлиста, принадлежащего пользователю или группе.

        Пример использования:\n
        result = client.editPlaylist(playlistId="yourPlaylistId", title="prombl — npc", description="Release Date: December 24, 2021", photo="yourPhotoUrl", groupId="yourGroupId")\n
        print(result)

        :param playlistId: идентификатор плейлиста, информацию которого необходимо изменить.
        :param title: новое название плейлиста. (Необязательно)
        :param description: новое описание плейлиста. (Необязательно)
        :param photo: новое фото плейлиста. (Необязательно)
        :param groupId: идентификатор группы, в которой находится плейлист. (Необязательно)
        :return: `True`, если информация плейлиста успешно обновлена, `False` в противном случае.
        """

        if not any((title, description is not None, photo is not None)):
            return False

        if not groupId:
            groupId = self.getSelf().get("id")

        params = {
            "playlist_id": playlistId,
            "owner_id": groupId,
            **({"title": title} if title else {}),
            **({"description": description} if description is not None else {})
        }

        if not any(("title" in params, "description" in params, photo is not None)):
            return False

        else:
            if any(("title" in params, "description" in params)):
                response = self._VKReq("editPlaylist", params)
                editInfoStatus = bool(response) if not (isinstance(response, dict) and response.get("error")) else response

            else:
                editInfoStatus = None

        if any((editInfoStatus is True, editInfoStatus is None)) and photo is not None:
            editPhotoStatus = self._editPlaylistPhoto(playlistId, photo, groupId)
            if editInfoStatus is None:
                editInfoStatus = editPhotoStatus

        return editInfoStatus


    def copyPlaylist(self, playlistId: int, ownerId: int = None, groupId: int = None, skipTitle: bool = False, skipDescription: bool = False, skipPhoto: bool = False) -> Union[int, None, dict]:
        """
        Копирует плейлист, принадлежий пользователю или группе в музыку пользователя или группы.

        Пример использования:\n
        result = client.copyPlaylist(playlistId=1, ownerId=-215973356, groupId: "yourGroupId", skipTitle=True, skipDescription=True, skipPhoto=True)\n
        print(result)

        :param playlistId: идентификатор плейлиста, который необходимо скопировать. (int)
        :param ownerId: идентификатор владельца плейлиста (пользователь или группа). (int, по умолчанию текущий пользователь)
        :param groupId: идентификатор группы, в которую необходимо скопировать плейлист. (int, необязательно)
        :param skipTitle: флаг, указывающий, необходимо ли не копировать название плейлиста (если `True`, будут использованы текущие дата и время). (bool, по умолчанию `False`)
        :param skipDescription: флаг, указывающий, необходимо ли не копировать описание плейлиста. (bool, по умолчанию `False`)
        :param skipPhoto: флаг, указывающий, необходимо ли не копировать фото плейлиста. (bool, по умолчанию `False`)
        :return: идентификатор плейлиста, если плейлист успешно скопирован, `None` в противном случае.
        """

        if not ownerId:
            ownerId = self.getSelf().get("id")

        playlist = self.getPlaylist(playlistId, ownerId, True)

        if isinstance(playlist, dict):
            if playlist.get("error"):
                return playlist

            title = playlist.get("title")
            description = playlist.get("description")
            photo = playlist.get("photo")

        else:
            title = playlist.title
            description = playlist.description if hasattr(playlist, "description") else None
            photo = playlist.photo if hasattr(playlist, "photo") else None

        if skipTitle:
            title = datetime.utcnow().replace(tzinfo=pytz.utc).astimezone(moscowTz).strftime("%d.%m.%Y / %H:%M:%S")

        if skipDescription:
            description = None

        if skipPhoto:
            photo = None

        elif photo:
            _, photo = photo.popitem()

        newPlaylistId = self.createPlaylist(title, description, photo, groupId)
        if isinstance(newPlaylistId, dict) and newPlaylistId.get("error"):
            return newPlaylistId

        tracks = (playlist.tracks if hasattr(playlist, "tracks") else None) if isinstance(playlist, Playlist) else playlist.get("tracks") if isinstance(playlist, dict) else None
        if not tracks:
            return newPlaylistId

        ownerIds = []
        trackIds = []
        for track in tracks[::-1]:
            ownerId, trackId = (track.ownerId, track.trackId) if isinstance(track, Track) else (track.get("ownerId"), track.get("trackId"))
            ownerIds.append(ownerId)
            trackIds.append(trackId)
        self.add(ownerIds, trackIds, newPlaylistId, groupId)

        return newPlaylistId


    def reorder(self, trackId: int, beforeTrackId: int = None, afterTrackId: int = None) -> Union[bool, dict]:
        """
        Изменяет порядок аудиотрека в музыке пользователя. Должен быть заполнен один из типов аргументов на выбор: `beforeTrackId` или `afterTrackId`.

        Пример использования для перемещения на место перед определённым треком:\n
        result = client.reorder(trackId="yourTrackId", beforeTrackId="yourBeforeTrackId")\n
        print(result)

        Пример использования для перемещения на место после определённого трека:\n
        result = client.reorder(trackId="yourTrackId", afterTrackId="yourAfterTrackId")\n
        print(result)

        :param trackId: идентификатор аудиотрека, порядок которого необходимо изменить. (int)
        :param beforeTrackId: идентификатор аудиотрека перед которым необходимо поместить аудиотрек. (int, необязательно)
        :param afterTrackId: идентификатор аудиотрека после которого необходимо поместить аудиотрек. (int, необязательно)
        :return: `True`, если порядок трека успешно изменён, `False` в противном случае.
        """

        if not any((beforeTrackId, afterTrackId)):
            return self._raiseError("trackReorderNeedsBeforeOrAfterArgument")

        if all((beforeTrackId, afterTrackId)):
            return self._raiseError("trackReorderNeedsOnlyBeforOrAfterNotBoth")

        response = self._VKReq("reorder", {"audio_id": trackId, **({"before": beforeTrackId} if beforeTrackId else {"after": afterTrackId})})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def followArtist(self, artistId: int = None) -> Union[bool, dict]:
        """
        Подписывается на обновления музыки артиста.

        Пример использования:\n
        result = client.followArtist(artistId=5696274288194638935)\n
        print(result)

        :param artistId: идентификатор артиста, на обновления которого необходимо подписаться. (int)
        :return: `True`, если Вы успешно подписались на обновления музыки артиста, `False` в противном случае.
        """

        response = self._VKReq("followArtist", {"artist_id": artistId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def unfollowArtist(self, artistId: int = None) -> Union[bool, dict]:
        """
        Отписывается от обновлений музыки артиста.

        Пример использования:\n
        result = client.unfollowArtist(artistId=5696274288194638935)\n
        print(result)

        :param artistId: идентификатор артиста, от обновлений которого необходимо отписаться. (int)
        :return: `True`, если Вы успешно отписались от обновлений музыки артиста, `False` в противном случае.
        """

        response = self._VKReq("unfollowArtist", {"artist_id": artistId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def followCurator(self, curatorId: int = None) -> Union[bool, dict]:
        """
        Подписывается на обновления музыки куратора.

        Пример использования:\n
        result = client.followCurator(curatorId=28905875)\n
        print(result)

        :param curatorId: идентификатор куратора, на обновления которого необходимо подписаться. (int)
        :return: `True`, если Вы успешно подписались на обновления музыки куратора, `False` в противном случае.
        """

        response = self._VKReq("followCurator", {"curator_id": curatorId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def unfollowCurator(self, curatorId: int = None) -> Union[bool, dict]:
        """
        Отписывается от обновлений музыки куратора.

        Пример использования:\n
        result = client.unfollowCurator(curatorId=28905875)\n
        print(result)

        :param curatorId: идентификатор куратора, от обновлений которого необходимо отписаться. (int)
        :return: `True`, если Вы успешно отписались от обновлений музыки куратора, `False` в противном случае.
        """

        response = self._VKReq("unfollowCurator", {"curator_id": curatorId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def followOwner(self, ownerId: int = None) -> Union[bool, dict]:
        """
        Подписывается на обновления музыки пользователя или группы.

        Пример использования:\n
        result = client.followOwner(ownerId=-215973356)\n
        print(result)

        :param ownerId: идентификатор пользователя или группы, на обновления которого(ой) необходимо подписаться. (int)
        :return: `True`, если Вы успешно подписались на обновления музыки пользователя или группы, `False` в противном случае.
        """

        response = self._VKReq("followOwner", {"owner_id": ownerId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def unfollowOwner(self, ownerId: int = None) -> Union[bool, dict]:
        """
        Отписывается от обновлений музыки пользователя или группы.

        Пример использования:\n
        result = client.unfollowOwner(ownerId=-215973356)\n
        print(result)

        :param ownerId: идентификатор пользователя или группы, от обновлений которого(ой) необходимо отписаться. (int)
        :return: `True`, если Вы успешно отписались на обновлений музыки пользователя или группы, `False` в противном случае.
        """

        response = self._VKReq("followOwner", {"owner_id": ownerId})
        return bool(response) if not (isinstance(response, dict) and response.get("error")) else response


    def setBroadcast(self, ownerId: int = None, trackId: int = None, groupIds: Union[List[str], str] = None) -> Union[bool, dict]:
        """
        Устанавливает (удаляет) аудиотрек в (из) статус(а) пользователя или группы.

        Пример использования для установки аудиотрека в статус пользователя:\n
        result = client.setBroadcast(ownerId=474499156, trackId=456637846)\n
        print(result)

        Пример использования для установки аудиотрека в статус группы:\n
        result = client.setBroadcast(ownerId=474499156, trackId=456637846, groupdIds="yourGroupId")\n
        print(result)

        Пример использования для удаления аудиотрека из статуса пользователя:\n
        result = client.setBroadcast()\n
        print(result)

        Пример использования для удаления аудиотрека из статуса группы:\n
        result = client.setBroadcast(groupdIds="yourGroupId")\n
        print(result)

        :param ownerId: идентификатор владельца аудиотрека (пользователь или группа). (int, необязательно)
        :param trackId: идентификатор аудиотрека, который необходимо установить в статус. (int, необязательно)
        :param groupIds: идентификатор(ы) групп(ы), в (из) статус(а) которой необходимо установить (удалить) аудиотрек. (int, по умолчанию текущий пользователь)
        :return: `True`, если аудиотрек успешно установлен (удалён) в (из) статус(а), `False` в противном случае.
        """

        response = self._VKReq("setBroadcast", {**({"audio": f"{ownerId}_{trackId}"} if ownerId and trackId else {}), **({"target_ids": groupIds} if groupIds else {})})
        return True if not (isinstance(response, dict) and response.get("error")) else response