import unittest
from collections import Counter

from h8536.emulator.memory import MemoryAccess
from h8536.emulator.rx_divergence import (
    DivergenceContext,
    STATE_BUFFERS,
    bench_signature_line,
    classify_hits,
    format_interesting_writes,
    format_pc_hits,
    format_state_changes,
    label_frame,
    parse_frame,
    write_label,
)


class EmulatorRxDivergenceTest(unittest.TestCase):
    def test_reuses_probe_frame_parser_and_labels_bench_signature(self):
        frame = parse_frame("04 00 00 80 80")

        self.assertEqual(frame, bytes.fromhex("04000080805E"))
        self.assertIn("bench_cmd0_echo_80", label_frame(frame))
        self.assertIn("bench_connect_ok", bench_signature_line())

    def test_pc_hit_summary_makes_dispatch_outcome_visible(self):
        hits = Counter({0xBC69: 1, 0xBA26: 2})

        summary = classify_hits(hits)

        self.assertTrue(summary["cmd0_reached_BC69"])
        self.assertFalse(summary["cmd1_reached_BCD7"])
        self.assertFalse(summary["retry_echo"])
        self.assertFalse(summary["cmd7_replay"])

    def test_formats_watched_pc_hits_in_trace_order(self):
        context = DivergenceContext()
        for pc in (0xBC0F, 0xBC69, 0xBC69):
            context.record_pc(pc)

        lines = format_pc_hits(context)

        self.assertIn("H'BC0F:faa2_split=1", lines)
        self.assertIn("H'BC69:cmd0_set_value=2", lines)
        self.assertIn("first=H'BC0F,H'BC69,H'BC69", lines)

    def test_filters_required_write_addresses(self):
        accesses = [
            MemoryAccess(0xF860, 1, 0x12, "write", "on_chip_ram"),
            MemoryAccess(0xE001, 1, 0x34, "write", "external"),
            MemoryAccess(0xE002, 1, 0x56, "write", "external"),
            MemoryAccess(0xFEDD, 1, 0x78, "write", "register"),
        ]

        lines = format_interesting_writes(accesses)

        self.assertEqual(len(lines), 2)
        self.assertIn("rx_validation_F860_F865", lines[0])
        self.assertIn("primary_table_E000", lines[1])
        self.assertEqual(write_label(0xFAA6), "serial_latches_FAA2_FAA6")
        self.assertIsNone(write_label(0xE002))

    def test_state_changes_formats_bytes_and_ints_compactly(self):
        lines = format_state_changes(
            {"FAA2_session_flags": 0, "F860_F865_rx_validation": b"\x00" * 6},
            {"FAA2_session_flags": 0x80, "F860_F865_rx_validation": bytes.fromhex("010203040506")},
        )

        self.assertIn("FAA2_session_flags:00->80", lines)
        self.assertIn("F860_F865_rx_validation:00 00 00 00 00 00->01 02 03 04 05 06", lines)

    def test_snapshots_visible_40a0_candidate_table_slots(self):
        self.assertEqual(STATE_BUFFERS["E880_E881_current_0040"], (0xE880, 2))
        self.assertEqual(STATE_BUFFERS["E900_E901_current_0080"], (0xE900, 2))
        self.assertEqual(STATE_BUFFERS["E980_E981_current_00C0"], (0xE980, 2))


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