"""Tests for rbsp.core.encoding -- binary wire format and NaturalSignature."""

import os
import struct
import sys
import unittest

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

from rbsp.core.encoding import (
    MAGIC,
    SIGNATURE_SIZE,
    VERSION,
    FLAG_HAS_SEMANTIC,
    FLAG_HAS_STRUCTURAL,
    NaturalSignature,
    _compute_checksum,
    compute_size_class,
)


class TestNaturalSignatureRoundTrip(RBSPTestCase):
    """Verify binary encode/decode round-trips preserve all fields."""

    def test_round_trip_basic(self):
        sig = NaturalSignature(
            content_hash=0xDEADBEEF12345678,
            semantic_hash=0xCAFEBABE87654321,
            structural_hash=0x1111222233334444,
            timestamp=FIXED_TIMESTAMP,
            size_class=42,
            flags=FLAG_HAS_SEMANTIC | FLAG_HAS_STRUCTURAL,
        )
        raw = sig.to_bytes()
        self.assertEqual(len(raw), SIGNATURE_SIZE)

        restored = NaturalSignature.from_bytes(raw)
        self.assertEqual(restored.content_hash, sig.content_hash)
        self.assertEqual(restored.semantic_hash, sig.semantic_hash)
        self.assertEqual(restored.structural_hash, sig.structural_hash)
        self.assertAlmostEqual(restored.timestamp, sig.timestamp, places=3)
        self.assertEqual(restored.size_class, sig.size_class)
        self.assertEqual(restored.flags, sig.flags)

    def test_round_trip_zeros(self):
        sig = NaturalSignature(content_hash=0, timestamp=0.0)
        restored = NaturalSignature.from_bytes(sig.to_bytes())
        self.assertEqual(restored.content_hash, 0)
        self.assertEqual(restored.semantic_hash, 0)
        self.assertEqual(restored.structural_hash, 0)

    def test_round_trip_max_values(self):
        sig = NaturalSignature(
            content_hash=0xFFFFFFFFFFFFFFFF,
            semantic_hash=0xFFFFFFFFFFFFFFFF,
            structural_hash=0xFFFFFFFFFFFFFFFF,
            timestamp=FIXED_TIMESTAMP,
            size_class=0xFFFF,
            flags=0xFF,
        )
        restored = NaturalSignature.from_bytes(sig.to_bytes())
        self.assertEqual(restored.content_hash, 0xFFFFFFFFFFFFFFFF)
        self.assertEqual(restored.size_class, 0xFFFF)
        self.assertEqual(restored.flags, 0xFF)

    def test_round_trip_all_sample_sigs(self):
        for i, sig in enumerate(self.sample_sigs):
            with self.subTest(i=i):
                restored = NaturalSignature.from_bytes(sig.to_bytes())
                self.assertEqual(restored.content_hash, sig.content_hash)
                self.assertEqual(restored.semantic_hash, sig.semantic_hash)
                self.assertEqual(restored.structural_hash, sig.structural_hash)

    def test_hex_round_trip(self):
        sig = NaturalSignature(
            content_hash=0xABCD1234,
            timestamp=FIXED_TIMESTAMP,
        )
        hex_str = sig.hex()
        self.assertIsInstance(hex_str, str)
        self.assertEqual(len(hex_str), SIGNATURE_SIZE * 2)
        restored = NaturalSignature.from_hex(hex_str)
        self.assertEqual(restored.content_hash, sig.content_hash)


class TestMagicAndChecksum(RBSPTestCase):
    """Verify magic number validation and checksum integrity detection."""

    def test_magic_bytes_present(self):
        sig = NaturalSignature(content_hash=1, timestamp=FIXED_TIMESTAMP)
        raw = sig.to_bytes()
        magic_val = struct.unpack(">I", raw[:4])[0]
        self.assertEqual(magic_val, MAGIC)

    def test_bad_magic_rejected(self):
        sig = NaturalSignature(content_hash=1, timestamp=FIXED_TIMESTAMP)
        raw = bytearray(sig.to_bytes())
        raw[0] = 0x00  # corrupt magic
        with self.assertRaises(ValueError) as ctx:
            NaturalSignature.from_bytes(bytes(raw))
        self.assertIn("Bad magic", str(ctx.exception))

    def test_checksum_detects_corruption(self):
        sig = NaturalSignature(content_hash=1, timestamp=FIXED_TIMESTAMP)
        raw = bytearray(sig.to_bytes())
        # Corrupt a byte in the payload (not magic, not checksum)
        raw[10] ^= 0xFF
        with self.assertRaises(ValueError) as ctx:
            NaturalSignature.from_bytes(bytes(raw))
        self.assertIn("Checksum mismatch", str(ctx.exception))

    def test_truncated_data_rejected(self):
        sig = NaturalSignature(content_hash=1, timestamp=FIXED_TIMESTAMP)
        raw = sig.to_bytes()
        with self.assertRaises(ValueError):
            NaturalSignature.from_bytes(raw[:32])

    def test_version_field(self):
        sig = NaturalSignature(content_hash=1, timestamp=FIXED_TIMESTAMP)
        raw = sig.to_bytes()
        version = raw[4]
        self.assertEqual(version, VERSION)

    def test_checksum_deterministic(self):
        data = b"test data for checksum"
        c1 = _compute_checksum(data)
        c2 = _compute_checksum(data)
        self.assertEqual(c1, c2)

    def test_checksum_differs_for_different_data(self):
        c1 = _compute_checksum(b"aaa")
        c2 = _compute_checksum(b"aab")
        self.assertNotEqual(c1, c2)


class TestComputeSizeClass(unittest.TestCase):
    """Test the logarithmic size-class bucketing."""

    def test_zero_length(self):
        self.assertEqual(compute_size_class(0), 0)

    def test_negative_length(self):
        self.assertEqual(compute_size_class(-1), 0)

    def test_one_byte(self):
        self.assertEqual(compute_size_class(1), 1)

    def test_powers_of_two(self):
        self.assertEqual(compute_size_class(2), 2)
        self.assertEqual(compute_size_class(4), 3)
        self.assertEqual(compute_size_class(1024), 11)
        self.assertEqual(compute_size_class(1048576), 21)  # 1 MiB

    def test_monotonic(self):
        prev = 0
        for size in [1, 10, 100, 1000, 10000, 100000, 1000000]:
            sc = compute_size_class(size)
            self.assertGreaterEqual(sc, prev)
            prev = sc

    def test_max_clamp(self):
        huge = 2 ** 100000
        self.assertEqual(compute_size_class(huge), 0xFFFF)


class TestNaturalSignatureImmutability(unittest.TestCase):
    """Verify frozen dataclass semantics."""

    def test_cannot_set_attribute(self):
        sig = NaturalSignature(content_hash=1, timestamp=FIXED_TIMESTAMP)
        with self.assertRaises(AttributeError):
            sig.content_hash = 2

    def test_repr_contains_hashes(self):
        sig = NaturalSignature(content_hash=0xAB, timestamp=FIXED_TIMESTAMP)
        r = repr(sig)
        self.assertIn("NaturalSignature", r)
        self.assertIn("00AB", r)


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