using YandexMusic.API;
using YandexMusic.API.Common;
using YandexMusic.API.Common.Ynison;
using YandexMusic.API.Models.Account;
using YandexMusic.API.Models.Album;
using YandexMusic.API.Models.Artist;
using YandexMusic.API.Models.Common;
using YandexMusic.API.Models.Feed;
using YandexMusic.API.Models.Landing;
using YandexMusic.API.Models.Landing.Entity.Entities.Context;
using YandexMusic.API.Models.Library;
using YandexMusic.API.Models.Playlist;
using YandexMusic.API.Models.Queue;
using YandexMusic.API.Models.Radio;
using YandexMusic.API.Models.Search;
using YandexMusic.API.Models.Track;

namespace YandexMusic;

/// <summary>Асинхронный клиент для работы с API Яндекс Музыки.</summary>
public class YandexMusicClient : IDisposable
{
    private readonly YandexMusicApi _api;
    private readonly AuthStorage _storage;
    private YnisonPlayer? _player;

    /// <summary>Информация об аккаунте (после авторизации).</summary>
    public YAccount Account => _storage.User;

    /// <summary>Флаг авторизации.</summary>
    public bool IsAuthorized => _storage.IsAuthorized;

    /// <summary>Плеер Ynison (WebSocket) для синхронизации воспроизведения.</summary>
    public YnisonPlayer? Ynison => _player;

    /// <summary>Создаёт новый экземпляр клиента Яндекс Музыки.</summary>
    public YandexMusicClient()
    {
        _api = new YandexMusicApi();
        _storage = new AuthStorage();
        _player = _api.Ynison.GetPlayer(_storage);
    }

    #region Авторизация

    /// <summary>
    /// Авторизация по токену
    /// </summary>
    /// <param name="token">Токен авторизации</param>
    /// <returns></returns>
    public async Task<bool> Authorize(string token)
    {
        await _api.User.AuthorizeAsync(_storage, token);
        return _storage.IsAuthorized;
    }

    /// <summary>
    /// Создание сеанса и получение доступных методов авторизации
    /// </summary>
    /// <param name="userName">Имя пользователя</param>
    /// <returns></returns>
    public Task<YAuthTypes> CreateAuthSession(string userName)
    {
        return _api.User.CreateAuthSessionAsync(_storage, userName);
    }

    /// <summary>
    /// Получение ссылки на QR-код
    /// </summary>
    /// <returns></returns>
    public Task<string> GetAuthQRLink()
    {
        return _api.User.GetAuthQRLinkAsync(_storage);
    }

    /// <summary>
    /// Авторизация по QR-коду
    /// </summary>
    /// <returns></returns>
    public Task<YAuthQRStatus> AuthorizeByQR()
    {
        return _api.User.AuthorizeByQRAsync(_storage);
    }

    /// <summary>
    /// Получение <see cref="YAuthCaptcha"/>
    /// </summary>
    /// <returns></returns>
    public Task<YAuthCaptcha> GetCaptcha()
    {
        return _api.User.GetCaptchaAsync(_storage);
    }

    /// <summary>
    /// Авторизация по captcha
    /// </summary>
    /// <param name="captcha">Значение captcha</param>
    /// <returns></returns>
    public Task<YAuthBase> AuthorizeByCaptcha(string captcha)
    {
        return _api.User.AuthorizeByCaptchaAsync(_storage, captcha);
    }

    /// <summary>
    /// Получение письма авторизации на почту пользователя
    /// </summary>
    /// <returns></returns>
    public Task<YAuthLetter> GetAuthLetter()
    {
        return _api.User.GetAuthLetterAsync(_storage);
    }

    /// <summary>
    /// Авторизация после подтверждения входа через письмо
    /// </summary>
    /// <returns></returns>
    public Task<bool> AuthorizeByLetter()
    {
        return _api.User.AuthorizeByLetterAsync(_storage);
    }

    /// <summary>
    /// Авторизация с помощью пароля из приложения Яндекс
    /// </summary>
    /// <param name="password">Пароль</param>
    /// <returns></returns>
    public Task<YAuthBase> AuthorizeByAppPassword(string password)
    {
        return _api.User.AuthorizeByAppPasswordAsync(_storage, password);
    }

    /// <summary>
    /// Получение <see cref="YAccessToken"/> после авторизации с помощью QR, e-mail, пароля из приложения
    /// </summary>
    public Task<YAccessToken> GetAccessToken()
    {
        return _api.User.GetAccessTokenAsync(_storage);
    }

    /// <summary>
    /// Получение информации о пользователе через логин Яндекса
    /// </summary>
    public Task<YLoginInfo> GetLoginInfo()
    {
        return _api.User.GetLoginInfoAsync(_storage);
    }

    #endregion Авторизация

    #region Треки
    /// <summary>Получает трек по идентификатору.</summary>
    public async Task<YTrack?> GetTrackAsync(string id)
    {
        var response = await _api.Track.GetAsync(_storage, id);
        return response?.Result?.FirstOrDefault();
    }

    /// <summary>Получает список треков по идентификаторам.</summary>
    public async Task<List<YTrack>> GetTracksAsync(IEnumerable<string> ids)
    {
        var response = await _api.Track.GetAsync(_storage, ids);
        return response?.Result ?? new List<YTrack>();
    }
    #endregion

    #region Альбомы
    /// <summary>Получает альбом по идентификатору.</summary>
    public async Task<YAlbum?> GetAlbumAsync(string id)
    {
        var response = await _api.Album.GetAsync(_storage, id);
        return response?.Result;
    }

    /// <summary>Получает список альбомов по идентификаторам.</summary>
    public async Task<List<YAlbum>> GetAlbumsAsync(IEnumerable<string> ids)
    {
        var response = await _api.Album.GetAsync(_storage, ids);
        return response?.Result ?? new List<YAlbum>();
    }
    #endregion

    #region Главная страница
    /// <summary>Получает персональные блоки лендинга.</summary>
    public async Task<YLanding?> GetLandingAsync(params YLandingBlockType[] blocks)
    {
        var response = await _api.Landing.GetAsync(_storage, blocks);
        return response?.Result;
    }

    /// <summary>Получает ленту событий.</summary>
    public async Task<YFeed?> GetFeedAsync()
    {
        var response = await _api.Landing.GetFeedAsync(_storage);
        return response?.Result;
    }

    /// <summary>Получает детский лендинг.</summary>
    public async Task<YChildrenLanding?> GetChildrenLandingAsync()
    {
        var response = await _api.Landing.GetChildrenLandingAsync(_storage);
        return response?.Result;
    }
    #endregion

    #region Исполнители
    /// <summary>Получает информацию об исполнителе.</summary>
    public async Task<YArtistBriefInfo?> GetArtistAsync(string id)
    {
        var response = await _api.Artist.GetAsync(_storage, id);
        return response?.Result;
    }

    /// <summary>Получает список исполнителей.</summary>
    public async Task<List<YArtist>> GetArtistsAsync(IEnumerable<string> ids)
    {
        var response = await _api.Artist.GetAsync(_storage, ids);
        return response?.Result ?? new List<YArtist>();
    }
    #endregion

    #region Плейлисты
    /// <summary>Получает плейлист по пользователю и идентификатору.</summary>
    public async Task<YPlaylist?> GetPlaylistAsync(string user, string id)
    {
        var response = await _api.Playlist.GetAsync(_storage, user, id);
        return response?.Result;
    }

    /// <summary>Получает плейлист по UUID.</summary>
    public async Task<YPlaylist?> GetPlaylistAsync(string uuid)
    {
        var response = await _api.Playlist.GetAsync(_storage, uuid);
        return response?.Result;
    }

    /// <summary>Получает список плейлистов по списку пар (пользователь, идентификатор).</summary>
    public async Task<List<YPlaylist>> GetPlaylistsAsync(IEnumerable<(string user, string id)> ids)
    {
        var response = await _api.Playlist.GetAsync(_storage, ids);
        return response?.Result ?? new List<YPlaylist>();
    }

    /// <summary>Получает персональные плейлисты (с главной страницы).</summary>
    public async Task<List<YPlaylist>> GetPersonalPlaylistsAsync()
    {
        var playlists = await _api.Playlist.GetPersonalPlaylistsAsync(_storage);
        return playlists.Select(r => r.Result).Where(p => p != null).ToList()!;
    }

    /// <summary>Получает избранные плейлисты.</summary>
    public async Task<List<YPlaylist>> GetFavoritesAsync()
    {
        var response = await _api.Playlist.FavoritesAsync(_storage);
        return response?.Result ?? new List<YPlaylist>();
    }

    /// <summary>Получает плейлист «Дежавю».</summary>
    public async Task<YPlaylist?> GetDejaVuAsync()
    {
        var response = await _api.Playlist.DejaVuAsync(_storage);
        return response?.Result;
    }

    /// <summary>Получает плейлист «Тайник».</summary>
    public async Task<YPlaylist?> GetMissedAsync()
    {
        var response = await _api.Playlist.MissedAsync(_storage);
        return response?.Result;
    }

    /// <summary>Получает плейлист дня.</summary>
    public async Task<YPlaylist?> GetOfTheDayAsync()
    {
        var response = await _api.Playlist.OfTheDayAsync(_storage);
        return response?.Result;
    }

    /// <summary>Получает плейлист «Кинопоиск».</summary>
    public async Task<YPlaylist?> GetKinopoiskAsync()
    {
        var response = await _api.Playlist.KinopoiskAsync(_storage);
        return response?.Result;
    }

    /// <summary>Получает плейлист «Премьера».</summary>
    public async Task<YPlaylist?> GetPremiereAsync()
    {
        var response = await _api.Playlist.PremiereAsync(_storage);
        return response?.Result;
    }

    /// <summary>Создаёт новый плейлист с заданным именем.</summary>
    public async Task<YPlaylist?> CreatePlaylistAsync(string name)
    {
        var response = await _api.Playlist.CreateAsync(_storage, name);
        return response?.Result;
    }
    #endregion

    #region Поиск
    /// <summary>Выполняет поиск.</summary>
    public async Task<YSearch?> SearchAsync(string searchText, YSearchType searchType, int page = 0, int pageSize = 20)
    {
        var response = await _api.Search.SearchAsync(_storage, searchText, searchType, page, pageSize);
        return response?.Result;
    }

    /// <summary>Получает подсказки для поискового запроса.</summary>
    public async Task<YSearchSuggest?> GetSearchSuggestionsAsync(string searchText)
    {
        var response = await _api.Search.SuggestAsync(_storage, searchText);
        return response?.Result;
    }
    #endregion

    #region Библиотека
    /// <summary>Получает лайкнутые треки.</summary>
    public async Task<List<YTrack>> GetLikedTracksAsync()
    {
        var likes = await _api.Library.GetLikedTracksAsync(_storage);
        var ids = likes.Result?.Library.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty<string>();
        if (ids.Length == 0) return new List<YTrack>();
        var response = await _api.Track.GetAsync(_storage, ids);
        return response?.Result ?? new List<YTrack>();
    }

    /// <summary>Получает дизлайкнутые треки.</summary>
    public async Task<List<YTrack>> GetDislikedTracksAsync()
    {
        var dislikes = await _api.Library.GetDislikedTracksAsync(_storage);
        var ids = dislikes.Result?.Library.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty<string>();
        if (ids.Length == 0) return new List<YTrack>();
        var response = await _api.Track.GetAsync(_storage, ids);
        return response?.Result ?? new List<YTrack>();
    }

    /// <summary>Получает лайкнутые альбомы.</summary>
    public async Task<List<YAlbum>> GetLikedAlbumsAsync()
    {
        var albums = await _api.Library.GetLikedAlbumsAsync(_storage);
        var ids = albums.Result?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
        if (ids.Length == 0) return new List<YAlbum>();
        var response = await _api.Album.GetAsync(_storage, ids);
        return response?.Result ?? new List<YAlbum>();
    }

    /// <summary>Получает лайкнутых исполнителей.</summary>
    public async Task<List<YArtist>> GetLikedArtistsAsync()
    {
        var artists = await _api.Library.GetLikedArtistsAsync(_storage);
        var ids = artists.Result?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
        if (ids.Length == 0) return new List<YArtist>();
        var response = await _api.Artist.GetAsync(_storage, ids);
        return response?.Result ?? new List<YArtist>();
    }

    /// <summary>Получает дизлайкнутых исполнителей.</summary>
    public async Task<List<YArtist>> GetDislikedArtistsAsync()
    {
        var artists = await _api.Library.GetDislikedArtistsAsync(_storage);
        var ids = artists.Result?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
        if (ids.Length == 0) return new List<YArtist>();
        var response = await _api.Artist.GetAsync(_storage, ids);
        return response?.Result ?? new List<YArtist>();
    }

    /// <summary>Получает лайкнутые плейлисты.</summary>
    public async Task<List<YPlaylist>> GetLikedPlaylistsAsync()
    {
        var playlists = await _api.Library.GetLikedPlaylistsAsync(_storage);
        var ids = playlists.Result?
            .Select(p => (p.Playlist.Uid, p.Playlist.Kind))
            .ToArray() ?? Array.Empty<(string, string)>();
        if (ids.Length == 0) return new List<YPlaylist>();
        var response = await _api.Playlist.GetAsync(_storage, ids);
        return response?.Result ?? new List<YPlaylist>();
    }

    /// <summary>Получает список недавно прослушанных треков.</summary>
    /// <param name="contextTypes">Типы контекстов (альбом, исполнитель, плейлист).</param>
    /// <param name="trackCount">Количество треков на контекст.</param>
    /// <param name="contextCount">Количество контекстов.</param>
    public async Task<List<YRecentlyListened>> GetRecentlyListenedAsync(
        IEnumerable<YPlayContextType> contextTypes,
        int trackCount = 50,
        int contextCount = 10)
    {
        var response = await _api.Library.GetRecentlyListenedAsync(_storage, contextTypes, trackCount, contextCount);
        return response?.Result?.Contexts ?? new List<YRecentlyListened>();
    }
    #endregion

    #region Радио
    /// <summary>Получает список рекомендованных радиостанций.</summary>
    public async Task<List<YStation>> GetRadioDashboardAsync()
    {
        var response = await _api.Radio.GetStationsDashboardAsync(_storage);
        return response?.Result?.Stations ?? new List<YStation>();
    }

    /// <summary>Получает список всех радиостанций.</summary>
    public async Task<List<YStation>> GetRadioStationsAsync()
    {
        var response = await _api.Radio.GetStationsAsync(_storage);
        return response?.Result ?? new List<YStation>();
    }

    /// <summary>Получает информацию о радиостанции по идентификатору.</summary>
    public async Task<YStation?> GetRadioStationAsync(YStationId id)
    {
        var response = await _api.Radio.GetStationAsync(_storage, id);
        return response?.Result?.FirstOrDefault();
    }
    #endregion

    #region Очереди
    /// <summary>Получает все очереди с разных устройств.</summary>
    public async Task<YQueueItemsContainer?> GetQueuesAsync(string? device = null)
    {
        var response = await _api.Queue.ListAsync(_storage, device);
        return response?.Result;
    }

    /// <summary>Получает очередь по идентификатору.</summary>
    public async Task<YQueue?> GetQueueAsync(string queueId)
    {
        var response = await _api.Queue.GetAsync(_storage, queueId);
        return response?.Result;
    }

    /// <summary>Создаёт новую очередь.</summary>
    public async Task<YNewQueue?> CreateQueueAsync(YQueue queue, string? device = null)
    {
        var response = await _api.Queue.CreateAsync(_storage, queue, device);
        return response?.Result;
    }

    /// <summary>Обновляет позицию в очереди.</summary>
    public async Task<YUpdatedQueue?> UpdateQueuePositionAsync(string queueId, int currentIndex, bool isInteractive, string? device = null)
    {
        var response = await _api.Queue.UpdatePositionAsync(_storage, queueId, currentIndex, isInteractive, device);
        return response?.Result;
    }
    #endregion

    #region Загрузка треков (UGC)
    /// <summary>Загружает трек из файла в плейлист.</summary>
    public async Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, string filePath)
    {
        var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName);
        var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, filePath);
        return response?.Result;
    }

    /// <summary>Загружает трек из потока в плейлист.</summary>
    public async Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, Stream stream)
    {
        var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName);
        var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, stream);
        return response?.Result;
    }

    /// <summary>Загружает трек из массива байтов в плейлист.</summary>
    public async Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, byte[] file)
    {
        var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName);
        var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, file);
        return response?.Result;
    }
    #endregion

    #region Лейблы
    /// <summary>Получает альбомы лейбла с пагинацией.</summary>
    public async Task<List<YAlbum>> GetAlbumsByLabelAsync(YLabel label, int page = 0)
    {
        var response = await _api.Label.GetAlbumsByLabelAsync(_storage, label, page);
        return response?.Result?.Albums ?? new List<YAlbum>();
    }

    /// <summary>Получает артистов лейбла с пагинацией.</summary>
    public async Task<List<YArtist>> GetArtistsByLabelAsync(YLabel label, int page = 0)
    {
        var response = await _api.Label.GetArtistsByLabelAsync(_storage, label, page);
        return response?.Result?.Artists ?? new List<YArtist>();
    }
    #endregion

    #region Ynison (WebSocket плеер)
    /// <summary>Подключается к Ynison и запускает синхронизацию состояния плеера.</summary>
    public async Task ConnectYnisonAsync()
    {
        if (_player == null)
            _player = _api.Ynison.GetPlayer(_storage);
        await _player.ConnectAsync();
    }

    /// <summary>Отключается от Ynison.</summary>
    public async Task DisconnectYnisonAsync()
    {
        if (_player != null)
            await _player.DisconnectAsync();
    }
    #endregion

    #region IDisposable
    private bool _disposed;

    /// <summary>Освобождает ресурсы.</summary>
    public void Dispose()
    {
        if (_disposed) return;
        _player?.Dispose();
        _disposed = true;
        GC.SuppressFinalize(this);
    }
    #endregion
}