#!/usr/bin/env python3
"""
Probe configured remote hub hosts and list **USB power-control hubs** (per-port switching and
monitoring) on each via remote ``fiwi.py discover`` — serial, model, hub index / port count.
Uses the generic ``discover`` view, not the ``show_hostcards`` concentrator label (same scan).

Does **not** load BrainStem locally. Default: one SSH **readiness** probe (repo ``cd``, ``fiwi.py``,
``env/``, ``import brainstem`` via ``FIWI_REMOTE_PYTHON``), then remote ``discover``. Use
``--probe-only`` to skip discovery; ``--invoke`` to run another Fi-Wi subcommand instead.
``--list-only`` prints resolved config and hub list (no SSH).

**Standalone**::

    python tests/check_remote.py -c uax24
    python tests/check_remote.py -c uax24 --probe-only
    python tests/check_remote.py -c uax24 --list-only
    python tests/check_remote.py -c uax24 --invoke show_hostcards

``--config`` matches ``FIWI_CONFIG`` (profile name or absolute ``*.ini`` path).
"""

from __future__ import annotations

import argparse
import os
import shlex
import sys

_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
if _ROOT not in sys.path:
    sys.path.insert(0, _ROOT)


def _configure_paths() -> None:
    import fiwi.paths as paths_mod

    paths_mod.configure(_ROOT)


def _print_config_summary() -> None:
    from fiwi.ssh import SshNodeConfig

    cfg = SshNodeConfig.load()
    raw = (cfg.calibrate_remotes or "").strip()
    print("Resolved remote SSH (after paths + INI)", flush=True)
    print("-" * 60, flush=True)
    print(f"  FIWI_REMOTE_PYTHON → {cfg.python}", flush=True)
    print(f"  FIWI_REMOTE_SCRIPT → {cfg.script}", flush=True)
    print(f"  FIWI_SSH_BIN         {cfg.ssh_bin}", flush=True)
    if cfg.ssh_extra_argv:
        print(f"  FIWI_SSH_OPTS        {' '.join(cfg.ssh_extra_argv)}", flush=True)
    print(f"  merged hub hosts     {raw or '(none)'}", flush=True)
    print("-" * 60, flush=True)
    print(flush=True)


def _print_ssh_client_hints(stderr: str) -> None:
    err = stderr or ""
    if "Bad owner or permissions" in err and "ssh_config" in err:
        print(
            "  Hint: OpenSSH refused to read a config snippet (ownership/permissions). "
            "On Fedora: ``sudo chmod 644 /etc/ssh/ssh_config.d/*.conf`` (or remove the bad include).",
            flush=True,
        )


def _print_readiness(_host: str, r) -> None:
    from fiwi.fiwi_relay.host import RemoteReadiness

    assert isinstance(r, RemoteReadiness)
    print("== remote readiness ==", flush=True)
    if r.timed_out:
        print("  SSH: timed out", flush=True)
        return
    if not r.ssh_connected:
        print(f"  SSH: failed (exit {r.ssh_exit_code})", flush=True)
        if r.raw_stderr.strip():
            print(r.raw_stderr.rstrip(), file=sys.stderr, flush=True)
        _print_ssh_client_hints(r.raw_stderr)
        return
    hn = (r.hostname or "").strip() or "?"
    un = (r.uname or "").strip() or "?"
    print(f"  hostname: {hn}", flush=True)
    print(f"  uname:    {un}", flush=True)
    print(f"  repo cd:  {'ok' if r.cd_ok else 'FAIL'}", flush=True)
    print(f"  fiwi.py:  {'present' if r.script_present else 'MISSING'}", flush=True)
    print(f"  env/:     {'yes' if r.venv_present else 'no'}", flush=True)
    pyx = (r.remote_python_executable or "").strip()
    if pyx:
        print(f"  {pyx}", flush=True)
    bs = "ok" if r.brainstem_import_ok else "FAIL (ModuleNotFound or error)"
    print(f"  brainstem (FIWI_REMOTE_PYTHON): {bs}", flush=True)
    bvenv = "ok" if r.brainstem_ok_in_venv else ("n/a" if not r.venv_present else "FAIL")
    print(f"  brainstem (env/bin/python3):    {bvenv}", flush=True)
    if r.suggests_remote_python_venv:
        from fiwi.ssh import SshNodeConfig

        from fiwi.fiwi_relay.host import suggested_remote_venv_python

        hint = suggested_remote_venv_python(SshNodeConfig.load().script)
        print(
            f"  → Set remote_python in [remote_ssh] to {hint!r} "
            f"(deps are in env/, not system python3).",
            flush=True,
        )
    elif r.suggests_fiwi_relay_setup:
        from fiwi.fiwi_relay.host import ssh_target_points_here

        cfg = (os.environ.get("FIWI_CONFIG") or "").strip() or "default"
        cmd = f"{shlex.quote(sys.executable)} -m fiwi.fiwi_relay -c {shlex.quote(cfg)}"
        where = "this checkout" if ssh_target_points_here(_host) else "the hub over SSH"
        print(f"  → Run: {cmd}  (installs deps on {where})", flush=True)
    if r.raw_stderr.strip() and not r.suggests_fiwi_relay_setup and not r.suggests_remote_python_venv:
        print(r.raw_stderr.rstrip(), file=sys.stderr, flush=True)


def _probe_host(target: str, *, timeout: float) -> tuple[int, object]:
    """SSH readiness probe; returns (exit_code, RemoteReadiness)."""
    from fiwi.fiwi_relay.host import probe_remote_hub_readiness

    r = probe_remote_hub_readiness(target, timeout=timeout)
    _print_readiness(target, r)
    if r.timed_out:
        return 124, r
    if not r.ssh_connected:
        return r.ssh_exit_code if r.ssh_exit_code >= 0 else 255, r
    return 0, r


def _merged_hosts() -> list[str]:
    from fiwi.ssh import SshNodeConfig

    raw = (SshNodeConfig.load().calibrate_remotes or "").strip()
    seen: list[str] = []
    for part in raw.split(","):
        h = part.strip()
        if h and h not in seen:
            seen.append(h)
    return seen


def main() -> int:
    p = argparse.ArgumentParser(
        description="SSH probe for remote USB power-control hub hosts from config.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument(
        "-c",
        "--config",
        metavar="PROFILE_OR_INI",
        help="INI profile or path (sets FIWI_CONFIG for this run)",
    )
    p.add_argument(
        "--timeout",
        type=float,
        default=45.0,
        metavar="SEC",
        help="SSH timeout for the light probe (default: 45)",
    )
    p.add_argument(
        "--discover-timeout",
        "--hostcards-timeout",
        type=float,
        default=90.0,
        dest="discover_timeout",
        metavar="SEC",
        help="Timeout for remote discover (default: 90; only after successful probe)",
    )
    p.add_argument(
        "--list-only",
        action="store_true",
        help="Print resolved SSH config and hub list only (no SSH)",
    )
    p.add_argument(
        "--probe-only",
        action="store_true",
        help="Only run SSH readiness probe; do not run remote discover",
    )
    p.add_argument(
        "--invoke",
        nargs=argparse.REMAINDER,
        metavar="ARGS",
        help="Run only this Fi-Wi subcommand on each host (skips probe and default discover)",
    )
    args = p.parse_args()
    if args.config:
        os.environ["FIWI_CONFIG"] = args.config.strip()

    try:
        os.chdir(_ROOT)
    except OSError as exc:
        print(f"FAIL: cannot chdir to repo root {_ROOT!r}: {exc}", file=sys.stderr)
        return 1

    _configure_paths()
    _print_config_summary()

    hosts = _merged_hosts()
    if not hosts:
        print(
            "No remote hosts configured. Set [remote_hubs] hosts or FIWI_REMOTE_HUBS / "
            "FIWI_CALIBRATE_REMOTES.",
            flush=True,
        )
        return 2

    if args.list_only:
        print(f"Hubs ({len(hosts)}): {', '.join(hosts)}", flush=True)
        label = os.environ.get("FIWI_CONFIG", "default")
        print(f"\nDone  [config: {label}]")
        return 0

    invoke_args = args.invoke
    if invoke_args is not None and len(invoke_args) >= 1 and invoke_args[0] == "--":
        invoke_args = invoke_args[1:]

    from fiwi.ssh import SshNode

    rc = 0
    for host in hosts:
        print(f"\n>>> {host}\n", flush=True)
        if invoke_args:
            try:
                node = SshNode.parse(host)
                code, out, err = node.invoke_capture(list(invoke_args), timeout=args.timeout, defer=False)
                if out.strip():
                    print(out.rstrip(), flush=True)
                if err.strip():
                    print(err.rstrip(), file=sys.stderr, flush=True)
                if code != 0:
                    print(f"  (fiwi invoke exit {code})", flush=True)
                    rc = 1
            except Exception as exc:
                print(f"  invoke_capture failed: {exc}", flush=True)
                rc = 1
            continue

        prc, readiness = _probe_host(host, timeout=args.timeout)
        if prc != 0:
            rc = 1
            continue
        if readiness.suggests_fiwi_relay_setup or readiness.suggests_remote_python_venv:
            rc = 1

        if args.probe_only:
            continue

        print("\n== remote USB power-control hubs (fiwi discover) ==\n", flush=True)
        try:
            node = SshNode.parse(host)
            code, out, err = node.invoke_capture(
                ["discover"],
                timeout=args.discover_timeout,
                defer=False,
            )
            if out.strip():
                print(out.rstrip(), flush=True)
            if err.strip():
                print(err.rstrip(), file=sys.stderr, flush=True)
            if code != 0:
                print(f"  (discover exit {code})", flush=True)
                rc = 1
        except Exception as exc:
            print(f"  discover failed: {exc}", flush=True)
            rc = 1

    label = os.environ.get("FIWI_CONFIG", "default")
    print(f"\nDone  [config: {label}]")
    return rc


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