"""Unit tests for common/config_client.py — the stdlib receiver of the config service.
Mirrors andrea_svc_auth/tests/test_client.py (urllib.request.urlopen monkeypatched).
Asserts: the X-Agent-Key header is sent from env, the box param is threaded, and the
FAIL-SOFT contract holds (connection error / non-200 / bad JSON / non-dict -> {} and
never raises)."""
import json
import urllib.error

import pytest

# common/ is on testpaths; import the module under test directly.
import config_client as cc


class _FakeResp:
    def __init__(self, status, body=b"{}"):
        self.status = status
        self._body = body

    def read(self):
        return self._body

    def __enter__(self):
        return self

    def __exit__(self, *a):
        return False


def test_fetch_sends_key_and_parses(monkeypatch):
    cap = {}
    monkeypatch.setenv("ANDREA_SVC_API_KEY", "K3Y")
    monkeypatch.delenv("CONFIG_SERVICE_URL", raising=False)

    def fake_urlopen(req, timeout=None):
        cap["url"] = req.full_url
        cap["headers"] = {k.lower(): v for k, v in req.header_items()}
        cap["method"] = req.get_method()
        return _FakeResp(200, json.dumps({"POLL_MIN": "5", "LOAD_GATE": "8"}).encode())

    monkeypatch.setattr(cc.urllib.request, "urlopen", fake_urlopen)
    out = cc.fetch_config("compressor")
    assert out == {"POLL_MIN": "5", "LOAD_GATE": "8"}
    assert cap["method"] == "GET"
    assert cap["headers"].get("x-agent-key") == "K3Y"
    assert cap["url"] == "http://10.121.1.1:9079/config/compressor"


def test_box_param_threaded(monkeypatch):
    cap = {}
    monkeypatch.setenv("ANDREA_SVC_API_KEY", "K3Y")
    monkeypatch.setenv("CONFIG_SERVICE_URL", "http://cfg:9079/")

    def fake_urlopen(req, timeout=None):
        cap["url"] = req.full_url
        return _FakeResp(200, b'{"X":"1"}')

    monkeypatch.setattr(cc.urllib.request, "urlopen", fake_urlopen)
    out = cc.fetch_config("compressor", box="w2295")
    assert out == {"X": "1"}
    assert cap["url"] == "http://cfg:9079/config/compressor?box=w2295"   # trailing / stripped


def test_no_key_omits_header(monkeypatch):
    cap = {}
    monkeypatch.delenv("ANDREA_SVC_API_KEY", raising=False)

    def fake_urlopen(req, timeout=None):
        cap["headers"] = {k.lower() for k, _ in req.header_items()}
        return _FakeResp(200, b"{}")

    monkeypatch.setattr(cc.urllib.request, "urlopen", fake_urlopen)
    cc.fetch_config("svc")
    assert "x-agent-key" not in cap["headers"]


# ── FAIL-SOFT: never raise, always {} ─────────────────────────────────────────
def test_failsoft_connection_error(monkeypatch):
    def boom(req, timeout=None):
        raise urllib.error.URLError("connection refused")
    monkeypatch.setattr(cc.urllib.request, "urlopen", boom)
    assert cc.fetch_config("svc") == {}             # no raise


def test_failsoft_timeout(monkeypatch):
    def boom(req, timeout=None):
        raise TimeoutError("timed out")
    monkeypatch.setattr(cc.urllib.request, "urlopen", boom)
    assert cc.fetch_config("svc") == {}


def test_failsoft_non_200(monkeypatch):
    monkeypatch.setattr(cc.urllib.request, "urlopen",
                        lambda req, timeout=None: _FakeResp(500, b'{"x":1}'))
    assert cc.fetch_config("svc") == {}


def test_failsoft_http_error(monkeypatch):
    def boom(req, timeout=None):
        raise urllib.error.HTTPError("u", 403, "forbidden", {}, None)
    monkeypatch.setattr(cc.urllib.request, "urlopen", boom)
    assert cc.fetch_config("svc") == {}


def test_failsoft_bad_json(monkeypatch):
    monkeypatch.setattr(cc.urllib.request, "urlopen",
                        lambda req, timeout=None: _FakeResp(200, b"not json{{"))
    assert cc.fetch_config("svc") == {}


def test_failsoft_non_dict_json(monkeypatch):
    monkeypatch.setattr(cc.urllib.request, "urlopen",
                        lambda req, timeout=None: _FakeResp(200, b'["a","b"]'))
    assert cc.fetch_config("svc") == {}             # a JSON array is not a config dict


# ── layer_env filters to requested keys ───────────────────────────────────────
def test_layer_env_filters_requested_keys(monkeypatch):
    monkeypatch.setattr(cc, "fetch_config",
                        lambda *a, **k: {"A": "1", "B": "2", "C": "3"})
    out = cc.layer_env("svc", ["A", "C", "MISSING"])
    assert out == {"A": "1", "C": "3"}              # only present-and-requested


def test_layer_env_empty_on_fetch_failure(monkeypatch):
    monkeypatch.setattr(cc, "fetch_config", lambda *a, **k: {})
    assert cc.layer_env("svc", ["A"]) == {}
