"""Live config/secrets reload tests (rm#129): classification, the diff, in-process mirroring
(auth key live on the next request), the DB reconnect path, the service hook, and the security
contract that VALUES are never returned."""
import os

from django.test import override_settings

from andrea_svc_web import config as cfgmod
from andrea_svc_web import reload as reloadmod


def _seed(applied: dict, served: dict, monkeypatch):
    """Baseline = `applied`; the config-service then serves `served`."""
    cfgmod.record_applied(applied)
    monkeypatch.setattr(reloadmod, "fetch_config", lambda *a, **k: dict(served))


# ── classification ────────────────────────────────────────────────────────────────────────────
def test_classify_db_beats_auth():
    # DB_PASSWORD is a credential AND a DB field — it must classify as db so it triggers a reconnect
    assert reloadmod.classify("DB_PASSWORD") == "db"
    assert reloadmod.classify("CONFIG_DB_HOST") == "db"
    assert reloadmod.classify("DATABASE_URL") == "db"
    assert reloadmod.classify("ANDREA_SVC_API_KEY") == "auth"
    assert reloadmod.classify("DOCSTORE_TOKEN") == "auth"
    assert reloadmod.classify("POLL_INTERVAL") == "other"


# ── diff ──────────────────────────────────────────────────────────────────────────────────────
def test_only_changed_keys_are_reported(monkeypatch):
    _seed({"A": "1", "POLL_INTERVAL": "5"}, {"A": "1", "POLL_INTERVAL": "9"}, monkeypatch)
    out = reloadmod.reload_config()
    assert out["reloaded"] is True
    assert out["changed_count"] == 1
    assert out["changed"]["other"] == ["POLL_INTERVAL"]
    assert os.environ["POLL_INTERVAL"] == "9"


def test_empty_payload_is_failsoft_not_a_wipe(monkeypatch):
    _seed({"A": "1"}, {}, monkeypatch)
    out = reloadmod.reload_config()
    assert out["reloaded"] is False              # unreachable config-service != "everything changed"
    assert out["changed_count"] == 0


# ── auth key: live on the NEXT request, no reconnect ───────────────────────────────────────────
def test_rotated_api_key_becomes_the_live_service_key(monkeypatch):
    monkeypatch.delenv("ANDREA_SVC_KEYS", raising=False)
    _seed({"ANDREA_SVC_API_KEY": "old"}, {"ANDREA_SVC_API_KEY": "new"}, monkeypatch)
    with override_settings(ANDREA_SVC_KEYS="old"):
        out = reloadmod.reload_config()
        from django.conf import settings
        # the auth layer reads settings.ANDREA_SVC_KEYS per request -> the new key is already live
        assert settings.ANDREA_SVC_KEYS == "new"
    assert out["changed"]["auth"] == ["ANDREA_SVC_API_KEY"]
    assert out["db_aliases_reconnected"] == []          # a key rotation must NOT bounce the DB


# ── DB credentials: settings re-pointed + connections dropped ─────────────────────────────────
def test_rotated_db_credentials_repoint_settings(monkeypatch):
    _seed({"DB_PASSWORD": "old", "DB_HOST": "10.0.0.1"},
          {"DB_PASSWORD": "new", "DB_HOST": "10.0.0.2"}, monkeypatch)
    closed = {"n": 0}
    monkeypatch.setattr(reloadmod, "_apply_databases",
                        lambda: (closed.__setitem__("n", closed["n"] + 1), ["default"])[1])
    out = reloadmod.reload_config()
    assert set(out["changed"]["db"]) == {"DB_PASSWORD", "DB_HOST"}
    assert out["db_aliases_reconnected"] == ["default"]
    assert closed["n"] == 1                              # reconnect ran exactly once


def test_apply_databases_repoints_default_alias(monkeypatch):
    monkeypatch.setenv("DB_HOST", "10.0.0.9")
    monkeypatch.setenv("DB_PASSWORD", "rotated")
    dbs = {"default": {"ENGINE": "x", "HOST": "10.0.0.1", "PASSWORD": "old"}}
    with override_settings(DATABASES=dbs):
        touched = reloadmod._apply_databases()
        assert touched == ["default"]
        assert dbs["default"]["HOST"] == "10.0.0.9"
        assert dbs["default"]["PASSWORD"] == "rotated"


# ── service hook (pools outside the ORM) ──────────────────────────────────────────────────────
def test_service_hook_is_called_and_a_broken_hook_does_not_raise(monkeypatch):
    seen = {}
    monkeypatch.setattr(reloadmod, "_run_hook", lambda s: seen.setdefault("summary", s) and "h")
    _seed({"X": "1"}, {"X": "2"}, monkeypatch)
    reloadmod.reload_config()
    assert seen["summary"]["changed"]["other"] == ["X"]

    def _boom(_):
        raise RuntimeError("hook exploded")
    monkeypatch.setattr(reloadmod, "_run_hook", _boom)
    _seed({"X": "2"}, {"X": "3"}, monkeypatch)
    out = reloadmod.reload_config()                      # a broken hook is reported, never fatal
    assert out["reloaded"] is True and "hook_error" in out


# ── security: names only, never values ────────────────────────────────────────────────────────
def test_response_never_carries_secret_values(monkeypatch):
    _seed({"DB_PASSWORD": "old-secret"}, {"DB_PASSWORD": "SUPER-SECRET-VALUE"}, monkeypatch)
    monkeypatch.setattr(reloadmod, "_apply_databases", lambda: ["default"])
    out = reloadmod.reload_config()
    assert "SUPER-SECRET-VALUE" not in repr(out)
    assert out["changed"]["db"] == ["DB_PASSWORD"]
