using NUnit.Framework;
using OkonaPad.Input;
using OkonaPad.Networking.Binary;

namespace OkonaPad.Tests
{
    [TestFixture]
    public class OkonaInputBridgeParsingTests
    {
        #region Helper Methods

        /// <summary>
        /// Creates a binary message with the standard header format.
        /// </summary>
        private static byte[] CreateMessage(MessageType type, params byte[] payload)
        {
            int payloadLength = payload?.Length ?? 0;
            int totalLength = 1 + payloadLength; // type(1) + payload
            byte[] buffer = new byte[2 + totalLength]; // length(2) + type(1) + payload

            // Write length header (big-endian)
            buffer[0] = (byte)(totalLength >> 8);
            buffer[1] = (byte)(totalLength & 0xFF);
            // Write message type
            buffer[2] = (byte)type;
            // Write payload
            if (payload != null)
            {
                for (int i = 0; i < payload.Length; i++)
                {
                    buffer[3 + i] = payload[i];
                }
            }

            return buffer;
        }

        /// <summary>
        /// Creates a button message.
        /// </summary>
        private static byte[] CreateButtonMessage(ButtonCode button, bool pressed)
        {
            return CreateMessage(MessageType.Button, (byte)button, pressed ? (byte)1 : (byte)0);
        }

        /// <summary>
        /// Creates a D-pad message.
        /// </summary>
        private static byte[] CreateDpadMessage(DpadDirection direction, bool pressed)
        {
            return CreateMessage(MessageType.Dpad, (byte)direction, pressed ? (byte)1 : (byte)0);
        }

        /// <summary>
        /// Creates a joystick message with big-endian int16 values.
        /// </summary>
        private static byte[] CreateJoystickMessage(short x, short y)
        {
            byte[] payload = new byte[4];
            // Big-endian X
            payload[0] = (byte)(x >> 8);
            payload[1] = (byte)(x & 0xFF);
            // Big-endian Y
            payload[2] = (byte)(y >> 8);
            payload[3] = (byte)(y & 0xFF);
            return CreateMessage(MessageType.Joystick, payload);
        }

        #endregion

        #region Button Parsing Tests

        [Test]
        public void TryParseSingleFrame_ButtonA_Pressed_ParsesCorrectly()
        {
            byte[] data = CreateButtonMessage(ButtonCode.A, true);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(1, pm.ControllerId);
            Assert.AreEqual(MessageType.Button, pm.Type);
            Assert.AreEqual(ButtonCode.A, pm.Button);
            Assert.AreEqual(1, pm.State);
        }

        [Test]
        public void TryParseSingleFrame_ButtonB_Released_ParsesCorrectly()
        {
            byte[] data = CreateButtonMessage(ButtonCode.B, false);

            bool result = OkonaInputBridge.TryParseSingleFrame(2, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(2, pm.ControllerId);
            Assert.AreEqual(MessageType.Button, pm.Type);
            Assert.AreEqual(ButtonCode.B, pm.Button);
            Assert.AreEqual(0, pm.State);
        }

        [Test]
        public void TryParseSingleFrame_AllButtonCodes_ParseCorrectly()
        {
            ButtonCode[] buttons = {
                ButtonCode.A, ButtonCode.B, ButtonCode.X, ButtonCode.Y,
                ButtonCode.Start, ButtonCode.Select
            };

            foreach (var button in buttons)
            {
                byte[] data = CreateButtonMessage(button, true);
                bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

                Assert.IsTrue(result, $"Failed to parse button {button}");
                Assert.AreEqual(button, pm.Button, $"Button code mismatch for {button}");
            }
        }

        #endregion

        #region D-pad Parsing Tests

        [Test]
        public void TryParseSingleFrame_DpadUp_Pressed_ParsesCorrectly()
        {
            byte[] data = CreateDpadMessage(DpadDirection.Up, true);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(MessageType.Dpad, pm.Type);
            Assert.AreEqual(DpadDirection.Up, pm.Direction);
            Assert.AreEqual(1, pm.State);
        }

        [Test]
        public void TryParseSingleFrame_DpadDown_Released_ParsesCorrectly()
        {
            byte[] data = CreateDpadMessage(DpadDirection.Down, false);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(MessageType.Dpad, pm.Type);
            Assert.AreEqual(DpadDirection.Down, pm.Direction);
            Assert.AreEqual(0, pm.State);
        }

        [Test]
        public void TryParseSingleFrame_AllDpadDirections_ParseCorrectly()
        {
            DpadDirection[] directions = {
                DpadDirection.Up, DpadDirection.Down,
                DpadDirection.Left, DpadDirection.Right
            };

            foreach (var direction in directions)
            {
                byte[] data = CreateDpadMessage(direction, true);
                bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

                Assert.IsTrue(result, $"Failed to parse direction {direction}");
                Assert.AreEqual(direction, pm.Direction, $"Direction mismatch for {direction}");
            }
        }

        #endregion

        #region Joystick Parsing Tests

        [Test]
        public void TryParseSingleFrame_LeftJoystick_Center_ParsesCorrectly()
        {
            byte[] data = CreateJoystickMessage(0, 0);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(MessageType.Joystick, pm.Type);
            Assert.AreEqual(0, pm.X);
            Assert.AreEqual(0, pm.Y);
        }

        [Test]
        public void TryParseSingleFrame_LeftJoystick_FullRight_ParsesCorrectly()
        {
            byte[] data = CreateJoystickMessage(32767, 0);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(32767, pm.X);
            Assert.AreEqual(0, pm.Y);
        }

        [Test]
        public void TryParseSingleFrame_LeftJoystick_FullLeft_ParsesCorrectly()
        {
            byte[] data = CreateJoystickMessage(-32767, 0);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(-32767, pm.X);
            Assert.AreEqual(0, pm.Y);
        }

        [Test]
        public void TryParseSingleFrame_LeftJoystick_FullUp_ParsesCorrectly()
        {
            byte[] data = CreateJoystickMessage(0, 32767);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(0, pm.X);
            Assert.AreEqual(32767, pm.Y);
        }

        [Test]
        public void TryParseSingleFrame_LeftJoystick_Diagonal_ParsesCorrectly()
        {
            byte[] data = CreateJoystickMessage(16383, -16383);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(16383, pm.X);
            Assert.AreEqual(-16383, pm.Y);
        }

        #endregion

        #region Join/Pong Parsing Tests

        [Test]
        public void TryParseSingleFrame_Join_ParsesCorrectly()
        {
            byte[] data = CreateMessage(MessageType.Join);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(MessageType.Join, pm.Type);
            Assert.AreEqual(1, pm.ControllerId);
        }

        [Test]
        public void TryParseSingleFrame_Pong_ParsesCorrectly()
        {
            byte[] data = CreateMessage(MessageType.Pong);

            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out var pm);

            Assert.IsTrue(result);
            Assert.AreEqual(MessageType.Pong, pm.Type);
        }

        #endregion

        #region Error Handling Tests

        [Test]
        public void TryParseSingleFrame_NullData_ReturnsFalse()
        {
            bool result = OkonaInputBridge.TryParseSingleFrame(1, null, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_EmptyData_ReturnsFalse()
        {
            bool result = OkonaInputBridge.TryParseSingleFrame(1, new byte[0], out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_TooShort_ReturnsFalse()
        {
            byte[] data = new byte[] { 0x00, 0x01 }; // Only 2 bytes, need at least 3
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_LengthMismatch_ReturnsFalse()
        {
            // Header says length is 5, but actual data is only 3 bytes total
            byte[] data = new byte[] { 0x00, 0x05, 0x00 };
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_ButtonWrongLength_ReturnsFalse()
        {
            // Button message should be exactly 5 bytes
            byte[] data = new byte[] { 0x00, 0x02, (byte)MessageType.Button, 0x00 }; // 4 bytes
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_DpadWrongLength_ReturnsFalse()
        {
            // Dpad message should be exactly 5 bytes
            byte[] data = new byte[] { 0x00, 0x04, (byte)MessageType.Dpad, 0x00, 0x01, 0x02 }; // 6 bytes
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_JoystickWrongLength_ReturnsFalse()
        {
            // Joystick message should be exactly 7 bytes
            byte[] data = new byte[] { 0x00, 0x03, (byte)MessageType.Joystick, 0x00, 0x00 }; // 5 bytes
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_UnknownMessageType_ReturnsFalse()
        {
            // Unknown message type (99)
            byte[] data = new byte[] { 0x00, 0x01, 99 };
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        [Test]
        public void TryParseSingleFrame_RumbleType_ReturnsFalse()
        {
            // Rumble is server-to-client only, should not be parsed as input
            byte[] data = CreateMessage(MessageType.Rumble, 0x01, 0xF4);
            bool result = OkonaInputBridge.TryParseSingleFrame(1, data, out _);
            Assert.IsFalse(result);
        }

        #endregion

        #region Controller ID Tests

        [Test]
        public void TryParseSingleFrame_PreservesControllerId()
        {
            byte[] data = CreateButtonMessage(ButtonCode.A, true);

            for (int controllerId = 1; controllerId <= 6; controllerId++)
            {
                bool result = OkonaInputBridge.TryParseSingleFrame(controllerId, data, out var pm);
                Assert.IsTrue(result);
                Assert.AreEqual(controllerId, pm.ControllerId, $"Controller ID mismatch for {controllerId}");
            }
        }

        #endregion
    }
}
