"""Tests for rbsp.indexer.postings -- PostingList with compressed storage."""

import os
import sys
import time
import unittest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from rbsp.indexer.postings import PostingList, PostingEntry


class TestPostingListEmpty(unittest.TestCase):
    def test_empty_posting_list(self):
        pl = PostingList("test")
        self.assertEqual(len(pl), 0)
        self.assertEqual(list(pl), [])


class TestPostingListAddAndIterate(unittest.TestCase):
    def test_add_and_iterate(self):
        pl = PostingList("test")
        pl.add(5, term_freq=1)
        pl.add(10, term_freq=2)
        pl.add(20, term_freq=3)

        entries = list(pl)
        self.assertEqual(len(pl), 3)
        doc_ids = [e.doc_id for e in entries]
        self.assertEqual(doc_ids, [5, 10, 20])

    def test_add_preserves_term_freq(self):
        pl = PostingList("test")
        pl.add(7, term_freq=42)
        entries = list(pl)
        self.assertEqual(entries[0].term_freq, 42)


class TestPostingListAdvanceTo(unittest.TestCase):
    def setUp(self):
        self.pl = PostingList("test")
        for doc_id in [2, 5, 8, 13, 21, 34, 55]:
            self.pl.add(doc_id, term_freq=1)

    def test_advance_to_exact(self):
        entry = self.pl.advance_to(8)
        self.assertIsNotNone(entry)
        self.assertEqual(entry.doc_id, 8)

    def test_advance_to_skip(self):
        entry = self.pl.advance_to(10)
        self.assertIsNotNone(entry)
        self.assertEqual(entry.doc_id, 13)

    def test_advance_to_past_end(self):
        entry = self.pl.advance_to(100)
        self.assertIsNone(entry)

    def test_advance_to_first(self):
        entry = self.pl.advance_to(1)
        self.assertIsNotNone(entry)
        self.assertEqual(entry.doc_id, 2)

    def test_advance_to_last(self):
        entry = self.pl.advance_to(55)
        self.assertIsNotNone(entry)
        self.assertEqual(entry.doc_id, 55)


class TestPostingListSerialize(unittest.TestCase):
    def test_serialize_deserialize(self):
        pl = PostingList("test")
        pl.add(3, term_freq=5)
        pl.add(50, term_freq=10)
        pl.add(100, term_freq=1)

        data = pl.to_bytes()
        self.assertIsInstance(data, (bytes, bytearray))

        restored = PostingList.from_bytes(data, "test")
        self.assertEqual(len(restored), len(pl))

        original_entries = list(pl)
        restored_entries = list(restored)
        for orig, rest in zip(original_entries, restored_entries):
            self.assertEqual(orig.doc_id, rest.doc_id)
            self.assertEqual(orig.term_freq, rest.term_freq)

    def test_serialize_empty(self):
        pl = PostingList("test")
        data = pl.to_bytes()
        restored = PostingList.from_bytes(data, "test")
        self.assertEqual(len(restored), 0)


class TestPostingListIntersect(unittest.TestCase):
    def test_intersect_basic(self):
        a = PostingList("t1")
        for doc_id in [1, 3, 5, 7, 9]:
            a.add(doc_id, term_freq=1)
        b = PostingList("t2")
        for doc_id in [2, 3, 5, 8]:
            b.add(doc_id, term_freq=1)

        result = PostingList.intersect(a, b)
        self.assertEqual(sorted(set(result)), [3, 5])

    def test_intersect_no_overlap(self):
        a = PostingList("t1")
        for doc_id in [1, 3, 5]:
            a.add(doc_id, term_freq=1)
        b = PostingList("t2")
        for doc_id in [2, 4, 6]:
            b.add(doc_id, term_freq=1)

        result = PostingList.intersect(a, b)
        self.assertEqual(result, [])

    def test_intersect_identical(self):
        a = PostingList("t1")
        for doc_id in [10, 20, 30]:
            a.add(doc_id, term_freq=1)

        result = PostingList.intersect(a, a)
        self.assertEqual(sorted(result), [10, 20, 30])

    def test_intersect_with_empty(self):
        a = PostingList("t1")
        for doc_id in [1, 2, 3]:
            a.add(doc_id, term_freq=1)
        empty = PostingList("t2")

        result = PostingList.intersect(a, empty)
        self.assertEqual(result, [])


class TestPostingListUnion(unittest.TestCase):
    def test_union_basic(self):
        a = PostingList("t1")
        for doc_id in [1, 3, 5]:
            a.add(doc_id, term_freq=1)
        b = PostingList("t2")
        for doc_id in [2, 4, 6]:
            b.add(doc_id, term_freq=1)

        result = PostingList.union(a, b)
        self.assertEqual(sorted(result), [1, 2, 3, 4, 5, 6])

    def test_union_with_overlap(self):
        a = PostingList("t1")
        for doc_id in [1, 3, 5]:
            a.add(doc_id, term_freq=2)
        b = PostingList("t2")
        for doc_id in [3, 5, 7]:
            b.add(doc_id, term_freq=3)

        result = PostingList.union(a, b)
        self.assertEqual(sorted(set(result)), [1, 3, 5, 7])

    def test_union_with_empty(self):
        a = PostingList("t1")
        for doc_id in [10, 20]:
            a.add(doc_id, term_freq=1)
        empty = PostingList("t2")

        result = PostingList.union(a, empty)
        self.assertEqual(sorted(result), [10, 20])

    def test_union_both_empty(self):
        result = PostingList.union(PostingList("t1"), PostingList("t2"))
        self.assertEqual(result, [])


class TestPostingListLarge(unittest.TestCase):
    def test_large_posting_list(self):
        pl = PostingList("test")
        n = 10000
        for i in range(n):
            pl.add(i * 3, term_freq=1)

        self.assertEqual(len(pl), n)

        entries = list(pl)
        self.assertEqual(len(entries), n)
        for i, entry in enumerate(entries):
            self.assertEqual(entry.doc_id, i * 3)

        # advance_to near the end
        entry = pl.advance_to(29001)
        self.assertIsNotNone(entry)
        self.assertEqual(entry.doc_id, 29001)

        # Round-trip serialize
        data = pl.to_bytes()
        restored = PostingList.from_bytes(data, "test")
        self.assertEqual(len(restored), n)


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