import io
import json
import tempfile
import unittest
from pathlib import Path

from h8536.rx_branch_trace import analyze_rx_branch_trace, format_text_report, main


def ins(address: int, text: str | None = None) -> dict[str, object]:
    return {
        "address": address,
        "text": text or f"INS_{address:04X}",
        "mnemonic": (text or "NOP").split()[0],
        "operands": "",
        "kind": "normal",
        "targets": [],
        "references": [],
    }


def fixture_payload() -> dict[str, object]:
    addresses = {
        0x3FEF, 0x3FF3, 0x3FF5, 0x3FF9, 0x3FFD, 0x4001, 0x4003, 0x4007,
        0xBB57, 0xBB5B, 0xBB5F, 0xBB63, 0xBB67, 0xBB6D, 0xBB71, 0xBB75,
        0xBB77, 0xBB7D, 0xBB82, 0xBB84, 0xBB88, 0xBB8A, 0xBB90, 0xBB96,
        0xBB9A, 0xBB9C, 0xBB9E, 0xBBA3, 0xBBAB, 0xBBB0, 0xBBB3, 0xBBCB,
        0xBBCF, 0xBBD3, 0xBBD6, 0xBBD8, 0xBBDC, 0xBBE0, 0xBBE4, 0xBBE8,
        0xBBEC, 0xBBF0, 0xBBF3, 0xBBF7, 0xBBFD, 0xBC01, 0xBC08, 0xBC0C,
        0xBC0F, 0xBC13, 0xBC15, 0xBC19, 0xBC1D, 0xBC20, 0xBC24, 0xBC29,
        0xBC2E, 0xBC33, 0xBC37, 0xBC3A, 0xBC3C, 0xBC3E, 0xBC42, 0xBC45,
        0xBC4A, 0xBC4F, 0xBC54, 0xBC5C, 0xBC60, 0xBC63, 0xBC67, 0xBC69,
        0xBC75, 0xBC79, 0xBC82, 0xBC86, 0xBCB0, 0xBCCD, 0xBCD0, 0xBCD7,
        0xBCE0, 0xBCE8, 0xBCEC, 0xBCF0, 0xBCF6, 0xBCFA, 0xBCFD, 0xBD04,
        0xBD08, 0xBD0B, 0xBD0E, 0xBD1A, 0xBD1E, 0xBD22, 0xBD26, 0xBD35,
        0xBD64, 0xBD67, 0xBD6D, 0xBD75, 0xBD79, 0xBD80, 0xBD85, 0xBD94,
        0xBD9A, 0xBDB5, 0xBDBF, 0xBDC2, 0xBDC8, 0xBDD0, 0xBDD4, 0xBDDB,
        0xBDE5, 0xBDE9, 0xBDED, 0xBDF3, 0xBDFB, 0xBDFF, 0xBE05, 0xBE09,
        0xBE0D, 0xBE11, 0xBE15, 0xBE19, 0xBE1D, 0xBE22, 0xBE27, 0xBE29,
        0xBE2D, 0xBE31, 0xBE33, 0xBE37, 0xBE3C, 0xBE3E, 0xBE43, 0xBE47,
        0xBE4D, 0xBE52, 0xBE5A, 0xBE62, 0xBE6A, 0xBE70, 0xBE78, 0xBE80,
        0xBE82, 0xBE84, 0xBE88, 0xBE91, 0xBE95, 0xBE99, 0xBE9D, 0xBE9E,
        0xBEA5, 0xBEA9, 0xBEAD, 0xBEAF, 0xBEB5, 0xBEBB, 0xBEBF, 0xBEC1,
        0xBEC5, 0xBECB, 0xBED1, 0xBED5, 0xBEE4,
    }
    return {"instructions": [ins(address) for address in sorted(addresses)]}


class RxBranchTraceTest(unittest.TestCase):
    def test_analyzes_dispatch_split_and_commands(self):
        analysis = analyze_rx_branch_trace(fixture_payload())

        self.assertEqual(analysis["summary"]["confidence"], "high")
        self.assertEqual(analysis["frame_model"]["checksum_seed"], 0x5A)
        self.assertTrue(analysis["stages"][2]["present"])
        self.assertIn("FAA2 != 0", analysis["stages"][2]["summary"])

        commands = {command["command"]: command for command in analysis["commands"]}
        self.assertIn("continuation path only", commands[0x04]["availability"])
        self.assertIn("selector zero is special", "\n".join(commands[0x04]["side_effects"]))
        self.assertIn("selectors 0x006C", "\n".join(commands[0x05]["side_effects"]))
        self.assertIn("previous finalized TX frame", commands[0x07]["summary"])

    def test_text_report_mentions_bench_implications(self):
        text = format_text_report(analyze_rx_branch_trace(fixture_payload()))

        self.assertIn("H8/536 SCI1 RX Branch Trace", text)
        self.assertIn("cmd 0x04 continuation_set_value_candidate", text)
        self.assertIn("standalone command 4 frame from idle should not hit BD0E", text)
        self.assertIn("Command 5 is not a generic always-live ACK", text)
        self.assertIn("Selector Decode", text)
        self.assertIn("TXI/RXI race and continuation collapse", text)
        self.assertIn("RX-to-TX Feedback Loops", text)

    def test_cli_writes_json_output(self):
        with tempfile.TemporaryDirectory() as tmp:
            input_path = Path(tmp) / "rom.json"
            output_path = Path(tmp) / "rx.json"
            input_path.write_text(json.dumps(fixture_payload()), encoding="utf-8")
            stdout = io.StringIO()

            rc = main(["--json", "--out", str(output_path), str(input_path)], stdout=stdout)

            self.assertEqual(rc, 0)
            self.assertIn("wrote", stdout.getvalue())
            payload = json.loads(output_path.read_text(encoding="utf-8"))
        self.assertEqual(payload["kind"], "rx_branch_trace")
        self.assertIn("downstream_traces", payload)
        self.assertIn("feedback_loops", payload)


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