"""Collector: external dependency pings — Plane / Gitea / Anthropic (FR-6).

A light ``GET`` with a short timeout per configured dependency. never-raise: an
unreachable dependency returns ``False`` (a signal for the threshold), never an
exception (D8). Endpoints / timeouts are configured via ``WATCHDOG_DEPS`` (D5);
an empty config means no pings (fail-safe).
"""
from __future__ import annotations

import logging
import urllib.error
import urllib.request

logger = logging.getLogger("watchdog.collectors.deps")


def ping(url: str, timeout_s: float, *, opener=urllib.request.urlopen) -> bool:
    """True when ``url`` answers with a non-5xx HTTP status. never-raise.

    A 4xx still counts as "reachable" (the host is up and responding) — we ping
    for liveness, not for auth. ``opener`` is injected so tests never hit the
    network.
    """
    try:
        req = urllib.request.Request(url, method="GET")
        with opener(req, timeout=timeout_s) as resp:
            status = int(getattr(resp, "status", None) or resp.getcode())
            return status < 500
    except urllib.error.HTTPError as e:
        # An HTTP error response still proves the host is reachable, unless 5xx.
        return int(getattr(e, "code", 500)) < 500
    except Exception as e:  # noqa: BLE001 - unreachable -> down signal, not a crash
        logger.warning("watchdog: dep ping %s failed: %s", url, e)
        return False


def ping_all(
    deps: dict[str, str],
    timeout_s: float,
    *,
    opener=urllib.request.urlopen,
) -> dict[str, bool]:
    """Ping every configured dependency -> ``{name: reachable}``. never-raise."""
    out: dict[str, bool] = {}
    for name, url in deps.items():
        try:
            out[name] = ping(url, timeout_s, opener=opener)
        except Exception as e:  # noqa: BLE001 - one dep degrades, others continue
            logger.warning("watchdog: dep %s ping error: %s", name, e)
            out[name] = False
    return out
