using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

namespace YandexMusic.API.Common.Ynison;

/// <summary>WebSocket-клиент для взаимодействия с протоколом Ynison.</summary>
public class YnisonWebSocket : IDisposable
{
    private readonly ClientWebSocket _socketClient = new();
    private CancellationTokenSource? _cancellationTokenSource;
    private CancellationToken _cancellationToken;
    private readonly StringBuilder _data = new();
    private const int BufferSize = 4096;

    /// <summary>Флаг, указывает, открыто ли соединение.</summary>
    public bool IsConnected => _socketClient.State == WebSocketState.Open;

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

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

    /// <summary>Аргументы события получения данных.</summary>
    public class ReceiveEventArgs : EventArgs
    {
        /// <summary>Полученные данные (JSON-строка).</summary>
        public string Data { get; init; } = null!;
    }

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

    private static string GetProtocolData(string deviceId, string? redirectTicket)
    {
        var deviceInfo = new Dictionary<string, object>
        {
            { "app_name", "Chrome" },
            { "type", 1 }
        };
        var protocol = new Dictionary<string, string>
        {
            { "Ynison-Device-Id", deviceId },
            { "Ynison-Device-Info", JsonSerializer.Serialize(deviceInfo) }
        };
        if (!string.IsNullOrEmpty(redirectTicket))
            protocol.Add("Ynison-Redirect-Ticket", redirectTicket);
        return JsonSerializer.Serialize(protocol);
    }

    private async Task<string> ReadSocketContentAsync()
    {
        var buffer = new byte[BufferSize];
        WebSocketReceiveResult result;
        do
        {
            result = await _socketClient.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
            _data.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
        } while (!result.EndOfMessage);
        return _data.ToString();
    }

    /// <summary>Подключается к WebSocket.</summary>
    /// <param name="storage">Хранилище авторизации.</param>
    /// <param name="url">URL WebSocket.</param>
    /// <param name="redirectTicket">Тикет перенаправления (опционально).</param>
    public async Task ConnectAsync(AuthStorage storage, string url, string? redirectTicket = null)
    {
        _socketClient.Options.AddSubProtocol("Bearer");
        var protocolData = GetProtocolData(storage.DeviceId, redirectTicket);
        _socketClient.Options.SetRequestHeader("Sec-WebSocket-Protocol", $"Bearer, v2, {protocolData}");
        _socketClient.Options.SetRequestHeader("Origin", "https://music.yandex.ru");
        _socketClient.Options.SetRequestHeader("Authorization", $"OAuth {storage.Token}");
        _socketClient.Options.Proxy = storage.Context.WebProxy;

        _cancellationTokenSource = new CancellationTokenSource();
        _cancellationToken = _cancellationTokenSource.Token;

        await _socketClient.ConnectAsync(new Uri(url), CancellationToken.None);
    }

    /// <summary>Начинает асинхронный приём сообщений.</summary>
    public async Task BeginReceiveAsync()
    {
        if (_socketClient.State != WebSocketState.Open)
            return;

        try
        {
            while (!_cancellationToken.IsCancellationRequested && _socketClient.State == WebSocketState.Open)
            {
                var content = await ReadSocketContentAsync();
                OnReceive?.Invoke(this, new ReceiveEventArgs { Data = content });
                _data.Clear();
            }
        }
        catch (OperationCanceledException)
        {
            // Ожидаемая отмена
        }
        finally
        {
            var closeStatus = _socketClient.CloseStatus;
            var closeDesc = _socketClient.CloseStatusDescription;
            OnClose?.Invoke(this, new CloseEventArgs { Status = closeStatus, Description = closeDesc });
            if (_socketClient.State == WebSocketState.Open)
                await _socketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
        }
    }

    /// <summary>Отправляет JSON-сообщение.</summary>
    /// <param name="json">JSON-строка.</param>
    public async ValueTask SendAsync(string json)
    {
        var bytes = Encoding.UTF8.GetBytes(json);
        await _socketClient.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, _cancellationToken);
    }

    /// <summary>Останавливает приём сообщений.</summary>
    public async Task StopReceiveAsync()
    {
        if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
        {
            await _cancellationTokenSource.CancelAsync();
        }
    }

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