"""Tests for rbsp.core.compress -- Compression utilities."""

import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(__file__))
from conftest import RBSPTestCase

from rbsp.core.compress import (
    compress,
    decompress,
    compress_string,
    decompress_string,
    CompressedStore,
    BatchCompressor,
)


# ---------------------------------------------------------------------------
# compress / decompress
# ---------------------------------------------------------------------------

class TestCompressDecompress(unittest.TestCase):
    """Round-trip tests for low-level compress/decompress."""

    def test_round_trip_basic(self):
        data = b"hello world"
        self.assertEqual(decompress(compress(data)), data)

    def test_round_trip_empty(self):
        self.assertEqual(decompress(compress(b"")), b"")

    def test_round_trip_large(self):
        data = os.urandom(100_000)
        self.assertEqual(decompress(compress(data)), data)

    def test_round_trip_repetitive(self):
        """Repetitive data should compress well."""
        data = b"aaaa" * 10_000
        compressed = compress(data)
        self.assertLess(len(compressed), len(data))
        self.assertEqual(decompress(compressed), data)

    def test_compress_level_zero(self):
        """Level 0 stores without compression but still round-trips."""
        data = b"no compression"
        self.assertEqual(decompress(compress(data, level=0)), data)

    def test_compress_level_nine(self):
        data = b"x" * 1_000
        self.assertEqual(decompress(compress(data, level=9)), data)

    def test_decompress_too_short(self):
        with self.assertRaises(ValueError):
            decompress(b"abc")

    def test_decompress_bad_magic(self):
        with self.assertRaises(ValueError):
            decompress(b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"payload")

    def test_compressed_has_header(self):
        """Compressed output starts with the RBC magic."""
        data = compress(b"test")
        self.assertTrue(data.startswith(b"RBC\x01"))


# ---------------------------------------------------------------------------
# compress_string / decompress_string
# ---------------------------------------------------------------------------

class TestStringCompression(unittest.TestCase):
    """Round-trip tests for string convenience wrappers."""

    def test_round_trip_ascii(self):
        text = "hello world"
        self.assertEqual(decompress_string(compress_string(text)), text)

    def test_round_trip_unicode(self):
        text = "Unicode: \u00e9\u00e8\u00ea \u2603 \U0001f600"
        self.assertEqual(decompress_string(compress_string(text)), text)

    def test_round_trip_empty(self):
        self.assertEqual(decompress_string(compress_string("")), "")

    def test_round_trip_multiline(self):
        text = "line 1\nline 2\nline 3\n"
        self.assertEqual(decompress_string(compress_string(text)), text)


# ---------------------------------------------------------------------------
# CompressedStore
# ---------------------------------------------------------------------------

class TestCompressedStore(unittest.TestCase):
    """Tests for the CompressedStore key-value store."""

    def setUp(self):
        self.store = CompressedStore(min_size=32)

    def test_put_get_small(self):
        """Values below min_size are stored raw."""
        value = b"tiny"
        self.store.put("k", value)
        self.assertEqual(self.store.get("k"), value)

    def test_put_get_large(self):
        """Values above min_size are compressed then decompressed on get."""
        value = b"x" * 200
        self.store.put("k", value)
        self.assertEqual(self.store.get("k"), value)

    def test_get_missing_returns_none(self):
        self.assertIsNone(self.store.get("nope"))

    def test_keys_empty(self):
        self.assertEqual(self.store.keys(), [])

    def test_keys_returns_all(self):
        self.store.put("a", b"1")
        self.store.put("b", b"2")
        self.assertCountEqual(self.store.keys(), ["a", "b"])

    def test_overwrite(self):
        self.store.put("k", b"old")
        self.store.put("k", b"new")
        self.assertEqual(self.store.get("k"), b"new")

    def test_size_bytes(self):
        self.assertEqual(self.store.size_bytes(), 0)
        self.store.put("k", b"data")
        self.assertGreater(self.store.size_bytes(), 0)

    def test_compression_ratio_empty(self):
        self.assertEqual(self.store.compression_ratio(), 1.0)

    def test_compression_ratio_with_data(self):
        """Repetitive data should yield a ratio < 1."""
        self.store.put("big", b"a" * 1_000)
        ratio = self.store.compression_ratio()
        self.assertGreater(ratio, 0.0)
        self.assertLess(ratio, 1.0)

    def test_compression_ratio_small_data(self):
        """Small data stored raw has ratio == 1."""
        self.store.put("s", b"hi")
        self.assertEqual(self.store.compression_ratio(), 1.0)

    def test_large_compressed_smaller_than_original(self):
        """Verify that compressed storage saves space for repetitive data."""
        value = b"hello " * 500
        self.store.put("rep", value)
        self.assertLess(self.store.size_bytes(), len(value))


# ---------------------------------------------------------------------------
# BatchCompressor
# ---------------------------------------------------------------------------

class TestBatchCompressor(unittest.TestCase):
    """Tests for BatchCompressor bulk operations."""

    def test_round_trip_basic(self):
        items = [b"one", b"two", b"three"]
        blob = BatchCompressor.compress_batch(items)
        self.assertEqual(BatchCompressor.decompress_batch(blob), items)

    def test_round_trip_empty_list(self):
        blob = BatchCompressor.compress_batch([])
        self.assertEqual(BatchCompressor.decompress_batch(blob), [])

    def test_round_trip_single_item(self):
        items = [b"only"]
        blob = BatchCompressor.compress_batch(items)
        self.assertEqual(BatchCompressor.decompress_batch(blob), items)

    def test_round_trip_empty_items(self):
        """Items can be empty byte strings."""
        items = [b"", b"", b"data", b""]
        blob = BatchCompressor.compress_batch(items)
        self.assertEqual(BatchCompressor.decompress_batch(blob), items)

    def test_round_trip_binary(self):
        items = [os.urandom(100) for _ in range(10)]
        blob = BatchCompressor.compress_batch(items)
        self.assertEqual(BatchCompressor.decompress_batch(blob), items)

    def test_batch_is_single_blob(self):
        """The output should be a single bytes object, not a list."""
        blob = BatchCompressor.compress_batch([b"a", b"b"])
        self.assertIsInstance(blob, bytes)

    def test_batch_compressed_smaller(self):
        """Repetitive items should compress well as a batch."""
        items = [b"repetitive content " * 50 for _ in range(20)]
        blob = BatchCompressor.compress_batch(items)
        total_raw = sum(len(i) for i in items)
        self.assertLess(len(blob), total_raw)

    def test_decompress_batch_invalid(self):
        """Corrupted blob should raise."""
        with self.assertRaises(ValueError):
            BatchCompressor.decompress_batch(b"not valid")


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