"""Execution context — the injected clients a verb runs against.

Real clients are built lazily from the requested surfaces + the house credential
fallbacks. Tests pass a :class:`Context` with fakes directly, so no verb ever
needs the network. Building a client resolves a credential from disk/env straight
into the client — never into a returned value (AG-18).
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, TypeVar

from .errors import CredentialError
from .forge import CodebergForgeClient, ForgeAdmin
from .hosts import HostAdmin, SSHHostClient
from .registry import fed_root as _fed_root
from .woodpecker import WoodpeckerAdmin, WoodpeckerClient

if TYPE_CHECKING:
    from collections.abc import Callable
    from pathlib import Path

#: Default ops SSH key path for C7 (MGF_INFRA §1), overridable via ``$MGF_SSH_KEY``.
_DEFAULT_SSH_KEY = "mgf-standard/my_stuff/infomaniak/woodpecker_id_ed25519"


@dataclass
class Context:
    #: Clients are the read+write admin surfaces (the read-only doctor/probes accept
    #: the wider type; only ``sync --apply`` calls the write methods).
    fed_root: Path
    woodpecker: WoodpeckerAdmin | None = None
    forge: ForgeAdmin | None = None
    hosts: HostAdmin | None = None


def build_context(
    surfaces: set[str],
    *,
    root: str | Path | None = None,
    best_effort: bool = False,
    ssh_key: str | None = None,
) -> Context:
    """Construct real clients for the requested surfaces.

    Args:
        surfaces: the requested doctor surfaces (woodpecker/forge/local/hosts).
        best_effort: if True, a missing credential yields ``None`` (probe);
            otherwise it raises :class:`CredentialError` (doctor exit 2).
    """
    root_path = _fed_root(root)
    wp: WoodpeckerAdmin | None = None
    forge: ForgeAdmin | None = None
    hosts: HostAdmin | None = None

    if "woodpecker" in surfaces:
        wp = _maybe(lambda: WoodpeckerClient(fed_root=root_path), best_effort)
    if "forge" in surfaces:
        forge = _maybe(lambda: CodebergForgeClient(), best_effort)
    if "hosts" in surfaces:
        key = ssh_key or str(root_path / _DEFAULT_SSH_KEY)
        hosts = SSHHostClient(key_path=key)

    return Context(fed_root=root_path, woodpecker=wp, forge=forge, hosts=hosts)


_T = TypeVar("_T")


def _maybe(factory: Callable[[], _T], best_effort: bool) -> _T | None:
    if not best_effort:
        return factory()
    try:
        return factory()
    except CredentialError:
        return None
