import json
import tempfile
import unittest
from pathlib import Path

from h8536.model import Instruction
from h8536.pseudocode import PseudocodeOptions, generate_pseudocode
from h8536.render import format_listing, write_json
from h8536.rom import Rom
from h8536.serial_reconstruction import analyze_serial_reconstruction


def ins(address: int, mnemonic: str, operands: str = "", references: list[int] | None = None) -> Instruction:
    return Instruction(address, b"", mnemonic, operands, references=references or [])


class SerialReconstructionIntegrationTest(unittest.TestCase):
    def test_listing_json_and_pseudocode_include_candidate_frame_reconstruction(self):
        instructions = {
            0x3000: ins(0x3000, "MOV:G.B", "@H'F858, R0", [0xF858]),
            0x3004: ins(0x3004, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
            0x3008: ins(0x3008, "MOV:G.B", "#1, @H'F9C2", [0xF9C2]),
            0x3010: ins(0x3010, "MOV:E.B", "#H'5A, R0", []),
            0x3014: ins(0x3014, "XOR.B", "@H'F858, R0", [0xF858]),
            0x3018: ins(0x3018, "XOR.B", "@H'F859, R0", [0xF859]),
            0x301C: ins(0x301C, "XOR.B", "@H'F85A, R0", [0xF85A]),
            0x3020: ins(0x3020, "XOR.B", "@H'F85B, R0", [0xF85B]),
            0x3024: ins(0x3024, "XOR.B", "@H'F85C, R0", [0xF85C]),
            0x3028: ins(0x3028, "MOV:G.B", "R0, @H'F85D", [0xF85D]),
            0x3030: ins(0x3030, "MOV:G.B", "@H'F9C2, R1", [0xF9C2]),
            0x3034: ins(0x3034, "MOV:G.B", "@(-H'07A8,R1), R0", []),
            0x3038: ins(0x3038, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
            0x303C: ins(0x303C, "ADD:Q.B", "#1, @H'F9C2", [0xF9C2]),
            0x3040: ins(0x3040, "CMP:G.B", "#6, @H'F9C2", [0xF9C2]),
            0xBEEA: ins(0xBEEA, "BCLR.B", "#5, @FRT1_TCSR", [0xFE91]),
            0xBEEE: ins(0xBEEE, "TST.B", "@H'F9C0", [0xF9C0]),
            0xBEF4: ins(0xBEF4, "ADD:Q.B", "#-1, @H'F9C0", [0xF9C0]),
            0xBEF8: ins(0xBEF8, "TST.B", "@H'F9C1", [0xF9C1]),
            0xBEFE: ins(0xBEFE, "ADD:Q.B", "#-1, @H'F9C1", [0xF9C1]),
            0xBF02: ins(0xBF02, "TST.W", "@H'F9C6", [0xF9C6]),
            0xBF08: ins(0xBF08, "ADD:Q.W", "#-1, @H'F9C6", [0xF9C6]),
        }
        analysis = analyze_serial_reconstruction(instructions)

        listing = format_listing(
            Path("rom.bin"),
            Rom(b"\xFF" * 0x20),
            instructions,
            {},
            {},
            "min",
            traced=False,
            serial_reconstruction=analysis,
        )
        self.assertIn("; Serial Protocol Reconstruction", listing)
        self.assertIn("TX candidate: 6 bytes", listing)
        self.assertIn("TX path: initial byte is written from the TX frame buffer", listing)
        self.assertIn("; Serial RAM role candidates", listing)
        self.assertIn("periodic_report_countdown", listing)
        self.assertIn("candidate/evidence-supported SCI1 6-byte TX frame", listing)
        self.assertIn("RAM role post_tx_report_delay", listing)

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

        self.assertEqual(payload["serial_reconstruction"]["candidates"][0]["frame_length"], 6)
        self.assertEqual(payload["serial_reconstruction"]["candidates"][0]["tx_path"]["kind"], "interrupt_driven_txi")
        self.assertEqual(payload["serial_reconstruction"]["ram_roles"][2]["name"], "periodic_report_countdown")
        tdr_instruction = next(item for item in payload["instructions"] if item["address"] == 0x3038)
        self.assertIn("serial_reconstruction", tdr_instruction)
        timer_instruction = next(item for item in payload["instructions"] if item["address"] == 0xBF08)
        self.assertEqual(timer_instruction["serial_reconstruction"][0]["role_name"], "periodic_report_countdown")

        pseudocode = generate_pseudocode(
            payload,
            options=PseudocodeOptions(include_addresses=False, include_asm=False, structured=False),
        )
        self.assertIn("candidate/evidence-supported SCI1 6-byte TX frame", pseudocode)
        self.assertIn("candidate/evidence-supported RAM role periodic_report_countdown", pseudocode)


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