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

from h8536.eeprom_layout import (
    analyze_eeprom_layout,
    format_text_report,
    main,
    write_eeprom_layout,
)


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(0x410C, "MOV:G.W", "R5, @(-H'0C00,R0)"),
            instruction(0x41E8, "MOV:G.W", "R5, @(-H'0850,R1)"),
            instruction(0x40FA, "CMP:G.W", "#H'6B6F, @H'F402", [0xF402]),
            instruction(0xBD3D, "MOV:G.B", "@(-H'3A9B,R4), R1"),
            instruction(0xBD45, "MOV:G.W", "R0, @(-H'0C00,R1)"),
            instruction(0xBD49, "BTST.B", "#7, @H'F76E", [0xF76E]),
        ],
        "call_graph": {
            "nodes": [
                {"start": 0x4000, "end": 0x4216, "label": "loc_4000"},
                {"start": 0xBD0E, "end": 0xBD67, "label": "loc_BD0E"},
            ],
        },
    }


def rom_bytes() -> bytes:
    rom = bytearray([0xFF] * 0xCB00)
    rom[0xC564 : 0xC564 + 0x400] = b"\x00" * 0x400
    rom[0xC564 + 0x015 * 2 : 0xC564 + 0x015 * 2 + 2] = b"\x40\xAA"
    rom[0xC966:0xC968] = b"\x6B\x6F"
    rom[0xC968:0xC96A] = b"\xFE\x00"
    rom[0xCA0E:0xCA10] = b"\x80\x00"
    return bytes(rom)


class EepromLayoutTest(unittest.TestCase):
    def test_analysis_extracts_factory_defaults_records_and_selector_mapping(self):
        with tempfile.TemporaryDirectory() as tmp:
            rom_path = Path(tmp) / "rom.bin"
            rom_path.write_bytes(rom_bytes())

            analysis = analyze_eeprom_layout(payload(), rom_path=rom_path)

        self.assertEqual(analysis["kind"], "eeprom_layout")
        self.assertEqual(analysis["factory_defaults"]["entries"][1]["factory_word_hex"], "0x6B6F")
        self.assertEqual(analysis["persistent_records"][3]["ram_base_hex"], "H'F7C8")
        self.assertEqual(analysis["persistent_records"][3]["eeprom_base_hex"], "0x300")
        mapped = analysis["serial_persistence_mapping"]["entries"][0]
        self.assertEqual(mapped["selector_hex"], "0x015")
        self.assertEqual(mapped["shadow_address_hex"], "H'F4AA")
        self.assertEqual(mapped["eeprom_word_offset_hex"], "0xAA")
        self.assertEqual(mapped["factory_word_hex"], "0x8000")

    def test_text_report_mentions_core_layout(self):
        with tempfile.TemporaryDirectory() as tmp:
            rom_path = Path(tmp) / "rom.bin"
            rom_path.write_bytes(rom_bytes())
            analysis = analyze_eeprom_layout(payload(), rom_path=rom_path)

        text = format_text_report(analysis)

        self.assertIn("Persistent 8-Byte Records", text)
        self.assertIn("selector 0x015", text)
        self.assertIn("F76E", text)

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

            write_eeprom_layout(input_path, output_path, rom_path=rom_path, as_json=True)

            written = json.loads(output_path.read_text(encoding="utf-8"))
            self.assertEqual(written["kind"], "eeprom_layout")
            self.assertEqual(written["summary"]["persistent_record_count"], 16)

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

            stdout = io.StringIO()
            rc = main([str(input_path), "--rom", str(rom_path), "--out", str(output_path)], stdout=stdout)

            self.assertEqual(rc, 0)
            self.assertIn("wrote", stdout.getvalue())
            self.assertIn("EEPROM Layout Report", output_path.read_text(encoding="utf-8"))


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