import io
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from h8536.emulator.state_search import (
    SearchCase,
    SearchResult,
    StatePatch,
    build_cases,
    classify_display,
    main,
    parse_address,
    parse_matrix_patch,
    parse_single_patch,
    target_matches,
)


class EmulatorStateSearchTest(unittest.TestCase):
    def test_parse_patch_accepts_hex_address_and_value(self):
        patch = parse_single_patch("F730=0x41", size=1)

        self.assertEqual(patch, StatePatch(1, 0xF730, 0x41, "user"))
        self.assertEqual(patch.label(), "byte:H'F730=0x41")

    def test_parse_matrix_patch_expands_values(self):
        patches = parse_matrix_patch("E000=0x4080,0x8080", size=2)

        self.assertEqual(patches, [
            StatePatch(2, 0xE000, 0x4080, "user"),
            StatePatch(2, 0xE000, 0x8080, "user"),
        ])

    def test_parse_address_accepts_h_quote(self):
        self.assertEqual(parse_address("H'F970"), 0xF970)

    def test_connect_queue_preset_builds_small_rom_driven_matrix(self):
        parser = __import__("h8536.emulator.state_search", fromlist=["build_arg_parser"]).build_arg_parser()
        args = parser.parse_args(["--preset", "connect-queue"])

        cases = build_cases(args)

        self.assertEqual(len(cases), 25)
        first = cases[0]
        self.assertEqual(first.pc, 0x2806)
        self.assertIn(StatePatch(2, 0xF970, 0x0000, "preset"), first.patches)
        self.assertIn(StatePatch(2, 0xE000, 0x0000, "preset"), first.patches)

    def test_custom_matrix_combines_fixed_and_matrix_patches(self):
        parser = __import__("h8536.emulator.state_search", fromlist=["build_arg_parser"]).build_arg_parser()
        args = parser.parse_args([
            "--preset",
            "custom",
            "--pc",
            "0x2CB9",
            "--byte",
            "F730=0",
            "--matrix-word",
            "E000=0x4080,0x8080",
        ])

        cases = build_cases(args)

        self.assertEqual(len(cases), 2)
        self.assertEqual(cases[0], SearchCase((StatePatch(1, 0xF730, 0, "user"), StatePatch(2, 0xE000, 0x4080, "user")), 0x2CB9))

    def test_classify_display(self):
        self.assertEqual(classify_display(" CONNECT:  OK    | "), "ok")
        self.assertEqual(classify_display(" CONNECT:DXC-637 | "), "dxc")
        self.assertEqual(classify_display(" CONNECT:NOT ACT | "), "not-act")

    def test_target_matching(self):
        self.assertTrue(target_matches("ok", "ok"))
        self.assertTrue(target_matches("dxc", "any-connect"))
        self.assertTrue(target_matches("ok", "changed"))
        self.assertFalse(target_matches("not-act", "changed"))

    def test_cli_dry_run_lists_cases(self):
        stdout = io.StringIO()

        rc = main(["--dry-run", "--preset", "connect-branch", "--limit", "2"], stdout=stdout)

        self.assertEqual(rc, 0)
        output = stdout.getvalue()
        self.assertIn("preset=connect-branch cases=2", output)
        self.assertIn("case[0] pc=H'2CB9", output)

    def test_cli_json_output_uses_results_from_run_search(self):
        fake_result = SearchResult(
            case_index=0,
            patches=(StatePatch(2, 0xE000, 0x8080, "preset"),),
            pc=0x2CB9,
            steps=10,
            stopped_reason="stop_pc",
            final_pc=0xFFFF,
            display=" CONNECT:  OK    | ",
            line0=" CONNECT:  OK   ",
            outcome="ok",
            f730=0x81,
            e000=0x8080,
            f9b4=0,
            f9b9=0,
        )
        with tempfile.TemporaryDirectory() as tmpdir:
            path = Path(tmpdir) / "out.json"
            stdout = io.StringIO()
            with patch("h8536.emulator.state_search.run_search", return_value=[fake_result]):
                rc = main(["--preset", "custom", "--word", "E000=0x8080", "--json-out", str(path)], stdout=stdout)
            payload = json.loads(path.read_text(encoding="utf-8"))

        self.assertEqual(rc, 0)
        self.assertIn("hits=1", stdout.getvalue())
        self.assertEqual(payload["hits"][0]["outcome"], "ok")


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