using NUnit.Framework;
using RebootKit.Engine.Extensions;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Network;
using Unity.Collections;
using UnityEngine;

namespace Tests.Runtime.Engine {
    public class NetworkBufferWriterReaderTests {
        [Test]
        public void NetworkBuffer_Int() {
            const int k_Value = 12345;

            using NativeArray<byte> data = new NativeArray<byte>(4, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out int value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_Two_Ints() {
            const int k_Value1 = 12345;
            const int k_Value2 = 67890;

            using NativeArray<byte> data = new NativeArray<byte>(8, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value1);
            writer.Write(k_Value2);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out int value1));
            Assert.AreEqual(k_Value1, value1);
            Assert.IsTrue(reader.Read(out int value2));
            Assert.AreEqual(k_Value2, value2);
        }

        [Test]
        public void NetworkBuffer_Short() {
            const short k_Value = 123;

            using NativeArray<byte> data = new NativeArray<byte>(2, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out short value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_UShort() {
            const ushort k_Value = 123;
            using NativeArray<byte> data = new NativeArray<byte>(2, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out ushort value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_Long() {
            const long k_Value = 123456789L;

            using NativeArray<byte> data = new NativeArray<byte>(8, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out long value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_ULong() {
            const ulong k_Value = 123456789UL;

            using NativeArray<byte> data = new NativeArray<byte>(8, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out ulong value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_Bool() {
            const bool k_Value = true;

            using NativeArray<byte> data = new NativeArray<byte>(1, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out bool value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_Byte() {
            const byte k_Value = 0xAB;

            using NativeArray<byte> data = new NativeArray<byte>(1, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out byte value));
            Assert.AreEqual(k_Value, value);
        }

        [Test]
        public void NetworkBuffer_Float() {
            const float k_Value = 123.45f;
            using NativeArray<byte> data = new NativeArray<byte>(4, Allocator.Temp);
            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(k_Value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out float value));
            Assert.AreEqual(k_Value, value, 0.0001f);
        }

        [Test]
        public void NetworkBuffer_ReadBeyond() {
            using NativeArray<byte> data = new NativeArray<byte>(4, Allocator.Temp);

            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(12345);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out int value));
            Assert.AreEqual(12345, value);

            Assert.IsFalse(reader.Read(out int _));
        }

        [Test]
        public void NetworkBuffer_FixedString32Bytes() {
            FixedString32Bytes value = new FixedString32Bytes("henlo_world");

            using NativeArray<byte> data = new NativeArray<byte>(32, Allocator.Temp);
            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write(value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsTrue(reader.Read(out FixedString32Bytes readValue), "Failed to read FixedString32Bytes");
            Assert.IsTrue(value == readValue, "Value should be equal to read value");
        }

        [Test]
        public void NetworkBuffer_Empty() {
            using NativeArray<byte> data = new NativeArray<byte>(0, Allocator.Temp);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            Assert.IsFalse(reader.Read(out int value));
            Assert.AreEqual(0, value);
        }

        [Test]
        public void NetworkBuffer_Size_NativeArray() {
            NativeArray<byte> value = new NativeArray<byte>(new byte[] {
                1,
                2,
                3,
                4,
                5
            }, Allocator.Temp);

            using NativeArray<byte> data = new NativeArray<byte>(value.Length + sizeof(ushort), Allocator.Temp);
            using NetworkBufferWriter writer = new NetworkBufferWriter(data, 0);
            writer.Write((ushort) value.Length);
            writer.Write(value);

            using NetworkBufferReader reader = new NetworkBufferReader(data);
            reader.Read(out ushort readLength);
            Assert.IsTrue(readLength == value.Length,
                          $"Read Length({readLength }) should be equal to an actual value.length({value.Length})");
            Assert.IsTrue(reader.Read(out NativeArray<byte> readValue, readLength), "Failed to read NativeArray<byte>");
            Assert.AreEqual(value.Length, readValue.Length,
                            "Length of read NativeArray<byte> should match written length");

            for (int i = 0; i < value.Length; i++) {
                Assert.AreEqual(value[i], readValue[i], $"Value at index {i} should match");
            }

            value.Dispose();
            readValue.Dispose();
        }
    }
}