using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Text.Json;
using Websocket.Client;

namespace VRCX
{
    public partial class AppApi
    {
        private static readonly Uri _ovrtWebsocketUri = new("ws://127.0.0.1:11450/api");
        private static readonly byte[] _vrcxIcon = Convert.FromBase64String(" ");
        private static readonly object _ovrtLock = new();
        private static WebsocketClient _ovrtWebsocketClient;

        private static void InitializeOvrTk()
        {
            lock (_ovrtLock)
            {
                if (_ovrtWebsocketClient != null)
                    return;

                var dotnetWebsocketClientFactory = new Func<ClientWebSocket>(() =>
                {
                    var client = new ClientWebSocket
                    {
                        Options =
                        {
                            KeepAliveInterval = TimeSpan.FromSeconds(5),
                        }
                    };
                    client.Options.SetRequestHeader("user-agent", Program.Version);
                    return client;
                });

                _ovrtWebsocketClient = new WebsocketClient(_ovrtWebsocketUri, dotnetWebsocketClientFactory)
                {
                    Name = "OVRToolkit Websocket",
                    // Swap ReconnectTimeout when Ping is implemented
                    // ReconnectTimeout = TimeSpan.FromSeconds(20),
                    ReconnectTimeout = null,
                    ErrorReconnectTimeout = TimeSpan.FromSeconds(30),
                };

                _ovrtWebsocketClient.ReconnectionHappened.Subscribe(info =>
                {
                    logger.ConditionalDebug("[OVRToolkit Websocket] Reconnection happened, type: {0}", info?.Type.ToString());
                });
                _ovrtWebsocketClient.DisconnectionHappened.Subscribe(info =>
                {
                    logger.ConditionalDebug("[OVRToolkit Websocket] Disconnection happened, type: {0}", info?.Type.ToString());
                });
                _ovrtWebsocketClient.MessageReceived.Subscribe(msg =>
                {
                    logger.ConditionalDebug("[OVRToolkit Websocket] Message received: {0}", msg.Text);
                });

                _ovrtWebsocketClient.Start().Wait();
            }
        }

        private static void SendMessages(IEnumerable<OvrtMessage> ovrtMessages)
        {
            if (ovrtMessages != null && ovrtMessages.Any())
            {
                if (_ovrtWebsocketClient == null)
                    InitializeOvrTk();

                if (_ovrtWebsocketClient.IsRunning)
                {
                    foreach (var message in ovrtMessages)
                    {
                        _ovrtWebsocketClient.Send(JsonSerializer.Serialize(message));
                    }
                }
            }
        }

        /// <summary>
        /// Displays an OVRToolkit notification with the specified title and body.
        /// HUD notification - Visible in the lower part of the HMD view and moves with the head.
        /// Wrist notification - Visible above the wristwatch until cleared by the user via the 'X' icon.
        /// </summary>
        /// <param name="hudNotification">Whether or not to display a HUD notification.</param>
        /// <param name="wristNotification">Whether or not to display a Wrist notification.</param>
        /// <param name="title">The title of the notification.</param>
        /// <param name="body">The content of the notification.</param>
        /// <param name="timeout">[CURRENTLY UNUSED]The timeout of the notification.</param>
        /// <param name="opacity">The opacity of the notification (0.0 to 1.0).</param>
        /// <param name="image">The image of the notification.</param>
        public void OVRTNotification(bool hudNotification, bool wristNotification, string title, string body, int timeout, double opacity, string image = "")
        {
            List<OvrtMessage> messages = [];

            byte[] imageBytes = null;
            try
            {
                if (!string.IsNullOrWhiteSpace(image) && File.Exists(image))
                {
                    imageBytes = File.ReadAllBytes(image);
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Failed to read OVRT notification image");
            }
            imageBytes ??= _vrcxIcon;

            if (wristNotification)
            {
                messages.Add(new OvrtMessage
                {
                    MessageType = "SendWristNotification",
                    Json = JsonSerializer.Serialize(new OvrtWristNotificationMessage
                    {
                        Body = title + " - " + body
                    })
                });
            }

            if (hudNotification)
            {
                messages.Add(new OvrtMessage
                {
                    MessageType = "SendNotification",
                    Json = JsonSerializer.Serialize(new OvrtHudNotificationMessage
                    {
                        Title = title,
                        Body = body,
                        Icon = imageBytes
                    })
                });
            }

            SendMessages(messages);
        }
        
        private struct OvrtMessage
        {
            [System.Text.Json.Serialization.JsonPropertyName("messageType")]
            public string MessageType { get; set; }

            [System.Text.Json.Serialization.JsonPropertyName("json")]
            public string Json { get; set; }
        }

        private struct OvrtHudNotificationMessage
        {
            [System.Text.Json.Serialization.JsonPropertyName("title")]
            public string Title { get; set; }

            [System.Text.Json.Serialization.JsonPropertyName("body")]
            public string Body { get; set; }

            [System.Text.Json.Serialization.JsonPropertyName("icon")]
            public byte[] Icon { get; set; }
        }

        private struct OvrtWristNotificationMessage
        {
            [System.Text.Json.Serialization.JsonPropertyName("body")]
            public string Body { get; set; }
        }
    }
}