#!/usr/bin/env python3
"""Re-run host tests, the isolated STM32G030 link, static gates, and hashes."""

from __future__ import annotations

import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile


ROOT = Path(__file__).resolve().parent
FW = ROOT / "固件"
OUT = FW / "vacuum_target" / "build"


def run(label: str, command: list[str], cwd: Path = ROOT) -> str:
    result = subprocess.run(command, cwd=cwd, text=True, capture_output=True)
    if result.returncode:
        raise RuntimeError(
            f"{label} failed ({result.returncode})\n"
            f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
        )
    return (result.stdout + result.stderr).strip()


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as source:
        for block in iter(lambda: source.read(1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()


def host_test(label: str, sources: list[Path], include_dirs: list[Path]) -> str:
    clang = shutil.which("clang")
    if clang is None:
        raise RuntimeError("clang is required for host verification")
    with tempfile.TemporaryDirectory(prefix="vacuum-vrefint-") as tmp:
        binary = Path(tmp) / f"{label}-test"
        command = [
            clang,
            "-std=c11",
            "-Wall",
            "-Wextra",
            "-Werror",
            "-pedantic",
            "-fsanitize=address,undefined",
            "-g",
        ]
        for include_dir in include_dirs:
            command.extend(["-I", str(include_dir)])
        command.extend(str(path) for path in sources)
        command.extend(["-o", str(binary)])
        run(f"{label} compile", command)
        return run(label, [str(binary)])


def main() -> None:
    telemetry = FW / "vacuum_target" / "src" / "vacuum_telemetry.c"
    tests = FW / "vacuum_target" / "test"
    control = FW / "vacuum_control"
    target = FW / "vacuum_target"

    host_results = {
        "telemetry": host_test(
            "telemetry",
            [telemetry, tests / "test_vacuum_telemetry.c"],
            [target / "src"],
        ),
        "fan_policy": host_test(
            "fan-policy",
            [control / "fan_manager.c", control / "test_fan_manager.c"],
            [control],
        ),
        "hardware_output_lock": host_test(
            "output-lock",
            [target / "src" / "vacuum_output_lock.c", tests / "test_vacuum_output_lock.c"],
            [target / "src"],
        ),
    }

    build_output = run("STM32 target build", ["sh", str(target / "build.sh")])
    static = json.loads((OUT / "build_verification.json").read_text(encoding="utf-8"))
    if static.get("status") != "LINKED_IMAGE_STATIC_CHECK_PASS":
        raise RuntimeError(f"unexpected target image status: {static.get('status')}")
    if static.get("fan_power_output_enabled") is not False:
        raise RuntimeError("fan power output is not statically confirmed off")
    if static.get("physical_hardware_validation") is not False:
        raise RuntimeError("physical validation must remain unclaimed")

    nm = shutil.which("arm-none-eabi-nm")
    if nm is None:
        raise RuntimeError("arm-none-eabi-nm is required for linked-symbol verification")
    symbols = run("target symbol check", [nm, str(OUT / "vacuum_control_g030f6.elf")])
    if "vacuum_vdda_from_vrefint" not in symbols:
        raise RuntimeError("VREFINT-derived VDDA conversion is absent from linked firmware")

    board = (target / "src" / "vacuum_board.c").read_text(encoding="utf-8")
    if "ADC1_COMMON->CCR |= ADC_CCR_VREFEN;" not in board:
        raise RuntimeError("VREFINT ADC path is not enabled")
    if "read_adc(VREFINT_ADC_CHANNEL" not in board:
        raise RuntimeError("VREFINT ADC conversion is not in the sample path")
    if "ADC1->SMPR = ADC_SMPR_SMP1;" not in board:
        raise RuntimeError("long SMP1 sample time is not selected")
    if "0x7FFFFu << ADC_SMPR_SMPSEL_Pos" in board:
        raise RuntimeError("old SMP2 selection regression detected")

    tracked = [
        ROOT / "README.md",
        ROOT / "verify_candidate.py",
        target / "src" / "vacuum_board.c",
        target / "src" / "vacuum_board.h",
        target / "src" / "vacuum_telemetry.c",
        target / "src" / "vacuum_telemetry.h",
        tests / "test_vacuum_telemetry.c",
        control / "fan_manager.c",
        control / "fan_manager.h",
        control / "test_fan_manager.c",
        OUT / "build_verification.json",
        OUT / "vacuum_control_g030f6.bin",
        OUT / "vacuum_control_g030f6.elf",
        OUT / "vacuum_control_g030f6.map",
    ]
    hashes = {str(path.relative_to(ROOT)): sha256(path) for path in tracked}
    result = {
        "status": "HOST_TESTS_AND_TARGET_LINK_PASS_PHYSICAL_NOT_RUN",
        "target": "STM32G030F6P6 / Cortex-M0+",
        "host_tests": host_results,
        "target_static_check": static,
        "vrefint_source_path_enabled": True,
        "linked_vdda_calibration_symbol": True,
        "adc_long_sample_time_checked": True,
        "fan_hardware_output_locked_off": True,
        "physical_hardware_validation": False,
        "product_function_complete": False,
        "target_build_console": build_output,
        "sha256": hashes,
    }
    report = ROOT / "verification.json"
    report.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    checksum_text = "".join(f"{digest}  {name}\n" for name, digest in hashes.items())
    (ROOT / "SHA256SUMS.txt").write_text(checksum_text, encoding="utf-8")
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
