"""Host client (read-only SSH ``stat``) — doctor C7, opt-in (``--surface hosts``).

One read-only SSH per host; ``stat`` only, never file contents (SPEC §5 C7). The
command runner is injectable so C7's checks run offline. The SSH key path comes
from the registry (the infomaniak ops key), user ``ubuntu`` per MGF_INFRA §1.

Three distinct outcomes (never conflated — a present-but-unreadable file must NOT
page as missing):
  * ``present``     — ``stat`` returned mode+owner.
  * ``missing``     — the remote reported "No such file or directory".
  * ``unreachable`` — the host is down/auth-failed, or the path sits under a
    directory ``ubuntu`` cannot traverse (e.g. ``/opt/steward`` 0750) and the
    ``sudo -n`` fallback is unavailable. Present-but-unverifiable, not missing.
"""

from __future__ import annotations

import shlex
import subprocess
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Protocol

if TYPE_CHECKING:
    from pathlib import Path

DEFAULT_USER = "ubuntu"

#: A runner takes an argv list, returns (returncode, stdout, stderr).
Runner = Callable[[list[str]], tuple[int, str, str]]

PRESENT = "present"
MISSING = "missing"
UNREACHABLE = "unreachable"


@dataclass(frozen=True)
class HostStat:
    status: str  # present | missing | unreachable
    mode: str | None = None  # octal, e.g. "600"
    owner: str | None = None
    detail: str = ""

    @property
    def present(self) -> bool:
        return self.status == PRESENT


class HostAPI(Protocol):
    def stat(self, host: str, path: str) -> HostStat: ...


def _default_runner(argv: list[str]) -> tuple[int, str, str]:
    proc = subprocess.run(argv, capture_output=True, text=True, timeout=60)  # noqa: S603
    return proc.returncode, proc.stdout, proc.stderr


class SSHHostClient:
    """Read-only ``stat -c '%a %U'`` over SSH, one connection per call.

    The remote command is sent as a single shell-quoted string so the ``%a %U``
    format survives SSH's space-join of the remote argv (the bug that made every
    host-file read as MISSING). Falls back to ``sudo -n stat`` for paths under a
    directory ``ubuntu`` cannot traverse (``sudo -n`` never prompts/hangs).
    """

    def __init__(
        self,
        *,
        key_path: str | Path,
        user: str = DEFAULT_USER,
        runner: Runner = _default_runner,
        use_sudo_fallback: bool = True,
    ) -> None:
        self.key_path = str(key_path)
        self.user = user
        self._runner = runner
        self._use_sudo = use_sudo_fallback

    def _ssh_argv(self, host: str, remote_cmd: str) -> list[str]:
        return [
            "ssh",
            "-i",
            self.key_path,
            "-o",
            "BatchMode=yes",
            "-o",
            "StrictHostKeyChecking=accept-new",
            "-o",
            "ConnectTimeout=10",
            f"{self.user}@{host}",
            remote_cmd,
        ]

    @staticmethod
    def _remote_stat(path: str, *, sudo: bool = False) -> str:
        # Single quoted remote command: the '%a %U' format stays one arg.
        prefix = "sudo -n " if sudo else ""
        return f"{prefix}stat -c '%a %U' {shlex.quote(path)}"

    @staticmethod
    def _parse_present(out: str) -> HostStat | None:
        parts = out.split()
        if len(parts) >= 2:
            return HostStat(PRESENT, mode=parts[0], owner=parts[1])
        return None

    def stat(self, host: str, path: str) -> HostStat:
        rc, out, err = self._runner(self._ssh_argv(host, self._remote_stat(path)))
        if rc == 0 and out.strip():
            got = self._parse_present(out)
            if got is not None:
                return got
        low = err.lower()
        if "no such file" in low:
            return HostStat(MISSING, detail="no such file or directory")
        if rc == 255:  # SSH transport/auth failure — host unreachable
            return HostStat(UNREACHABLE, detail=(err.strip()[:120] or "ssh connection failed"))
        # Remote traversal/permission denial — try a privileged read, else unreachable.
        if self._use_sudo and ("permission denied" in low or "not a directory" in low):
            rc2, out2, _e2 = self._runner(self._ssh_argv(host, self._remote_stat(path, sudo=True)))
            if rc2 == 0 and out2.strip():
                got = self._parse_present(out2)
                if got is not None:
                    return HostStat(PRESENT, mode=got.mode, owner=got.owner, detail="via sudo")
            return HostStat(
                UNREACHABLE, detail="present but unverifiable (permission denied; sudo unavailable)"
            )
        return HostStat(UNREACHABLE, detail=(err.strip()[:120] or f"stat failed rc={rc}"))
