using ABI_RC.Core.Networking;
using ABI_RC.Systems.ModNetwork;
using DarkRift;
using NAK.AvatarScaleMod.AvatarScaling;
using UnityEngine;

namespace NAK.AvatarScaleMod.Networking;

// overcomplicated, but functional
// a

public static class ModNetwork
{
    public static bool Debug_NetworkInbound = false;
    public static bool Debug_NetworkOutbound = false;

    private static bool _isSubscribedToModNetwork;
    
    #region Constants

    private const string ModId = "MelonMod.NAK.AvatarScaleMod";
    private const float SendRateLimit = 0.25f;
    private const float ReceiveRateLimit = 0.2f;
    private const int MaxWarnings = 2;
    private const float TimeoutDuration = 10f;
    
    private class QueuedMessage
    {
        public MessageType Type { get; set; }
        public float Height { get; set; }
        public string TargetPlayer { get; set; }
        public string Sender { get; set; }
    }
    
    #endregion

    #region Private State

    private static readonly Dictionary<string, QueuedMessage> OutboundQueue = new();
    private static float LastSentTime;

    private static readonly Dictionary<string, QueuedMessage> InboundQueue = new();
    private static readonly Dictionary<string, float> LastReceivedTimes = new();

    private static readonly Dictionary<string, int> UserWarnings = new();
    private static readonly Dictionary<string, float> UserTimeouts = new();

    #endregion

    #region Enums

    private enum MessageType : byte
    {
        SyncHeight = 0, // just send height
        RequestHeight = 1 // send height, request height back
    }

    #endregion

    #region Mod Network Internals

    internal static void Subscribe()
    {
        ModNetworkManager.Subscribe(ModId, OnMessageReceived);
        
        _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId);
        if (!_isSubscribedToModNetwork)
            AvatarScaleMod.Logger.Error("Failed to subscribe to Mod Network! This is a critical error! Please report this to the mod author! (NAK) (ModNetwork.cs) (Subscribe) (Line 150) (AvatarScaleMod) (AvatarScale) (MelonLoader) (MelonLoader.Mods) (MelonLoader.Mods.MelonMod) (MelonLoader.MelonMod) (MelonLoader.MelonMod.MelonBaseMod) (MelonLoader.MelonMod.MelonMod) (MelonLoader.MelonMod.MelonModBase) (MelonLoader.MelonMod.MelonModBase`1) (MelonLoader.MelonMod.MelonModBase`1[[NAK.AvatarScaleMod.AvatarScaling.AvatarScaleMod, NAK.AvatarScaleMod, Version=123");
        
        AvatarScaleEvents.OnLocalAvatarHeightChanged.AddListener(scaler => SendNetworkHeight(scaler.GetTargetHeight()));
    }
    
    internal static void Update()
    {
        if (!_isSubscribedToModNetwork)
            return;
        
        ProcessOutboundQueue();
        ProcessInboundQueue();
    }

    private static void SendMessage(MessageType messageType, float height, string playerId = null)
    {
        if (!IsConnectedToGameNetwork())
            return;

        if (!Enum.IsDefined(typeof(MessageType), messageType))
            return;
        
        if (!string.IsNullOrEmpty(playerId))
        {
            // to specific user
            using ModNetworkMessage modMsg = new(ModId, playerId);
            modMsg.Write((byte)messageType);
            modMsg.Write(height);
            modMsg.Send();
        }
        else
        {
            // to all users
            using ModNetworkMessage modMsg = new(ModId);
            modMsg.Write((byte)messageType);
            modMsg.Write(height);
            modMsg.Send();
        }
    }

    private static void OnMessageReceived(ModNetworkMessage msg)
    {
        msg.Read(out byte msgTypeRaw);
        msg.Read(out float receivedHeight);

        if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw))
            return;

        // User is in timeout
        if (UserTimeouts.TryGetValue(msg.Sender, out var timeoutEnd) && Time.time < timeoutEnd)
            return;

        if (IsRateLimited(msg.Sender))
            return;

        QueuedMessage inboundMessage = new()
        {
            Type = (MessageType)msgTypeRaw,
            Height = receivedHeight,
            Sender = msg.Sender
        };

        InboundQueue[msg.Sender] = inboundMessage;
    }

    #endregion

    #region Public Methods

    public static void SendNetworkHeight(float newHeight)
    {
        OutboundQueue["global"] = new QueuedMessage { Type = MessageType.SyncHeight, Height = newHeight };
    }

    public static void RequestHeightSync()
    {
        var myCurrentHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
        OutboundQueue["global"] = new QueuedMessage { Type = MessageType.RequestHeight, Height = myCurrentHeight };
    }

    #endregion

    #region Outbound Height Queue

    private static void ProcessOutboundQueue()
    {
        if (OutboundQueue.Count == 0 || Time.time - LastSentTime < SendRateLimit)
            return;

        foreach (QueuedMessage message in OutboundQueue.Values)
        {
            SendMessage(message.Type, message.Height, message.TargetPlayer);
            
            if (Debug_NetworkOutbound)
                AvatarScaleMod.Logger.Msg(
                    $"Sending message {message.Type.ToString()} to {(string.IsNullOrEmpty(message.TargetPlayer) ? "ALL" : message.TargetPlayer)}: {message.Height}");
        }

        OutboundQueue.Clear();
        LastSentTime = Time.time;
    }

    #endregion

    #region Inbound Height Queue

    private static bool IsRateLimited(string userId)
    {
        // Rate-limit checking
        if (LastReceivedTimes.TryGetValue(userId, out var lastReceivedTime) &&
            Time.time - lastReceivedTime < ReceiveRateLimit)
        {
            if (UserWarnings.TryGetValue(userId, out var warnings))
            {
                warnings++;
                UserWarnings[userId] = warnings;

                if (warnings >= MaxWarnings)
                {
                    UserTimeouts[userId] = Time.time + TimeoutDuration;
                    AvatarScaleMod.Logger.Warning($"User is sending height updates too fast! Applying 10s timeout... : {userId}");
                    return true;
                }
            }
            else
            {
                UserWarnings[userId] = 1;
            }

            return true;
        }

        LastReceivedTimes[userId] = Time.time;
        UserWarnings.Remove(userId); // Reset warnings
        return false;
    }

    private static void ProcessInboundQueue()
    {
        foreach (QueuedMessage message in InboundQueue.Values)
        {
            switch (message.Type)
            {
                case MessageType.RequestHeight:
                {
                    var myNetworkHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
                    OutboundQueue[message.Sender] = new QueuedMessage
                    {
                        Type = MessageType.SyncHeight,
                        Height = myNetworkHeight,
                        TargetPlayer = message.Sender
                    };

                    AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
                    break;
                }
                case MessageType.SyncHeight:
                    AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
                    break;
                default:
                    AvatarScaleMod.Logger.Error($"Invalid message type received from: {message.Sender}");
                    break;
            }
            
            if (Debug_NetworkInbound)
                AvatarScaleMod.Logger.Msg($"Received message {message.Type.ToString()} from {message.Sender}: {message.Height}");
        }

        InboundQueue.Clear();
    }

    #endregion

    #region Private Methods

    private static bool IsConnectedToGameNetwork()
    {
        return NetworkManager.Instance != null
               && NetworkManager.Instance.GameNetwork != null
               && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
    }

    #endregion

    #region Messages

    private class AvatarHeightMessage
    {
        public float Height { get; set; }
        public bool IsUniversal { get; set; }
    }

    private static void AddAvatarHeightMessageConverter()
    {
        ModNetworkMessage.AddConverter(Reader, Writer);
        return;

        AvatarHeightMessage Reader(ModNetworkMessage msg)
        {
            AvatarHeightMessage avatarHeightMessage = new();
            msg.Read(out float height);
            avatarHeightMessage.Height = height;
            msg.Read(out bool isUniversal);
            avatarHeightMessage.IsUniversal = isUniversal;
            return avatarHeightMessage;
        }

        void Writer(ModNetworkMessage msg, AvatarHeightMessage value)
        {
            msg.Write(value.Height);
            msg.Write(value.IsUniversal);
        }
    }

    #endregion
}