using RelayClient.Crypto;
using WebSocketSharp;
using System.Text.Json;
using System.Text.Json.Serialization;
using RelayShared.Rtc;

namespace RelayClient;


public partial class MainPage : ContentPage
{
    private readonly string _username;
    private readonly WebSocket _wsc;
    private string? _serverPublicKey;
    private string? _currentChannelId;
    private string? _currentChannelName;

    private readonly Dictionary<string, List<ChatMessage>> _messagesByChannel = new();
    private readonly List<ChannelItem> _channels = [];
    
    public MainPage(string username)
    {
        InitializeComponent();

        _username = username;
        UserLabel.Text = $"Logged in as: {_username}";

        if (!KeyStorage.HasKeys(_username))
        {
            var keys = E2EeHelper.GenerateRsaKeyPair();
            KeyStorage.SavePrivateKey(_username, keys.privateKey);
            KeyStorage.SavePublicKey(_username, keys.publicKey);
        }

        _wsc = new WebSocket("ws://localhost:1337/");
        
        _wsc.OnMessage += WscOnMessage;
        _wsc.Connect();

        var publicKey = KeyStorage.LoadPublicKey(_username);
        _wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
        _wsc.Send("GET_SERVER_KEY");
        _wsc.Send("GET_CHANNELS");
        
        hybridWebView.SetInvokeJavaScriptTarget(this);
        ServerAPI.setupClient();

    }

    private void SendButton_OnClicked(object? sender, EventArgs e)
    {
        SendMessage();
    }

    private void MessageEntry_OnCompleted(object? sender, EventArgs e)
    {
        SendMessage();
    }

    private void SendMessage()
    {
        var text = MessageEntry.Text?.Trim();

        if (string.IsNullOrWhiteSpace(text))
            return;

        if (string.IsNullOrWhiteSpace(_serverPublicKey))
        {
            Console.WriteLine("Server public key not loaded yet.");
            return;
        }
        
        if (string.IsNullOrWhiteSpace(_currentChannelId))
        {
            Console.WriteLine("No channel selected yet.");
            return;
        }

        var encrypted = E2EeHelper.EncryptForRecipient(text, _serverPublicKey);

        var payload = new SocketEncryptedMessage
        {
            ChannelId = _currentChannelId!,
            Type = SignalType.ClientEncryptedChat,
            SenderUsername = _username,
            CipherText = encrypted.CipherText,
            Nonce = encrypted.Nonce,
            Tag = encrypted.Tag,
            EncryptedKey = encrypted.EncryptedKey
        };

        var json = JsonSerializer.Serialize(payload);
        _wsc.Send(json);

        Console.WriteLine($"[{_username}] sent encrypted message.");

        MessageEntry.Text = string.Empty;
        MessageEntry.Focus();
    }

    private void WscOnMessage(object? sender, MessageEventArgs e)
    {
        if (e.Data.StartsWith("SERVER:REGISTERED_KEY:"))
        {
            Console.WriteLine(e.Data);
            return;
        }
        
        // SafeSendRawToWebView($"[{_username}] RAW WS DATA: {e.Data}");
        
        Console.WriteLine($"[{_username}] RAW WS DATA: {e.Data}");

        try
        {
            using var doc = JsonDocument.Parse(e.Data);
            var root = doc.RootElement;

            if (!root.TryGetProperty("Type", out var typeElement))
                return;

            var type = (SignalType) typeElement.GetInt32();

            if (type == SignalType.ChannelList)
            {
                var channelList = JsonSerializer.Deserialize<SocketChannelList>(e.Data);
                if (channelList is null)
                    return;

                _channels.Clear();
                _channels.AddRange(channelList.Channels.OrderBy(c => c.CreatedAt));

                var defaultChannel = _channels
                                         .Where(c => c.Name.Equals("welcome", StringComparison.OrdinalIgnoreCase))
                                         .OrderBy(c => c.CreatedAt)
                                         .FirstOrDefault()
                                     ?? _channels.OrderBy(c => c.CreatedAt).FirstOrDefault();

                if (defaultChannel is not null)
                {
                    _currentChannelId = defaultChannel.ChannelId;
                    _currentChannelName = defaultChannel.Name;

                    MainThread.BeginInvokeOnMainThread(async () =>
                    {
                        ChannelLabel.Text = $"#{_currentChannelName}";
                        RenderChannelList();
                        await PushRtcContextToJsAsync();
                    });

                    _wsc.Send($"GET_HISTORY|{_username}|{_currentChannelId}");
                }

                return;
            }

            if (type == SignalType.ServerPublicKey)
            {
                var serverKeyMessage = JsonSerializer.Deserialize<ServerPublicKeyMessage>(e.Data);
                if (serverKeyMessage is not null)
                {
                    _serverPublicKey = serverKeyMessage.PublicKey;
                    Console.WriteLine($"[{_username}] loaded server public key.");
                }

                return;
            }
            
            if (type == SignalType.EncryptedSignal)
            {
                var payload = JsonSerializer.Deserialize<SocketRtcSignalMessage>(e.Data);
                if (payload is null)
                    return;

                if (payload.ChannelId != _currentChannelId)
                    return;

                if (payload.SenderUsername == _username)
                    return;

                var privateKey = KeyStorage.LoadPrivateKey(_username);

                var decryptedJson = E2EeHelper.DecryptForRecipient(
                    new EncryptedPayload
                    {
                        CipherText = payload.CipherText,
                        Nonce = payload.Nonce,
                        Tag = payload.Tag,
                        EncryptedKey = payload.EncryptedKey
                    },
                    privateKey
                );

                MainThread.BeginInvokeOnMainThread(async () =>
                {
                    await SendRtcSignalToJsAsync(decryptedJson);
                });

                return;
            }
            
            if (type is SignalType.OfferUpdated or SignalType.AnswerUpdated or SignalType.CandidateAdded or SignalType.CallLeft)
            {
                var rtcNotification = JsonSerializer.Deserialize<RtcNotificationMessage>(e.Data);
                if (rtcNotification is null)
                    return;

                var notificationType = rtcNotification.Type;
                var notificationChannelId = rtcNotification.ChannelId ?? string.Empty;

                if (notificationChannelId != _currentChannelId)
                    return;

                // SafeSendRawToWebView("RTC notification received: " + notificationType + " for " + notificationChannelId);

                MainThread.BeginInvokeOnMainThread(async () =>
                {
                    switch (notificationType)
                    {
                        case SignalType.OfferUpdated:
                        {
                            if (rtcNotification.Username == _username)
                                break;

                            var offer = await GetRtcOffer();
                            await SendRtcSignalToJsAsync(offer);
                            break;
                        }
                        case SignalType.AnswerUpdated:
                        {
                            var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId);
                            if (answer is not null)
                            {
                                await AnswerCallback(answer);
                            }
                            break;
                        }
                        case SignalType.CandidateAdded:
                        {
                            if (rtcNotification.Username == _username)
                                break;

                            try
                            {
                                IceCandidate? iceCandidate = JsonSerializer.Deserialize<IceCandidate>(rtcNotification.Direction);

                                if (iceCandidate is null)
                                    break;

                                IceCandidateCallback(iceCandidate);
                            }
                            catch (Exception ex)
                            {
                                SafeSendRawToWebView($"Candidate rejected: {ex.Message}");
                            }

                            break;
                        }
                        case SignalType.CallLeft:
                        {
                            SafeSendRawToWebView("RTC call left notification received.");
                            RtcLeaveCallback();
                            break;
                        }
                    }
                });

                return;
            }
            
            if (type != SignalType.EncryptedChat)
                return;

            var pyload = JsonSerializer.Deserialize<SocketEncryptedMessage>(e.Data);
            if (pyload is null)
                return;

            if (pyload.RecipientUsername != _username)
                return;

            Console.WriteLine($"[{_username}] received encrypted payload for {pyload.RecipientUsername}");

            var privKey = KeyStorage.LoadPrivateKey(_username);

            var decryptedText = E2EeHelper.DecryptForRecipient(
                new EncryptedPayload
                {
                    CipherText = pyload.CipherText,
                    Nonce = pyload.Nonce,
                    Tag = pyload.Tag,
                    EncryptedKey = pyload.EncryptedKey
                },
                privKey
            );

            Console.WriteLine($"[{_username}] decrypted message from {pyload.SenderUsername}: {decryptedText}");

            var message = new ChatMessage
            {
                SenderUsername = pyload.SenderUsername,
                Text = decryptedText,
                Timestamp = DateTime.Now
            };

            if (!_messagesByChannel.ContainsKey(pyload.ChannelId))
            {
                _messagesByChannel[pyload.ChannelId] = [];
            }

            _messagesByChannel[pyload.ChannelId].Add(message);

            if (pyload.ChannelId == _currentChannelId)
            {
                MainThread.BeginInvokeOnMainThread(() =>
                {
                    RenderSingleMessage(message);
                });
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{_username}] failed to process websocket message: {ex.Message}");
        }
    }

    protected override void OnDisappearing()
    {
        _wsc.OnMessage -= WscOnMessage;
        _wsc.Close();
        base.OnDisappearing();
    }

    private void RenderChannelList()
    {
        SidebarList.Children.Clear();

        foreach (var channel in _channels.OrderBy(c => c.CreatedAt))
        {
            var button = new ChannelButton
            {
                Text = $"#{channel.Name}",
                Type = channel.Type,
                Group = channel.Group
            };

            button.Clicked += (_, _) =>
            {
                _currentChannelId = channel.ChannelId;
                _currentChannelName = channel.Name;
                
                MainThread.BeginInvokeOnMainThread(async () =>
                {
                    await PushRtcContextToJsAsync();
                    if (channel.Type == ChannelType.Voice)
                    {
                        SwapView();
                        // JoinRtcChannel(); //TODO: Join voice calls when clicking channel rather than a separate button
                    }
                });

                ChannelLabel.Text = $"#{_currentChannelName}";
                RenderCurrentChannelMessages();

                if (!_messagesByChannel.ContainsKey(channel.ChannelId))
                {
                    _wsc.Send($"GET_HISTORY|{_username}|{channel.ChannelId}");
                }
            };

            SidebarList.Children.Add(button);
        }
    }

    private void RenderCurrentChannelMessages()
    {
        MessagesLayout.Children.Clear();

        if (_currentChannelId is null)
            return;

        if (!_messagesByChannel.TryGetValue(_currentChannelId, out var messages))
            return;

        foreach (var message in messages.OrderBy(m => m.Timestamp))
        {
            RenderSingleMessage(message);
        }
    }

    private async void RenderSingleMessage(ChatMessage message)
    {
        bool isOwnMessage = message.SenderUsername == _username;

        var bubble = new Border
        {
            StrokeThickness = 1,
            Padding = 10,
            Margin = isOwnMessage
                ? new Thickness(40, 0, 0, 0)
                : new Thickness(0, 0, 40, 0),
            HorizontalOptions = isOwnMessage
                ? LayoutOptions.End
                : LayoutOptions.Start,
            Content = new VerticalStackLayout
            {
                Spacing = 2,
                Children =
                {
                    new Label { Text = message.SenderUsername, FontAttributes = FontAttributes.Bold, FontSize = 12 },
                    new Label { Text = message.Text, FontSize = 14 },
                    new Label { Text = message.Timestamp.ToString("h:mm tt"), FontSize = 10 }
                }
            }
        };

        MessagesLayout.Children.Add(bubble);
        await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true);
    }

    private void SwapView()
    {
        if (RtcView.IsVisible)
        {
            MessagesScrollView.IsVisible = true;
            RtcView.IsVisible = false;
            ViewSwapped.Text = "Swap to Web View";

        }
        else
        {
            MessagesScrollView.IsVisible = false;
            RtcView.IsVisible = true;
            ViewSwapped.Text = "Swap to Message View";
        }
    }
    private void SwapView_OnClicked(object? sender, EventArgs e)
    {
        SwapView();
    }

    #region RTC Functions
    public async Task<bool> JoinRtcChannel()
    {
        if (string.IsNullOrWhiteSpace(_currentChannelId))
            return false;

        _wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}");

        // SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName} | {_currentChannelId} ");

        bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId);

        SafeSendRawToWebView($"Rtc Channel {_currentChannelName} | {_currentChannelId} is active: {active}");

        return active;
    }
    
    public void LeaveRtcChannel()
    {
        if (string.IsNullOrWhiteSpace(_currentChannelId))
            return;

        _wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}");
    }
    
    public async void WriteRtcOffer(string json)
    {
        try
        {
            RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
            DBOffer offer = new DBOffer
            {
                ChannelId = _currentChannelId,
                Username = _username,
                SessionDescription = description
            };
            var response = await ServerAPI.PostOfferAsync(offer);
            SafeSendRawToWebView(response.ToString());
        }
        catch (Exception ex)
        {
            SafeSendRawToWebView($"WriteRtcOffer failed: {ex.Message}");
        }

    }
    public async Task<string> GetRtcOffer()
    {
        RtcDescription? offer = await ServerAPI.GetOffersForChannelAsync(_currentChannelId);
        return JsonSerializer.Serialize(offer);
    }
    
    public async void WriteRtcAnswer(string json)
    {
        // SafeSendRawToWebView("WriteRtcAnswer entered with: " + json);

        try
        {
            RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
            DBOffer answer = new DBOffer
            {
                ChannelId = _currentChannelId,
                Username = _username,
                SessionDescription = description
            };
            await ServerAPI.PostAnswerAsync(answer);
            SafeSendRawToWebView("WriteRtcAnswer posted successfully");
        }
        catch (Exception ex)
        {
            SafeSendRawToWebView("WriteRtcAnswer failed: " + ex.Message);
        }
    }

    public async void WriteIceCandidate(string json)
    {
        try
        {
            IceCandidate? candidate = JsonSerializer.Deserialize<IceCandidate>(json);
            DBIceCandidate DBCandidate = new DBIceCandidate
            {
                ChannelId = _currentChannelId,
                Username = _username,
                Candidate = candidate
            };
            if (candidate == null) return;
            await ServerAPI.PostIceCandidateAsync(DBCandidate);
        }
        catch (Exception ex)
        {
            SafeSendRawToWebView("WriteIceCandidate failed: " + ex.Message);
        }
    }

    public async void IceCandidateCallback(IceCandidate candidate)
    {
        try
        {
            await hybridWebView.InvokeJavaScriptAsync("IceCandidateAdded", [candidate], [HybridJSType.Default.IceCandidate]);
        }
        catch (Exception ex)
        {
            SafeSendRawToWebView("WriteIceCandidate failed: " + ex.Message);
        }
    }
    public async Task AnswerCallback(RtcDescription answer)
    {
        answer.sdp = answer.sdp.Replace("\r\n", "(rn)");
        try
        {
            await hybridWebView.InvokeJavaScriptAsync("AnswerCallbackJS", [answer], [HybridJSType.Default.RtcDescription]);
        }
        catch (Exception ex)
        {
            SafeSendRawToWebView("AnswerCallback failed: " + ex.Message);
        }
    }

    public async void RtcLeaveCallback()
    {
        try
        {
            await hybridWebView.InvokeJavaScriptAsync("RtcLeaveCall", [], []);
        }
        catch (Exception ex)
        {
            SafeSendRawToWebView("RtcLeaveCallback failed: " + ex.Message);
        }
    }
    
    private async Task SendRtcSignalToJsAsync(string rawJson)
    {
        var jsArg = JsonSerializer.Serialize(rawJson);
        await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal({jsArg})");
    } //Remove?
    
    private async Task PushRtcContextToJsAsync()
    {
        var usernameJson = JsonSerializer.Serialize(_username);
        var channelIdJson = JsonSerializer.Serialize(_currentChannelId);

        await hybridWebView.EvaluateJavaScriptAsync($"window.setUsername({usernameJson})");
        await hybridWebView.EvaluateJavaScriptAsync($"window.setChannelId({channelIdJson})");

        Console.WriteLine($"[{_username}] pushed RTC context into HybridWebView.");
    } //Remove?
    
    public void SendRtcSignal(string json)
    {
        if (string.IsNullOrWhiteSpace(_serverPublicKey))
        {
            Console.WriteLine("Server public key not loaded yet.");
            return;
        }

        RtcSignalMessage? rtcSignal;
        try
        {
            rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(json);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Failed to parse RTC signal from JS: {ex.Message}");
            return;
        }

        if (rtcSignal is null)
            return;

        var encrypted = E2EeHelper.EncryptForRecipient(json, _serverPublicKey);

        var payload = new SocketRtcSignalMessage
        {
            Type = SignalType.EncryptedSignal,
            SenderUsername = _username,
            ChannelId = rtcSignal.ChannelId,
            CipherText = encrypted.CipherText,
            Nonce = encrypted.Nonce,
            Tag = encrypted.Tag,
            EncryptedKey = encrypted.EncryptedKey
        };

        _wsc.Send(JsonSerializer.Serialize(payload));
        Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}");
    } //Remove?
    
    public async Task<string> GetRtcParticipants()
    {
        var participants = await ServerAPI.GetRtcParticipantsAsync(_currentChannelId);
        return JsonSerializer.Serialize(participants);
    }
    
    #endregion
    private void OnSendMessageButtonClicked(object sender, EventArgs e)
    {
        SafeSendRawToWebView($"Hello from C#!");
    }

    private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
    {
        if (e.Message == "rtc_page_ready")
        {
            await PushRtcContextToJsAsync();
            return;
        }

        SafeSendRawToWebView($"JS RAW -> C#: {e.Message}");
    }
    
    private void SafeSendRawToWebView(string message)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            try
            {
                hybridWebView.SendRawMessage(message);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[{_username}] failed to send raw message to HybridWebView: {ex.Message}");
            }
        });
    }

    public class ChannelButton : Button
    {
        public ChannelType Type { get; set; }
        public string Group { get; set; }
    }
    
    [JsonSourceGenerationOptions(WriteIndented = false)]
    [JsonSerializable(typeof(RtcDescription))]
    [JsonSerializable(typeof(List<RtcSignalMessage>))]
    [JsonSerializable(typeof(IceCandidate))]
    [JsonSerializable(typeof(List<IceCandidate>))]
    [JsonSerializable(typeof(string))]
    internal partial class HybridJSType : JsonSerializerContext
    {
        // This type's attributes specify JSON serialization info to preserve type structure
        // for trimmed builds.
    }
    
}