"""ConfigServer client — source a service's secrets + tweaks from the central config-service, so a
basement service stops reading dot-envs (operator 2026-07-19: "ConfigServer for secrets and tweaks").

STDLIB-ONLY (urllib) + FAIL-SOFT by contract (any error / non-200 / bad JSON → {}), vendored here —
mirroring andrea_svc_auth/client.py and common/config_client.py — so a standalone service IMAGE needs
no extra dep and a config-service outage degrades to the baked-in env/default, never an outage.

Config-service wire contract (services/config-service): GET /config/{service}(?box=<host>) with the
X-Agent-Key header → merged {k: v} (the box overlays host-specific knobs over the global '*' defaults).
"""
import json
import os
import urllib.parse
import urllib.request

API_KEY_ENV = "ANDREA_SVC_API_KEY"
URL_ENV = "CONFIG_SERVICE_URL"
DEFAULT_URL = "http://10.121.1.1:9079"
API_KEY_HEADER = "X-Agent-Key"


def fetch_config(service: str, box: str | None = None, *, timeout: float = 5) -> dict:
    """GET {CONFIG_SERVICE_URL}/config/{service}(?box=) with X-Agent-Key → merged {k: v}.
    FAIL-SOFT: any error / non-200 / bad JSON → {} (never raises)."""
    try:
        base = (os.getenv(URL_ENV) or DEFAULT_URL).rstrip("/")
        url = f"{base}/config/{urllib.parse.quote(str(service))}"
        if box:
            url += "?" + urllib.parse.urlencode({"box": box})
        headers = {}
        key = os.getenv(API_KEY_ENV, "")
        if key:
            headers[API_KEY_HEADER] = key
        req = urllib.request.Request(url, method="GET", headers=headers)
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            status = getattr(resp, "status", None) or resp.getcode()
            if status != 200:
                return {}
            parsed = json.loads(resp.read())
        return parsed if isinstance(parsed, dict) else {}
    except Exception:                                      # noqa: BLE001 — fail-soft by contract
        return {}


def config_value(service: str, key: str, default=None, box: str | None = None):
    """One value with a config-service → env → default fallback (fail-soft)."""
    return fetch_config(service, box).get(key, os.environ.get(key, default))


# The config-service payload most recently APPLIED in this process. It is the diff baseline for the
# live reload (rm#129, andrea_svc_web.reload): "what changed since we last applied?" — without it a
# reload could not tell a rotated DB password (→ reconnect) from an unchanged one (→ no-op). Values are
# held in-process only, never logged or returned by the reload endpoint (names/classes only).
_APPLIED: dict = {}


def applied_snapshot() -> dict:
    """A COPY of the config payload last applied in this process (empty before the first apply)."""
    return dict(_APPLIED)


def record_applied(cfg: dict) -> None:
    """Set the diff baseline. Called by apply_to_env() at startup and by the reload path after it
    applies a new payload."""
    global _APPLIED
    _APPLIED = dict(cfg or {})


def apply_to_env(service: str, box: str | None = None) -> int:
    """Overlay the ConfigServer values onto os.environ so settings that read env transparently pick up
    ConfigServer secrets/tweaks (the config-service is authoritative → it OVERRIDES env; a fetch failure
    leaves env untouched). Call it FIRST in settings. Returns the number of keys applied. Fail-soft.

    Also records the payload as the reload diff baseline (rm#129)."""
    cfg = fetch_config(service, box)
    for k, v in cfg.items():
        os.environ[str(k)] = str(v)
    if cfg:
        record_applied(cfg)
    return len(cfg)
