"""Tests for rbsp.core.skiplist -- SkipList with O(log n) advance."""

from __future__ import annotations

import os
import random
import sys
import unittest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from rbsp.core.skiplist import SkipList


class TestSkipListAdd(unittest.TestCase):
    """Basic insertion semantics."""

    def setUp(self):
        random.seed(42)

    def test_add_single_element(self):
        sl = SkipList()
        sl.add(10)
        self.assertEqual(len(sl), 1)
        self.assertIn(10, sl)

    def test_add_multiple_elements(self):
        sl = SkipList()
        for v in [30, 10, 20]:
            sl.add(v)
        self.assertEqual(len(sl), 3)

    def test_add_duplicate_is_noop(self):
        sl = SkipList()
        sl.add(5)
        sl.add(5)
        sl.add(5)
        self.assertEqual(len(sl), 1)

    def test_add_preserves_sorted_order(self):
        sl = SkipList()
        for v in [50, 10, 30, 20, 40]:
            sl.add(v)
        self.assertEqual(list(sl), [10, 20, 30, 40, 50])


class TestSkipListContains(unittest.TestCase):
    """Membership test (__contains__)."""

    def setUp(self):
        random.seed(42)

    def test_contains_present(self):
        sl = SkipList()
        for v in [10, 20, 30]:
            sl.add(v)
        self.assertIn(10, sl)
        self.assertIn(20, sl)
        self.assertIn(30, sl)

    def test_not_contains_absent(self):
        sl = SkipList()
        sl.add(10)
        self.assertNotIn(5, sl)
        self.assertNotIn(15, sl)
        self.assertNotIn(100, sl)

    def test_empty_skiplist_contains_nothing(self):
        sl = SkipList()
        self.assertNotIn(0, sl)
        self.assertNotIn(1, sl)


class TestSkipListAdvanceTo(unittest.TestCase):
    """advance_to returns first value >= target."""

    def setUp(self):
        random.seed(42)
        self.sl = SkipList()
        for v in [10, 20, 30, 40, 50]:
            self.sl.add(v)

    def test_advance_to_exact(self):
        self.assertEqual(self.sl.advance_to(30), 30)

    def test_advance_to_between_values(self):
        self.assertEqual(self.sl.advance_to(25), 30)

    def test_advance_to_below_all(self):
        self.assertEqual(self.sl.advance_to(1), 10)

    def test_advance_to_above_all(self):
        self.assertIsNone(self.sl.advance_to(51))

    def test_advance_to_first_element(self):
        self.assertEqual(self.sl.advance_to(10), 10)

    def test_advance_to_last_element(self):
        self.assertEqual(self.sl.advance_to(50), 50)

    def test_advance_to_empty_list(self):
        sl = SkipList()
        self.assertIsNone(sl.advance_to(5))


class TestSkipListIter(unittest.TestCase):
    """Iteration yields sorted values."""

    def setUp(self):
        random.seed(42)

    def test_iter_sorted_order(self):
        sl = SkipList()
        for v in [30, 10, 50, 20, 40]:
            sl.add(v)
        self.assertEqual(list(sl), [10, 20, 30, 40, 50])

    def test_iter_empty(self):
        sl = SkipList()
        self.assertEqual(list(sl), [])

    def test_iter_single(self):
        sl = SkipList()
        sl.add(42)
        self.assertEqual(list(sl), [42])

    def test_iter_many(self):
        sl = SkipList()
        values = list(range(0, 100, 3))
        random.shuffle(values)
        for v in values:
            sl.add(v)
        self.assertEqual(list(sl), sorted(values))


class TestSkipListLen(unittest.TestCase):
    """__len__ tracks element count."""

    def setUp(self):
        random.seed(42)

    def test_len_empty(self):
        sl = SkipList()
        self.assertEqual(len(sl), 0)

    def test_len_after_adds(self):
        sl = SkipList()
        for v in range(10):
            sl.add(v)
        self.assertEqual(len(sl), 10)

    def test_len_with_duplicates(self):
        sl = SkipList()
        for v in [1, 2, 3, 1, 2, 3]:
            sl.add(v)
        self.assertEqual(len(sl), 3)


class TestSkipListIntersection(unittest.TestCase):
    """Static intersection of two skip lists."""

    def setUp(self):
        random.seed(42)

    def test_basic_intersection(self):
        a = SkipList()
        for v in [10, 20, 30, 40, 50]:
            a.add(v)
        b = SkipList()
        for v in [20, 40, 60, 80]:
            b.add(v)
        result = SkipList.intersection(a, b)
        self.assertEqual(list(result), [20, 40])

    def test_no_overlap(self):
        a = SkipList()
        for v in [1, 2, 3]:
            a.add(v)
        b = SkipList()
        for v in [4, 5, 6]:
            b.add(v)
        result = SkipList.intersection(a, b)
        self.assertEqual(list(result), [])

    def test_full_overlap(self):
        a = SkipList()
        b = SkipList()
        for v in [10, 20, 30]:
            a.add(v)
            b.add(v)
        result = SkipList.intersection(a, b)
        self.assertEqual(list(result), [10, 20, 30])

    def test_empty_intersection(self):
        a = SkipList()
        b = SkipList()
        result = SkipList.intersection(a, b)
        self.assertEqual(list(result), [])
        self.assertEqual(len(result), 0)

    def test_one_empty(self):
        a = SkipList()
        for v in [10, 20]:
            a.add(v)
        b = SkipList()
        result = SkipList.intersection(a, b)
        self.assertEqual(list(result), [])

    def test_commutative(self):
        a = SkipList()
        for v in [10, 20, 30, 40]:
            a.add(v)
        b = SkipList()
        for v in [20, 30, 50]:
            b.add(v)
        r1 = list(SkipList.intersection(a, b))
        r2 = list(SkipList.intersection(b, a))
        self.assertEqual(r1, r2)


class TestSkipListUnion(unittest.TestCase):
    """Static union of two skip lists."""

    def setUp(self):
        random.seed(42)

    def test_basic_union(self):
        a = SkipList()
        for v in [10, 30, 50]:
            a.add(v)
        b = SkipList()
        for v in [20, 40, 60]:
            b.add(v)
        result = SkipList.union(a, b)
        self.assertEqual(list(result), [10, 20, 30, 40, 50, 60])

    def test_overlapping_union(self):
        a = SkipList()
        for v in [10, 20, 30]:
            a.add(v)
        b = SkipList()
        for v in [20, 30, 40]:
            b.add(v)
        result = SkipList.union(a, b)
        self.assertEqual(list(result), [10, 20, 30, 40])

    def test_identical_union(self):
        a = SkipList()
        b = SkipList()
        for v in [1, 2, 3]:
            a.add(v)
            b.add(v)
        result = SkipList.union(a, b)
        self.assertEqual(list(result), [1, 2, 3])

    def test_empty_union(self):
        a = SkipList()
        b = SkipList()
        result = SkipList.union(a, b)
        self.assertEqual(list(result), [])

    def test_one_empty_union(self):
        a = SkipList()
        for v in [10, 20, 30]:
            a.add(v)
        b = SkipList()
        result = SkipList.union(a, b)
        self.assertEqual(list(result), [10, 20, 30])

    def test_commutative(self):
        a = SkipList()
        for v in [10, 20]:
            a.add(v)
        b = SkipList()
        for v in [20, 30]:
            b.add(v)
        r1 = list(SkipList.union(a, b))
        r2 = list(SkipList.union(b, a))
        self.assertEqual(r1, r2)


class TestSkipListRepr(unittest.TestCase):
    """__repr__ produces a useful string."""

    def setUp(self):
        random.seed(42)

    def test_repr_short_list(self):
        sl = SkipList()
        for v in [10, 20, 30]:
            sl.add(v)
        r = repr(sl)
        self.assertIn("SkipList", r)
        self.assertIn("len=3", r)

    def test_repr_long_list_truncates(self):
        sl = SkipList()
        for v in range(20):
            sl.add(v)
        r = repr(sl)
        self.assertIn("...", r)

    def test_repr_empty(self):
        sl = SkipList()
        r = repr(sl)
        self.assertIn("SkipList", r)
        self.assertIn("len=0", r)


class TestSkipListLargeDataset(unittest.TestCase):
    """Stress test with larger datasets."""

    def setUp(self):
        random.seed(42)

    def test_large_insertion_and_lookup(self):
        sl = SkipList()
        values = list(range(0, 1000, 2))
        random.shuffle(values)
        for v in values:
            sl.add(v)
        self.assertEqual(len(sl), 500)
        # All even values should be present
        for v in range(0, 1000, 2):
            self.assertIn(v, sl)
        # All odd values should be absent
        for v in range(1, 100, 2):
            self.assertNotIn(v, sl)

    def test_advance_to_stress(self):
        sl = SkipList()
        for v in range(0, 100, 5):
            sl.add(v)
        # advance_to 7 should give 10
        self.assertEqual(sl.advance_to(7), 10)
        # advance_to 0 should give 0
        self.assertEqual(sl.advance_to(0), 0)
        # advance_to 100 should give None (100 is not in range)
        self.assertIsNone(sl.advance_to(100))
        # advance_to 95 should give 95
        self.assertEqual(sl.advance_to(95), 95)


if __name__ == "__main__":
    unittest.main()
