using Mbproxy.Proxy.Multiplexing;
using Shouldly;
using Xunit;

namespace Mbproxy.Tests.Proxy.Multiplexing;

/// <summary>
/// Equality + hash-distribution coverage for the Phase-10 <see cref="CoalescingKey"/>
/// record struct. The key is the load-bearing primitive of read coalescing: bad equality
/// would either cause unrelated requests to share a backend round-trip (correctness loss)
/// or prevent legitimate same-key requests from coalescing (performance loss).
/// </summary>
[Trait("Category", "Unit")]
public sealed class CoalescingKeyTests
{
    [Fact]
    public void Equality_OnIdenticalKeys_ReturnsTrue()
    {
        var a = new CoalescingKey(UnitId: 1, Fc: 0x03, StartAddress: 100, Qty:4);
        var b = new CoalescingKey(UnitId: 1, Fc: 0x03, StartAddress: 100, Qty:4);

        a.ShouldBe(b, "identical keys must compare equal");
        a.GetHashCode().ShouldBe(b.GetHashCode(), "identical keys must hash to the same bucket");
    }

    [Fact]
    public void Equality_OnDifferentFc_ReturnsFalse()
    {
        // FC03 (Read Holding Registers) and FC04 (Read Input Registers) name DIFFERENT
        // Modbus tables even for the same address. Coalescing them would deliver wrong
        // data.
        var fc03 = new CoalescingKey(UnitId: 1, Fc: 0x03, StartAddress: 100, Qty:4);
        var fc04 = new CoalescingKey(UnitId: 1, Fc: 0x04, StartAddress: 100, Qty:4);

        fc03.ShouldNotBe(fc04, "FC03 and FC04 keys must never coalesce");
    }

    [Fact]
    public void Equality_OnDifferentUnitId_ReturnsFalse()
    {
        // Different unit IDs typically address different PLC personalities behind a shared
        // socket (multi-drop / gateway-backed setups). Never coalesce across them.
        var u1 = new CoalescingKey(UnitId: 1, Fc: 0x03, StartAddress: 100, Qty:4);
        var u2 = new CoalescingKey(UnitId: 2, Fc: 0x03, StartAddress: 100, Qty:4);

        u1.ShouldNotBe(u2, "different unit IDs must never coalesce");
    }

    [Fact]
    public void Equality_OnDifferentQty_ReturnsFalse()
    {
        var read1 = new CoalescingKey(UnitId: 1, Fc: 0x03, StartAddress: 100, Qty:1);
        var read4 = new CoalescingKey(UnitId: 1, Fc: 0x03, StartAddress: 100, Qty:4);

        read1.ShouldNotBe(read4, "different qty must not coalesce — response register count differs");
    }

    [Fact]
    public void HashCode_DistributionSanity()
    {
        // Build 10,000 keys at random V-memory-ish addresses and bucket the low byte of
        // GetHashCode. A reasonable hash should spread fairly evenly across 256 buckets.
        // Threshold: no single bucket holds > 5% of total (well above ideal 1/256 = 0.4%).
        const int Count = 10_000;
        var rng = new Random(17);
        var buckets = new int[256];

        for (int i = 0; i < Count; i++)
        {
            ushort start = (ushort)rng.Next(0, 4096);   // 12-bit V-memory space
            ushort qty   = (ushort)rng.Next(1, 128);
            byte unit    = (byte)rng.Next(0, 4);
            byte fc      = rng.Next(2) == 0 ? (byte)0x03 : (byte)0x04;

            int bucket = new CoalescingKey(unit, fc, start, qty).GetHashCode() & 0xFF;
            buckets[bucket]++;
        }

        int max = 0;
        for (int i = 0; i < buckets.Length; i++) if (buckets[i] > max) max = buckets[i];

        int ceiling = Count * 5 / 100;
        max.ShouldBeLessThanOrEqualTo(ceiling,
            $"hash distribution is uneven — the busiest bucket holds {max} > {ceiling} keys");
    }
}
