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

from h8536.ccu_seed_hints import (
    analyze_ccu_seed_hints,
    checksum,
    encode_host_frame,
    frame_hex,
    main,
    selector_bytes,
    write_ccu_seed_hints,
)


def reference(address: int) -> dict:
    return {"address": address}


def instruction(address: int, mnemonic: str, operands: str = "", refs: list[int] | None = None) -> dict:
    return {
        "address": address,
        "mnemonic": mnemonic,
        "operands": operands,
        "text": f"{mnemonic} {operands}".strip(),
        "references": [reference(item) for item in (refs or [])],
        "targets": [],
    }


def payload() -> dict:
    return {
        "instructions": [
            instruction(0xC000, "MOV:G.W", "#H'0006, R3"),
            instruction(0xC004, "MOV:G.W", "@(-H'2000,R3), R0"),
            instruction(0xC008, "MOV:G.W", "R1, @H'E1EC", [0xE1EC]),
        ],
        "call_graph": {
            "nodes": [
                {"start": 0xC000, "end": 0xC0FF, "label": "loc_C000"},
            ],
        },
        "indirect_flow": {
            "sites": [
                {
                    "table": {
                        "base": 0x28A6,
                        "entries": [
                            {"index": 0, "entry_address": 0x28A6, "target": 0x2CB9, "target_label": "loc_2CB9"},
                            {"index": 1, "entry_address": 0x28A8, "target": 0x1234, "target_label": "loc_1234"},
                            {"index": 2, "entry_address": 0x28AA, "target": 0x1234, "target_label": "loc_1234"},
                        ],
                    },
                }
            ],
        },
    }


class CcuSeedHintsTest(unittest.TestCase):
    def test_selector_encoding_matches_loc_622b_ranges(self):
        self.assertEqual(selector_bytes(0x000), (0x00, 0x00))
        self.assertEqual(selector_bytes(0x07F), (0x00, 0x7F))
        self.assertEqual(selector_bytes(0x080), (0x01, 0x00))
        self.assertEqual(selector_bytes(0x17F), (0x01, 0xFF))
        self.assertEqual(selector_bytes(0x180), (0x02, 0x00))
        self.assertEqual(selector_bytes(0x1FF), (0x02, 0x7F))

    def test_frame_encoding_uses_xor_seed(self):
        frame = encode_host_frame(0x00, 0x000, 0x8080)

        self.assertEqual(frame, [0x00, 0x00, 0x00, 0x80, 0x80, 0x5A])
        self.assertEqual(checksum(frame[:5]), frame[5])
        self.assertEqual(frame_hex(frame), "00 00 00 80 80 5A")

    def test_analysis_emits_seed_plan_and_selector_reasons(self):
        analysis = analyze_ccu_seed_hints(payload(), rom_path=None)
        by_selector = {
            int(item["selector"]): item
            for item in analysis["selector_candidates"]
        }

        self.assertEqual(analysis["kind"], "ccu_seed_hints")
        self.assertIn(0x000, by_selector)
        self.assertIn(0x0F6, by_selector)
        self.assertIn("00 00 00 80 80 5A", [step["frame"] for step in analysis["seed_plan"]["steps"]])
        self.assertIn("01 01 76 00 00 2C", [step["readback_frame"] for step in analysis["seed_plan"]["steps"]])

    def test_write_json_output(self):
        with tempfile.TemporaryDirectory() as tmp:
            input_path = Path(tmp) / "rom.json"
            output_path = Path(tmp) / "hints.json"
            input_path.write_text(json.dumps(payload()), encoding="utf-8")

            write_ccu_seed_hints(input_path, output_path, rom_path=None, as_json=True)

            written = json.loads(output_path.read_text(encoding="utf-8"))
            self.assertEqual(written["kind"], "ccu_seed_hints")
            self.assertGreaterEqual(written["summary"]["candidate_count"], 1)

    def test_cli_writes_text_report(self):
        with tempfile.TemporaryDirectory() as tmp:
            input_path = Path(tmp) / "rom.json"
            output_path = Path(tmp) / "hints.txt"
            input_path.write_text(json.dumps(payload()), encoding="utf-8")

            stdout = io.StringIO()
            rc = main([str(input_path), "--rom", str(Path(tmp) / "missing.bin"), "--out", str(output_path)], stdout=stdout)

            self.assertEqual(rc, 0)
            self.assertIn("wrote", stdout.getvalue())
            self.assertIn("Candidate Fake-CCU Seed Plan", output_path.read_text(encoding="utf-8"))


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