#!/usr/bin/env python3
"""Production Wi-Fi binding checks (static, source-level).

Complements `wifi_usb_binding_test.cpp` (behavioural) and the ownership tests.
These catch the exact production bugs Codex found on hardware that fake-driven
host tests cannot see:

  B1  `UsbWiFiOps.context` must be a real, stable object address in production.
      A `nullptr` (or a sentinel cast) makes every USB verb refuse itself while
      fakes still pass.
  B5  `esp_wifi_set_config` / `esp_wifi_connect` return values must be checked,
      and a candidate must be rejected when its return code is not ESP_OK.

The checks are intentionally narrow: they assert the production call shape, not
a particular implementation.
"""
from __future__ import annotations

import pathlib
import re
import sys

REPO = pathlib.Path(__file__).resolve().parents[2]
WIFI_MANAGER = REPO / "firmware/main/wifi_manager.cpp"


def strip_comments_and_strings(text: str) -> str:
    text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
    text = re.sub(r'"(?:\\.|[^"\\])*"', '""', text)
    out = []
    for line in text.splitlines():
        idx = line.find("//")
        out.append(line[:idx] if idx != -1 else line)
    return "\n".join(out)


def check_usb_ops_context_is_real(failures: list[str]) -> None:
    code = strip_comments_and_strings(WIFI_MANAGER.read_text(encoding="utf-8"))
    # Find the production UsbWiFiOps construction.
    match = re.search(r"UsbWiFiOps\s+ops\s*\{(.*?)\};", code, re.S)
    if not match:
        failures.append("wifi_manager.cpp: no `UsbWiFiOps ops{...}` construction found")
        return
    fields = [f.strip() for f in match.group(1).split(",")]
    context = fields[-1] if fields else ""
    if context in {"nullptr", "NULL", "0"}:
        failures.append(
            "wifi_manager.cpp: production UsbWiFiOps.context is null; "
            "every USB verb would refuse itself (B1)"
        )
    if "reinterpret_cast" in context:
        failures.append(
            "wifi_manager.cpp: production UsbWiFiOps.context is a cast sentinel, "
            "not a stable object address (B1)"
        )
    if not context.startswith("&"):
        failures.append(
            f"wifi_manager.cpp: production UsbWiFiOps.context {context!r} is not "
            "an address-of a stable object (B1)"
        )
    # The address-of target must be declared (a real object).
    if context.startswith("&"):
        name = context[1:].strip()
        if not re.search(r"\b" + re.escape(name) + r"\b", code):
            failures.append(f"wifi_manager.cpp: context object {name!r} is not declared")


def check_driver_return_values_checked(failures: list[str]) -> None:
    code = strip_comments_and_strings(WIFI_MANAGER.read_text(encoding="utf-8"))
    # Inside validate_candidate the SetConfig step must check the return code,
    # whether it is compared inline or captured into a variable.
    body = re.search(r"validate_candidate\(.*?\n\}", code, re.S)
    if not body:
        failures.append("wifi_manager.cpp: validate_candidate not found")
        return
    snippet = body.group(0)
    inline_set = re.search(r"esp_wifi_set_config\s*\([^)]*\)\s*(!=\s*ESP_OK|==\s*ESP_OK)", snippet)
    captured_set = re.search(r"\w+\s*=\s*esp_wifi_set_config\s*\([^)]*\)", snippet) and \
        re.search(r"!=\s*ESP_OK", snippet)
    if not (inline_set or captured_set):
        failures.append(
            "wifi_manager.cpp: validate_candidate ignores esp_wifi_set_config's "
            "return value; a failed reconfigure can leave the old config and "
            "look like success (B5)"
        )
    inline_connect = re.search(r"esp_wifi_connect\s*\([^)]*\)\s*(!=\s*ESP_OK|==\s*ESP_OK)", snippet)
    if not inline_connect:
        failures.append(
            "wifi_manager.cpp: validate_candidate ignores esp_wifi_connect's "
            "return value (B5)"
        )
    # The candidate must verify the associated AP's SSID before persisting. The
    # helper that reads it must exist and be consulted inside validate_candidate.
    whole = code
    if "esp_wifi_sta_get_ap_info" not in whole:
        failures.append(
            "wifi_manager.cpp: no esp_wifi_sta_get_ap_info call; the actual "
            "associated SSID is never verified (B5)"
        )
    if "current_ap_ssid(" not in snippet:
        failures.append(
            "wifi_manager.cpp: validate_candidate does not verify the actual "
            "associated SSID before persisting (B5)"
        )


def check_no_strncpy_for_exact_fields(failures: list[str]) -> None:
    """32-byte SSID / 64-char PSK must not be truncated with strncpy(..., -1)."""
    code = strip_comments_and_strings(WIFI_MANAGER.read_text(encoding="utf-8"))
    # Any strncpy into the fixed sta config arrays is a truncation risk.
    if re.search(r"strncpy\([^;]*config\.sta\.(ssid|password)", code):
        failures.append(
            "wifi_manager.cpp: strncpy into config.sta.{ssid,password} can "
            "truncate a legal 32-byte SSID or 64-char PSK; use exact_copy_field (item 11)"
        )


def main() -> int:
    failures: list[str] = []
    check_usb_ops_context_is_real(failures)
    check_driver_return_values_checked(failures)
    check_no_strncpy_for_exact_fields(failures)
    if failures:
        for failure in failures:
            print(f"FAIL: {failure}", file=sys.stderr)
        return 1
    print("Production Wi-Fi binding: real USB context, checked driver returns, "
          "exact-length credential copy PASS")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
