using System.Net.WebSockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using YandexMusic.API.Models.Track;
using YandexMusic.API.Models.Ynison;
using YandexMusic.API.Models.Ynison.Messages;

namespace YandexMusic.API.Common.Ynison;

/// <summary>Плеер для управления воспроизведением через протокол Ynison (WebSocket).</summary>
public class YnisonPlayer : IDisposable
{
    private readonly JsonSerializerOptions _jsonOptions;
    private readonly AuthStorage _storage;
    private YnisonWebSocket? _redirector;
    private YnisonWebSocket? _state;

    /// <summary>API Яндекс Музыки.</summary>
    public YandexMusicApi API { get; }

    /// <summary>Текущее состояние плеера.</summary>
    public YYnisonState? State { get; private set; }

    /// <summary>Текущий проигрываемый трек.</summary>
    public YTrack? Current => GetCurrentAsync().GetAwaiter().GetResult();

    /// <summary>Событие получения нового состояния.</summary>
    public event EventHandler<ReceiveEventArgs>? OnReceive;

    /// <summary>Событие закрытия соединения.</summary>
    public event EventHandler<CloseEventArgs>? OnClose;

    /// <summary>Аргументы события получения состояния.</summary>
    public class ReceiveEventArgs : EventArgs
    {
        /// <summary>Состояние плеера.</summary>
        public YYnisonState State { get; init; } = null!;
    }

    /// <summary>Аргументы события закрытия соединения.</summary>
    public class CloseEventArgs : EventArgs
    {
        /// <summary>Статус закрытия.</summary>
        public WebSocketCloseStatus? Status { get; init; }
        /// <summary>Описание причины закрытия.</summary>
        public string? Description { get; init; }
    }

    internal YnisonPlayer(YandexMusicApi api, AuthStorage authStorage)
    {
        API = api;
        _storage = authStorage;
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
            Converters = { new JsonStringEnumConverter(new UpperSnakeCaseNamingPolicy(), false) }
        };
        _redirector = new YnisonWebSocket();
        _state = new YnisonWebSocket();
    }

    private string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions);

    private T Deserialize<T>(YYnisonMessageType messageType, string data)
    {
        return JsonSerializer.Deserialize<T>(data, _jsonOptions)
            ?? throw new JsonException("Десериализация вернула null");
    }

    private T DeserializeMessage<T>(YYnisonMessageType messageType, string data)
    {
        using var doc = JsonDocument.Parse(data);
        if (doc.RootElement.TryGetProperty("error", out _))
        {
            var error = Deserialize<YYnisonErrorMessage>(YYnisonMessageType.Error, data);
            throw error ?? new Exception("Ошибка десериализации ответа с ошибкой.");
        }
        return Deserialize<T>(messageType, data);
    }

    private string DefaultState()
    {
        var version = new YYnisonVersion
        {
            DeviceId = _storage.DeviceId,
            Version = "0"
        };
        var fullState = new YYnisonUpdateFullStateMessage
        {
            UpdateFullState = new YYnisonFullState
            {
                Device = new YYnisonDevice
                {
                    Capabilities = new YYnisonDeviceCapabilities { CanBePlayer = true },
                    Info = new YYnisonDeviceInfo
                    {
                        DeviceId = _storage.DeviceId,
                        AppName = "Yandex Music API",
                        AppVersion = "0.0.1",
                        Type = "WEB",
                        Title = "YandexMusicAPI"
                    },
                    IsShadow = true
                },
                PlayerState = new YYnisonPlayerState
                {
                    PlayerQueue = new YYnisonPlayerQueue { Version = version },
                    Status = new YYnisonPlayerStateStatus { Version = version }
                }
            }
        };
        return SerializeJson(fullState);
    }

    private async Task<YTrack?> GetCurrentAsync()
    {
        if (State == null) return null;
        int index = State.PlayerState.PlayerQueue.CurrentPlayableIndex;
        if (index < 0 || index >= State.PlayerState.PlayerQueue.PlayableList.Count)
            return null;
        var item = State.PlayerState.PlayerQueue.PlayableList[index];
        var response = await API.Track.GetAsync(_storage, item.PlayableId);
        return response?.Result?.FirstOrDefault();
    }

    private async Task UpdateStateAsync()
    {
        if (State == null) return;
        var update = new YYnisonUpdatePlayerStateMessage
        {
            UpdatePlayerState = State.PlayerState
        };
        update.UpdatePlayerState.Status.Version = new YYnisonVersion { DeviceId = _storage.DeviceId };
        update.UpdatePlayerState.PlayerQueue.Version = new YYnisonVersion { DeviceId = _storage.DeviceId };
        if (_state != null)
            await _state.SendAsync(SerializeJson(update));
    }

    /// <summary>Подключается к Ynison и начинает получение состояния.</summary>
    public async Task ConnectAsync()
    {
        if (_redirector == null) throw new ObjectDisposedException(nameof(YnisonPlayer));
        await _redirector.ConnectAsync(_storage, "wss://ynison.music.yandex.ru/redirector.YnisonRedirectService/GetRedirectToYnison");
        _redirector.OnReceive += async (socket, data) =>
        {
            var redirectInfo = Deserialize<YYnisonRedirect>(YYnisonMessageType.Redirect, data.Data);
            if (_state == null) return;
            if (_state.IsConnected) return;
            await _state.ConnectAsync(_storage, $"wss://{redirectInfo.Host}/ynison_state.YnisonStateService/PutYnisonState", redirectInfo.RedirectTicket);
            _state.OnReceive += (s, d) =>
            {
                var message = DeserializeMessage<YYnisonState>(YYnisonMessageType.State, d.Data);
                State = message;
                OnReceive?.Invoke(this, new ReceiveEventArgs { State = State });
            };
            _state.OnClose += (s, args) =>
            {
                OnClose?.Invoke(this, new CloseEventArgs { Status = args.Status, Description = args.Description });
            };
            _ = _state.BeginReceiveAsync();
            await _state.SendAsync(DefaultState());
        };
        await _redirector.BeginReceiveAsync();
    }

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

    /// <summary>Освобождает ресурсы.</summary>
    public void Dispose()
    {
        _redirector?.Dispose();
        _state?.Dispose();
        _redirector = null;
        _state = null;
        GC.SuppressFinalize(this);
    }
}