using System;
using System.Collections.Generic;
using System.Diagnostics;
using Shouldly;
using ZB.MOM.NatsNet.Server.Internal.DataStructures;

namespace ZB.MOM.NatsNet.Server.Tests.Internal.DataStructures;

public sealed class SequenceSetTests
{
    private static readonly int NumEntries = SequenceSet.NumEntries; // 2048

    // --- Basic operations ---

    [Fact]
    public void SeqSetBasics_ShouldSucceed()
    {
        var ss = new SequenceSet();
        var seqs = new ulong[] { 22, 222, 2000, 2, 2, 4 };
        foreach (var seq in seqs)
        {
            ss.Insert(seq);
            ss.Exists(seq).ShouldBeTrue();
        }

        ss.Nodes.ShouldBe(1);
        ss.Size.ShouldBe(seqs.Length - 1); // one duplicate
        var (lh, rh) = ss.Heights();
        lh.ShouldBe(0);
        rh.ShouldBe(0);
    }

    [Fact]
    public void SeqSetLeftLean_ShouldSucceed()
    {
        var ss = new SequenceSet();
        for (var i = (ulong)(4 * NumEntries); i > 0; i--)
            ss.Insert(i);

        ss.Nodes.ShouldBe(5);
        ss.Size.ShouldBe(4 * NumEntries);
        var (lh, rh) = ss.Heights();
        lh.ShouldBe(2);
        rh.ShouldBe(1);
    }

    [Fact]
    public void SeqSetRightLean_ShouldSucceed()
    {
        var ss = new SequenceSet();
        for (var i = 0UL; i < (ulong)(4 * NumEntries); i++)
            ss.Insert(i);

        ss.Nodes.ShouldBe(4);
        ss.Size.ShouldBe(4 * NumEntries);
        var (lh, rh) = ss.Heights();
        lh.ShouldBe(1);
        rh.ShouldBe(2);
    }

    [Fact]
    public void SeqSetCorrectness_ShouldSucceed()
    {
        var num = 100_000;
        var maxVal = 500_000;
        var rng = new Random(42);

        var reference = new HashSet<ulong>(num);
        var ss = new SequenceSet();

        for (var i = 0; i < num; i++)
        {
            var n = (ulong)rng.NextInt64(maxVal + 1);
            ss.Insert(n);
            reference.Add(n);
        }

        for (var i = 0UL; i <= (ulong)maxVal; i++)
            ss.Exists(i).ShouldBe(reference.Contains(i));
    }

    [Fact]
    public void SeqSetRange_ShouldSucceed()
    {
        var num = 2 * NumEntries + 22;
        var nums = new List<ulong>(num);
        for (var i = 0; i < num; i++)
            nums.Add((ulong)i);

        var rng = new Random(42);
        for (var i = nums.Count - 1; i > 0; i--)
        {
            var j = rng.Next(i + 1);
            (nums[i], nums[j]) = (nums[j], nums[i]);
        }

        var ss = new SequenceSet();
        foreach (var n in nums)
            ss.Insert(n);

        var collected = new List<ulong>();
        ss.Range(n => { collected.Add(n); return true; });
        collected.Count.ShouldBe(num);
        for (var i = 0; i < num; i++)
            collected[i].ShouldBe((ulong)i);

        // Test early termination.
        collected.Clear();
        ss.Range(n =>
        {
            if (n >= 10) return false;
            collected.Add(n);
            return true;
        });
        collected.Count.ShouldBe(10);
        for (var i = 0UL; i < 10; i++)
            collected[(int)i].ShouldBe(i);
    }

    [Fact]
    public void SeqSetDelete_ShouldSucceed()
    {
        var ss = new SequenceSet();
        var seqs = new ulong[] { 22, 222, 2222, 2, 2, 4 };
        foreach (var seq in seqs)
            ss.Insert(seq);

        foreach (var seq in seqs)
        {
            ss.Delete(seq);
            ss.Exists(seq).ShouldBeFalse();
        }

        ss.Root.ShouldBeNull();
    }

    [Fact]
    public void SeqSetInsertAndDeletePedantic_ShouldSucceed()
    {
        var ss = new SequenceSet();
        var num = 50 * NumEntries + 22;
        var nums = new List<ulong>(num);
        for (var i = 0; i < num; i++)
            nums.Add((ulong)i);

        var rng = new Random(42);
        for (var i = nums.Count - 1; i > 0; i--)
        {
            var j = rng.Next(i + 1);
            (nums[i], nums[j]) = (nums[j], nums[i]);
        }

        void AssertBalanced()
        {
            SequenceSet.Node.NodeIter(ss.Root, n =>
            {
                if (n != null && n.Height != (SequenceSet.Node.BalanceFactor(n) == int.MinValue ? 0 : 0))
                {
                    // Height check: verify height equals max child height + 1
                    var expectedH = 1 + Math.Max(n.Left?.Height ?? 0, n.Right?.Height ?? 0);
                    n.Height.ShouldBe(expectedH);
                }
            });

            var bf = SequenceSet.Node.BalanceFactor(ss.Root);
            (bf is >= -1 and <= 1).ShouldBeTrue();
        }

        foreach (var n in nums)
        {
            ss.Insert(n);
            AssertBalanced();
        }
        ss.Root.ShouldNotBeNull();

        foreach (var n in nums)
        {
            ss.Delete(n);
            AssertBalanced();
            ss.Exists(n).ShouldBeFalse();
            if (ss.Size > 0)
                ss.Root.ShouldNotBeNull();
        }
        ss.Root.ShouldBeNull();
    }

    [Fact]
    public void SeqSetMinMax_ShouldSucceed()
    {
        var ss = new SequenceSet();
        var seqs = new ulong[] { 22, 222, 2222, 2, 2, 4 };
        foreach (var seq in seqs)
            ss.Insert(seq);

        var (min, max) = ss.MinMax();
        min.ShouldBe(2UL);
        max.ShouldBe(2222UL);

        ss.Empty();

        var num = 22 * NumEntries + 22;
        var nums = new List<ulong>(num);
        for (var i = 0; i < num; i++)
            nums.Add((ulong)i);

        var rng = new Random(42);
        for (var i = nums.Count - 1; i > 0; i--)
        {
            var j = rng.Next(i + 1);
            (nums[i], nums[j]) = (nums[j], nums[i]);
        }
        foreach (var n in nums)
            ss.Insert(n);

        (min, max) = ss.MinMax();
        min.ShouldBe(0UL);
        max.ShouldBe((ulong)(num - 1));
    }

    [Fact]
    public void SeqSetClone_ShouldSucceed()
    {
        var num = 100_000;
        var maxVal = 500_000;
        var rng = new Random(42);

        var ss = new SequenceSet();
        for (var i = 0; i < num; i++)
            ss.Insert((ulong)rng.NextInt64(maxVal + 1));

        var ssc = ss.Clone();
        ssc.Size.ShouldBe(ss.Size);
        ssc.Nodes.ShouldBe(ss.Nodes);
    }

    [Fact]
    public void SeqSetUnion_ShouldSucceed()
    {
        var ss1 = new SequenceSet();
        var seqs1 = new ulong[] { 22, 222, 2222, 2, 2, 4 };
        foreach (var seq in seqs1) ss1.Insert(seq);

        var ss2 = new SequenceSet();
        var seqs2 = new ulong[] { 33, 333, 3333, 3, 33_333, 333_333 };
        foreach (var seq in seqs2) ss2.Insert(seq);

        var ss = SequenceSet.UnionSets(ss1, ss2);
        ss.ShouldNotBeNull();
        ss!.Size.ShouldBe(11);

        foreach (var n in seqs1) ss.Exists(n).ShouldBeTrue();
        foreach (var n in seqs2) ss.Exists(n).ShouldBeTrue();
    }

    [Fact]
    public void SeqSetFirst_ShouldSucceed()
    {
        var seqs = new ulong[] { 22, 222, 2222, 222_222 };
        foreach (var seq in seqs)
        {
            var ss = new SequenceSet();
            ss.Insert(seq);
            ss.Root.ShouldNotBeNull();
            ss.Root!.Base.ShouldBe((seq / (ulong)NumEntries) * (ulong)NumEntries);

            ss.Empty();
            ss.SetInitialMin(seq);
            ss.Insert(seq);
            ss.Root.ShouldNotBeNull();
            ss.Root!.Base.ShouldBe(seq);
        }
    }

    [Fact]
    public void SeqSetDistinctUnion_ShouldSucceed()
    {
        var ss1 = new SequenceSet();
        var seqs1 = new ulong[] { 1, 10, 100, 200 };
        foreach (var seq in seqs1) ss1.Insert(seq);

        var ss2 = new SequenceSet();
        var seqs2 = new ulong[] { 5000, 6100, 6200, 6222 };
        foreach (var seq in seqs2) ss2.Insert(seq);

        var ss = ss1.Clone();
        ss.Union(ss2);

        var allSeqs = new List<ulong>(seqs1);
        allSeqs.AddRange(seqs2);

        ss.Size.ShouldBe(allSeqs.Count);
        foreach (var seq in allSeqs)
            ss.Exists(seq).ShouldBeTrue();
    }

    [Fact]
    public void SeqSetDecodeV1_ShouldSucceed()
    {
        var seqs = new ulong[] { 22, 222, 2222, 222_222, 2_222_222 };
        var encStr = @"FgEDAAAABQAAAABgAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAADgIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA==";

        var enc = Convert.FromBase64String(encStr);
        var (ss, _) = SequenceSet.Decode(enc);

        ss.Size.ShouldBe(seqs.Length);
        foreach (var seq in seqs)
            ss.Exists(seq).ShouldBeTrue();
    }

    // --- Encode/Decode round-trip ---

    [Fact]
    public void SeqSetEncodeDecode_RoundTrip_ShouldSucceed()
    {
        var num = 2_500_000;
        var maxVal = 5_000_000;
        var rng = new Random(42);

        var reference = new HashSet<ulong>(num);
        var ss = new SequenceSet();
        for (var i = 0; i < num; i++)
        {
            var n = (ulong)rng.NextInt64(maxVal + 1);
            ss.Insert(n);
            reference.Add(n);
        }

        var buf = ss.Encode(null);
        var (ss2, _) = SequenceSet.Decode(buf);

        ss2.Nodes.ShouldBe(ss.Nodes);
        ss2.Size.ShouldBe(ss.Size);
    }

    // --- Performance / scale tests (no strict timing assertions) ---

    [Fact]
    public void NoRaceSeqSetSizeComparison_ShouldSucceed()
    {
        // Insert 5M items; verify correctness (memory comparison is GC-managed, skip strict thresholds)
        var num = 5_000_000;
        var maxVal = 7_000_000;
        var rng = new Random(42);

        var ss = new SequenceSet();
        var reference = new HashSet<ulong>(num);
        for (var i = 0; i < num; i++)
        {
            var n = (ulong)rng.NextInt64(maxVal + 1);
            ss.Insert(n);
            reference.Add(n);
        }

        ss.Size.ShouldBe(reference.Count);
    }

    [Fact]
    public void NoRaceSeqSetEncodeLarge_ShouldSucceed()
    {
        var num = 2_500_000;
        var maxVal = 5_000_000;
        var rng = new Random(42);

        var ss = new SequenceSet();
        for (var i = 0; i < num; i++)
            ss.Insert((ulong)rng.NextInt64(maxVal + 1));

        var buf = ss.Encode(null);
        var (ss2, _) = SequenceSet.Decode(buf);

        ss2.Nodes.ShouldBe(ss.Nodes);
        ss2.Size.ShouldBe(ss.Size);
    }

    [Fact]
    public void NoRaceSeqSetRelativeSpeed_ShouldSucceed()
    {
        // Correctness: all inserted items must be findable.
        // Timing assertions are omitted — performance is validated via BenchmarkDotNet benchmarks.
        var num = 1_000_000;
        var maxVal = 3_000_000;
        var rng = new Random(42);

        var seqs = new ulong[num];
        for (var i = 0; i < num; i++)
            seqs[i] = (ulong)rng.NextInt64(maxVal + 1);

        var ss = new SequenceSet();
        foreach (var n in seqs) ss.Insert(n);
        foreach (var n in seqs) ss.Exists(n).ShouldBeTrue();
    }
}
