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

from h8536.state_map_runner import (
    CONNECT_FORCE_PRESETS,
    StateMapEvent,
    analyze_events,
    format_analysis_report,
    main,
    parse_bench_log,
)


class StateMapRunnerTest(unittest.TestCase):
    def test_force_presets_match_expected_selector_zero_words(self):
        self.assertEqual(CONNECT_FORCE_PRESETS["dxc"][0], bytes.fromhex("04000040001E"))
        self.assertEqual(CONNECT_FORCE_PRESETS["dxc"][1], 0x4080)
        self.assertEqual(CONNECT_FORCE_PRESETS["ok"][0], bytes.fromhex("0400008000DE"))
        self.assertEqual(CONNECT_FORCE_PRESETS["ok"][1], 0x8080)
        self.assertEqual(CONNECT_FORCE_PRESETS["both"][0], bytes.fromhex("040000C0009E"))
        self.assertEqual(CONNECT_FORCE_PRESETS["both"][1], 0xC080)

    def test_analyzes_successful_selector_zero_readback(self):
        events = [
            StateMapEvent("rx", bytes.fromhex("0780C060205D"), timestamp_ms=1000),
            StateMapEvent("tx", bytes.fromhex("0400008000DE"), timestamp_ms=1006),
            StateMapEvent("tx", bytes.fromhex("01000000005B"), timestamp_ms=1060),
            StateMapEvent("rx", bytes.fromhex("04001280804C"), timestamp_ms=1070),
        ]

        analysis = analyze_events(events, expected_word=0x8080)

        self.assertEqual(analysis["outcome"]["name"], "selector_zero_retained")
        self.assertEqual(analysis["direct_readbacks"][0]["value"], 0x8080)
        self.assertTrue(analysis["direct_readbacks"][0]["matches_expected"])

    def test_warns_when_command1_readback_is_between_trigger_and_force(self):
        events = [
            StateMapEvent("rx", bytes.fromhex("07804040A07D"), timestamp_ms=1000),
            StateMapEvent("tx", bytes.fromhex("01000000005B"), timestamp_ms=1005),
            StateMapEvent("tx", bytes.fromhex("04000040001E"), timestamp_ms=1010),
        ]

        analysis = analyze_events(events, expected_word=0x4080)

        self.assertIn("command1_readback_between_trigger_and_force_can_spend_token", analysis["warnings"])
        self.assertEqual(analysis["outcome"]["name"], "force_not_proven")

    def test_parse_bench_log_uses_detect_lines_for_rx_and_tx_chunks_for_host_frames(self):
        log = (
            "00:00:01.000  RX 006 bytes  07 80 C0 60 20 5D\n"
            "00:00:01.000  DETECT visible_C0_6020_family_candidate 07 80 C0 60 20 5D\n"
            "00:00:01.006  TX 006 bytes  04 00 00 80 00 DE\n"
        )

        events = parse_bench_log(log)

        self.assertEqual([(event.direction, event.frame.hex()) for event in events], [
            ("rx", "0780c060205d"),
            ("tx", "0400008000de"),
        ])

    def test_format_report_mentions_outcome_and_readback_value(self):
        analysis = analyze_events(
            [
                StateMapEvent("rx", bytes.fromhex("0780C060205D"), timestamp_ms=1000),
                StateMapEvent("tx", bytes.fromhex("0400008000DE"), timestamp_ms=1006),
                StateMapEvent("rx", bytes.fromhex("04001280804C"), timestamp_ms=1070),
            ],
            expected_word=0x8080,
        )

        report = format_analysis_report(analysis)

        self.assertIn("outcome=selector_zero_retained", report)
        self.assertIn("value=0x8080 expected", report)

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

        rc = main(["--dry-run", "--preset", "dxc", "--prime-frame", "01 80 40 40 30 EB"], stdout=stdout)

        output = stdout.getvalue()
        self.assertEqual(rc, 0)
        self.assertIn("PT2 state-map proof runner", output)
        self.assertIn("force=04 00 00 40 00 1E", output)
        self.assertIn("expected_e0000=0x4080", output)
        self.assertIn("prime=01 80 40 40 30 EB", output)

    def test_cli_analyze_log_writes_json(self):
        log = (
            "00:00:01.000  DETECT visible_C0_6020_family_candidate 07 80 C0 60 20 5D\n"
            "00:00:01.006  TX 006 bytes  04 00 00 80 00 DE\n"
            "00:00:01.070  DETECT table_readback_candidate 04 00 12 80 80 4C\n"
        )
        with tempfile.TemporaryDirectory() as tmpdir:
            log_path = Path(tmpdir) / "capture.txt"
            json_path = Path(tmpdir) / "analysis.json"
            log_path.write_text(log, encoding="utf-8")
            stdout = io.StringIO()

            rc = main(["--analyze-log", str(log_path), "--preset", "ok", "--json-out", str(json_path)], stdout=stdout)

            payload = json.loads(json_path.read_text(encoding="utf-8"))
        self.assertEqual(rc, 0)
        self.assertIn("outcome=selector_zero_retained", stdout.getvalue())
        self.assertEqual(payload["outcome"]["name"], "selector_zero_retained")


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