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

namespace RelayClient;

public partial class MainPage : ContentPage
{
    private readonly string _username;
    private readonly RelaySocketClient _socket;
    private readonly RtcBridgeService _rtc;

    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);
        }

        ServerAPI.setupClient();

        _socket = new RelaySocketClient(_username);
        _rtc = new RtcBridgeService(
            _username,
            _socket,
            hybridWebView,
            () => _currentChannelId,
            SafeSendRawToWebView
        );
        
        hybridWebView.SetInvokeJavaScriptTarget(_rtc);

        _socket.Log += Console.WriteLine;
        _socket.ChannelListReceived += HandleChannelList;
        _socket.EncryptedChatReceived += HandleEncryptedChat;
        _socket.EncryptedRtcSignalReceived += payload =>
        {
            MainThread.BeginInvokeOnMainThread(async () =>
            {
                await _rtc.HandleIncomingRtcSignalAsync(payload);
            });
        };

        _socket.Connect();
    }

    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(_socket.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, _socket.ServerPublicKey);

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

        _socket.SendJson(payload);

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

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

    private void HandleChannelList(SocketChannelList channelList)
    {
        _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 null) return;

        _currentChannelId = defaultChannel.ChannelId;
        _currentChannelName = defaultChannel.Name;

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

        _socket.SendRaw($"GET_HISTORY|{_username}|{_currentChannelId}");
    }

    private void HandleEncryptedChat(SocketEncryptedMessage payload) {
        if (payload.RecipientUsername != _username)
            return;

        string decryptedText;

        try
        {
            var privateKey = KeyStorage.LoadPrivateKey(_username);

            decryptedText = E2EeHelper.DecryptForRecipient(
                new EncryptedPayload
                {
                    CipherText = payload.CipherText,
                    Nonce = payload.Nonce,
                    Tag = payload.Tag,
                    EncryptedKey = payload.EncryptedKey
                },
                privateKey
            ); 
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{_username}] failed to decrypt chat: {ex.Message}");
            return;
        }

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

        if (!_messagesByChannel.ContainsKey(payload.ChannelId))
            _messagesByChannel[payload.ChannelId] = [];

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

        if (payload.ChannelId == _currentChannelId)
        {
            MainThread.BeginInvokeOnMainThread(() =>
            {
                RenderSingleMessage(message);
            });
        }
    }

    protected override void OnDisappearing()
    {
        _socket.Disconnect();
        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 _rtc.PushRtcContextToJsAsync();

                    if (channel.Type == ChannelType.Voice)
                    {
                        if (!RtcView.IsVisible)
                            SwapView();

                        _ = _rtc.JoinRtcChannel();
                    }
                });

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

                if (!_messagesByChannel.ContainsKey(channel.ChannelId))
                    _socket.SendRaw($"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)
    {
        var 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();
    }

    private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
    {
        if (e.Message == "rtc_page_ready")
        {
            await _rtc.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; } = string.Empty;
    }

    [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
    {
    }
}