import itertools
import unittest
from serial_order import compare, fold_newest, results


class SerialOrderTests(unittest.TestCase):
    def test_all_eight_bit_pairs_against_piecewise_definition(self):
        for a, b in itertools.product(range(256), repeat=2):
            if a == b:
                expected = "equal"
            elif abs(a - b) == 128:
                expected = "ambiguous"
            elif (a < b and b - a < 128) or (a > b and a - b > 128):
                expected = "older"
            else:
                expected = "newer"
            self.assertEqual(compare(a, b, 8), expected)

    def test_counts_and_reversal(self):
        result = results()["enumeration"]
        self.assertEqual(result["relations"],
                         {"ambiguous": 256, "equal": 256, "newer": 32512, "older": 32512})
        self.assertEqual(result["unsignedDisagreementsOnDefinedPairs"], 16256)
        reverse = {"older": "newer", "newer": "older", "equal": "equal", "ambiguous": "ambiguous"}
        for a, b in itertools.product(range(256), repeat=2):
            self.assertEqual(compare(a, b, 8), reverse[compare(b, a, 8)])

    def test_half_range_is_not_equality(self):
        self.assertEqual(compare(3, 2147483651), "ambiguous")
        self.assertEqual(compare(2147483651, 3), "ambiguous")
        self.assertNotEqual(3, 2147483651)

    def test_cycle_has_no_half_range_pair(self):
        for a, b in [(0, 100), (100, 200), (200, 0)]:
            self.assertEqual(compare(a, b, 8), "older")
        winners = {fold_newest(p) for p in itertools.permutations([0, 100, 200])}
        self.assertEqual(winners, {0, 100, 200})

    def test_transitive_inside_every_small_common_window(self):
        # Exhaust every origin and increasing triple within a 4-bit half-window.
        for origin in range(16):
            for x, y, z in itertools.combinations(range(8), 3):
                a, b, c = [(origin + offset) % 16 for offset in (x, y, z)]
                self.assertEqual(compare(a, b, 4), "older")
                self.assertEqual(compare(b, c, 4), "older")
                self.assertEqual(compare(a, c, 4), "older")
                for permutation in itertools.permutations((a, b, c)):
                    self.assertEqual(fold_newest(permutation, 4), c)

    def test_translation_invariance(self):
        for offset in range(256):
            for a, b in [(0, 1), (255, 3), (0, 128), (0, 200)]:
                self.assertEqual(compare(a, b, 8),
                                 compare((a + offset) % 256, (b + offset) % 256, 8))

    def test_32_bit_boundaries_and_64_bit_precision(self):
        self.assertEqual(compare(4294967294, 3), "older")
        self.assertEqual(compare(3, 2147483650), "older")
        self.assertEqual(compare(3, 2147483652), "newer")
        self.assertEqual(compare((1 << 64) - 1, 0, 64), "older")
        self.assertEqual(compare(1 << 63, 0, 64), "ambiguous")

    def test_invalid_inputs(self):
        for args in [(-1, 0), (0, 1 << 32), (True, 1), (0.0, 1),
                     (0, 1, 1), (0, 1, 65), (0, 1, True), ("1", 2)]:
            with self.assertRaises(ValueError):
                compare(*args)

    def test_fold_validates_empty_and_singleton_inputs(self):
        with self.assertRaisesRegex(ValueError, "at least one"):
            fold_newest([])
        for values, bits in [([True], 8), ([-1], 8), ([256], 8),
                             ([1.0], 8), ([1], True), ([1], 65)]:
            with self.assertRaises(ValueError):
                fold_newest(values, bits)
        self.assertEqual(fold_newest([255], 8), 255)
        self.assertEqual(fold_newest([255, 255], 8), 255)
        with self.assertRaisesRegex(ValueError, "half-range"):
            fold_newest([0, 128], 8)

    def test_small_delta_does_not_reveal_elapsed_history(self):
        self.assertEqual((254 + 5) % 256, 3)
        self.assertEqual((254 + 261) % 256, 3)
        self.assertEqual(compare(254, 3, 8), "older")
        # A chronological advance outside the contract can reverse the answer.
        self.assertEqual((3 + 251) % 256, 254)
        self.assertEqual(compare(3, 254, 8), "newer")

    def test_full_wrap_erases_identity(self):
        self.assertEqual(compare(42, (42 + 256) % 256, 8), "equal")
        self.assertLess((7 << 32) | 42, (8 << 32) | 42)


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