import unittest

from tools.rtsp_test_time import SenderClock, NTP_UNIX_OFFSET, UINT32


def ntp(seconds):
    return (NTP_UNIX_OFFSET + seconds) << 32


class SenderClockTests(unittest.TestCase):
    def test_zero_is_valid_and_unobserved_is_null(self):
        clock = SenderClock("run1")
        self.assertIsNone(clock.snapshot()["latestRtpTimestamp"])
        self.assertFalse(clock.sender_report(7, ntp(1700000000), 0))
        clock.packet(0, 90000, 7)
        self.assertEqual(clock.snapshot()["latestRtpTimestamp"], 0)
        self.assertTrue(clock.sender_report(7, ntp(1700000000), 0))
        self.assertEqual(clock.anchor["frame_time_anchor"]["startWallTimeMs"], 1700000000000)

    def test_pair_not_process_start_and_pinned_within_epoch(self):
        clock = SenderClock("run1")
        clock.packet(900000, 90000, 7)
        clock.sender_report(7, ntp(1700000000), 900000)
        self.assertEqual(clock.anchor["frame_time_anchor"]["startWallTimeMs"], 1699999990000)
        original = clock.snapshot()
        clock.sender_report(7, ntp(1700000001), 990000)
        self.assertEqual(clock.anchor, original["anchor"])
        original["anchor"]["revision"] = 99
        self.assertEqual(clock.anchor["revision"], 0)

    def test_wrap_without_zero_needs_new_pair(self):
        clock = SenderClock("run1")
        clock.packet(UINT32 - 4000, 90000, 7)
        clock.sender_report(7, ntp(1700000000), UINT32 - 4000)
        clock.packet(5000, 90000, 7)
        self.assertIsNone(clock.anchor)
        self.assertEqual(clock.reason, "TIMESTAMP_DECREASE_UNCLASSIFIED")
        self.assertFalse(clock.sender_report(7, ntp(1700000000), UINT32 - 4000))
        self.assertTrue(clock.sender_report(7, ntp(1700000001), 95000))
        self.assertEqual(clock.anchor["revision"], 1)

    def test_reset_and_reorder_not_called_wrap(self):
        clock = SenderClock("run1")
        clock.packet(900000, 90000, 7)
        clock.sender_report(7, ntp(1700000000), 900000)
        clock.packet(891000, 90000, 7)
        self.assertIsNone(clock.anchor)
        self.assertEqual(clock.reason, "TIMESTAMP_DECREASE_UNCLASSIFIED")
        clock.packet(0, 90000, 8)
        self.assertEqual(clock.reason, "SOURCE_OR_CLOCK_CHANGED")
        self.assertFalse(clock.sender_report(7, ntp(1700000000), 0))
        self.assertTrue(clock.sender_report(8, ntp(1700000001), 0))
        self.assertEqual(clock.anchor["ssrc"], 8)

    def test_rate_change_invalidates_and_bad_fields_rejected(self):
        clock = SenderClock("run1")
        clock.packet(0, 90000, 7)
        clock.sender_report(7, ntp(1700000000), 0)
        clock.packet(100, 48000, 7)
        self.assertIsNone(clock.anchor)
        for values in [(True,90000,7),(-1,90000,7),(UINT32,90000,7),(0,0,7)]:
            with self.assertRaises(ValueError): clock.packet(*values)
        self.assertFalse(clock.sender_report(7, 0, 100))

    def test_same_frame_packets_do_not_create_new_frames(self):
        clock = SenderClock("run1")
        for _ in range(3): clock.packet(0,90000,7)
        clock.packet(9000,90000,7)
        self.assertEqual(clock.packet_count,4)
        self.assertEqual(clock.timestamp_count,2)


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