import json
import tempfile
import unittest
from pathlib import Path

from h8536.model import Instruction
from h8536.render import format_listing, write_json
from h8536.rom import Rom
from h8536.sci import analyze_sci, sci_comment_for_instruction


def sci1_setup(scr: int = 0x3C) -> dict[int, Instruction]:
    return {
        0x1000: Instruction(
            0x1000,
            bytes.fromhex("15FED80624"),
            "MOV:G.B",
            "#H'24, @SCI1_SMR",
            references=[0xFED8],
            comment="SCI1_SMR = H'24",
        ),
        0x1005: Instruction(
            0x1005,
            bytes([0x15, 0xFE, 0xDA, 0x06, scr]),
            "MOV:G.B",
            f"#H'{scr:02X}, @SCI1_SCR",
            references=[0xFEDA],
            comment=f"SCI1_SCR = H'{scr:02X}",
        ),
        0x100A: Instruction(
            0x100A,
            bytes.fromhex("15FED90607"),
            "MOV:G.B",
            "#H'07, @SCI1_BRR",
            references=[0xFED9],
            comment="SCI1_BRR = H'07",
        ),
    }


class SciInferenceTest(unittest.TestCase):
    def test_async_internal_baud_uses_manual_brr_formula(self):
        analysis = analyze_sci(sci1_setup(), clock_hz=16_000_000)
        config = analysis["channels"]["SCI1"]["configurations"][0]

        self.assertEqual(config["mode"], "async")
        self.assertEqual(config["cks_n"], 0)
        self.assertEqual(config["brr"], 7)
        self.assertEqual(config["baud_bps"], 31_250)
        self.assertEqual(config["formula"], "B = clock_hz / (64 * 2^(2n) * (N + 1))")
        self.assertIn("SCI1 async 8-bit even parity 1 stop baud 31250 bps", config["comment"])

    def test_missing_clock_keeps_baud_partial(self):
        analysis = analyze_sci(sci1_setup(), clock_hz=None)
        comment = sci_comment_for_instruction(analysis, 0x100A)
        config = analysis["channels"]["SCI1"]["configurations"][0]

        self.assertIsNone(config["baud_bps"])
        self.assertEqual(config["reason"], "clock_hz_missing")
        self.assertIn("baud needs --clock-hz", comment)
        self.assertNotIn("31250 bps", comment)

    def test_external_clock_selection_suppresses_internal_baud(self):
        analysis = analyze_sci(sci1_setup(scr=0x3E), clock_hz=16_000_000)
        config = analysis["channels"]["SCI1"]["configurations"][0]

        self.assertIsNone(config["baud_bps"])
        self.assertEqual(config["clock_source"], "external")
        self.assertEqual(config["reason"], "external_clock_selected")
        self.assertIn("external clock selected", config["comment"])

    def test_scr_bit_writes_are_tracked_without_repeating_same_baud(self):
        instructions = sci1_setup()
        instructions[0x1010] = Instruction(
            0x1010,
            bytes.fromhex("15FEDAC7"),
            "BSET.B",
            "#7, @SCI1_SCR",
            references=[0xFEDA],
            comment="set TIE (bit 7) of SCI1_SCR",
        )

        analysis = analyze_sci(instructions, clock_hz=16_000_000)
        writes = analysis["channels"]["SCI1"]["writes"]

        self.assertEqual([write["register"] for write in writes], ["SMR", "SCR", "BRR", "SCR"])
        self.assertEqual(writes[-1]["value"], 0xBC)
        self.assertNotIn(0x1010, analysis["annotations"])

    def test_listing_preserves_existing_comment_and_appends_sci_comment(self):
        instructions = sci1_setup()
        analysis = analyze_sci(instructions, clock_hz=16_000_000)

        listing = format_listing(
            Path("rom.bin"),
            Rom(b"\xFF" * 0x20),
            instructions,
            {},
            {},
            "min",
            traced=False,
            sci_analysis=analysis,
        )

        self.assertIn("SCI1_BRR = H'07; SCI1 async 8-bit even parity 1 stop baud 31250 bps", listing)

    def test_json_includes_top_level_and_instruction_sci_metadata(self):
        instructions = sci1_setup()
        analysis = analyze_sci(instructions, clock_hz=16_000_000)

        with tempfile.TemporaryDirectory() as tmp:
            path = Path(tmp) / "out.json"
            write_json(path, instructions, {}, {}, sci_analysis=analysis)
            payload = json.loads(path.read_text(encoding="utf-8"))

        self.assertEqual(payload["sci"]["channels"]["SCI1"]["configurations"][0]["baud_bps"], 31_250)
        brr_instruction = next(item for item in payload["instructions"] if item["address"] == 0x100A)
        self.assertEqual(brr_instruction["sci"]["inferences"][0]["brr"], 7)


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