"""
Tests for Phase 49: Centralized Configuration Management.
"""

import json

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all config storage to a temp directory and reset state."""
    config_dir = tmp_path / "config"
    config_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.config_manager as cm
    monkeypatch.setattr(cm, "CONFIG_DIR", config_dir)
    monkeypatch.setattr(cm, "CONFIG_FILE", config_dir / "platform.json")
    monkeypatch.setattr(cm, "HISTORY_FILE", config_dir / "history.jsonl")
    monkeypatch.setattr(cm, "BROKER_DIR", tmp_path)

    # Reset runtime state so tests are independent
    cm._reset()


# ── 1. Get / Set ─────────────────────────────────────────────────────────


class TestGetSet:
    def test_set_and_get(self):
        from brynq.config_manager import get, set
        result = set("my.key", 42)
        assert result["ok"] is True
        assert get("my.key") == 42

    def test_get_missing_returns_default(self):
        from brynq.config_manager import get
        assert get("nonexistent", "fallback") == "fallback"

    def test_get_missing_returns_none(self):
        from brynq.config_manager import get
        assert get("nonexistent") is None

    def test_set_overwrites(self):
        from brynq.config_manager import get, set
        set("x", 1)
        set("x", 2)
        assert get("x") == 2

    def test_get_returns_registered_default(self):
        from brynq.config_manager import get
        # Built-in default
        assert get("rate_limit.max_per_minute") == 100

    def test_explicit_value_overrides_default(self):
        from brynq.config_manager import get, set
        set("rate_limit.max_per_minute", 200)
        assert get("rate_limit.max_per_minute") == 200

    def test_set_returns_old_value(self):
        from brynq.config_manager import set
        set("k", "old")
        result = set("k", "new")
        assert result["old_value"] == "old"

    def test_set_string_value(self):
        from brynq.config_manager import get, set
        set("platform.name", "TestPlatform")
        assert get("platform.name") == "TestPlatform"


# ── 2. Sections ──────────────────────────────────────────────────────────


class TestSections:
    def test_get_section_from_defaults(self):
        from brynq.config_manager import get_section
        section = get_section("rate_limit")
        assert "rate_limit.max_per_minute" in section
        assert section["rate_limit.max_per_minute"] == 100

    def test_get_section_with_explicit_override(self):
        from brynq.config_manager import get_section, set
        set("rate_limit.max_per_minute", 500)
        section = get_section("rate_limit")
        assert section["rate_limit.max_per_minute"] == 500

    def test_get_section_empty(self):
        from brynq.config_manager import get_section
        assert get_section("nonexistent_section") == {}

    def test_list_sections(self):
        from brynq.config_manager import list_sections
        sections = list_sections()
        assert "rate_limit" in sections
        assert "auth" in sections
        assert "platform" in sections
        assert "marketplace" in sections
        assert "sandbox" in sections

    def test_list_sections_includes_explicit(self):
        from brynq.config_manager import list_sections, set
        set("custom_section.foo", "bar")
        sections = list_sections()
        assert "custom_section" in sections


# ── 3. Defaults ──────────────────────────────────────────────────────────


class TestDefaults:
    def test_builtin_defaults_present(self):
        from brynq.config_manager import get_all_defaults
        defaults = get_all_defaults()
        assert "rate_limit.max_per_minute" in defaults
        assert defaults["rate_limit.max_per_minute"]["value"] == 100

    def test_register_default(self):
        from brynq.config_manager import register_default, get, get_all_defaults
        register_default("custom.thing", 99, "A custom default")
        assert get("custom.thing") == 99
        defaults = get_all_defaults()
        assert defaults["custom.thing"]["description"] == "A custom default"

    def test_all_builtin_defaults(self):
        from brynq.config_manager import get
        assert get("auth.session_ttl_hours") == 168
        assert get("platform.name") == "Brynq"
        assert get("platform.version") == "1.0"
        assert get("marketplace.platform_fee_pct") == 15
        assert get("sandbox.default_trust_level") == "untrusted"


# ── 4. Validation ────────────────────────────────────────────────────────


class TestValidation:
    def test_rate_limit_rejects_negative(self):
        from brynq.config_manager import set
        result = set("rate_limit.max_per_minute", -5)
        assert "error" in result

    def test_rate_limit_rejects_zero(self):
        from brynq.config_manager import set
        result = set("rate_limit.max_per_minute", 0)
        assert "error" in result

    def test_rate_limit_rejects_float(self):
        from brynq.config_manager import set
        result = set("rate_limit.max_per_minute", 3.5)
        assert "error" in result

    def test_fee_rejects_over_100(self):
        from brynq.config_manager import set
        result = set("marketplace.platform_fee_pct", 101)
        assert "error" in result

    def test_fee_rejects_negative(self):
        from brynq.config_manager import set
        result = set("marketplace.platform_fee_pct", -1)
        assert "error" in result

    def test_fee_accepts_boundary(self):
        from brynq.config_manager import set, get
        assert set("marketplace.platform_fee_pct", 0)["ok"]
        assert get("marketplace.platform_fee_pct") == 0
        assert set("marketplace.platform_fee_pct", 100)["ok"]
        assert get("marketplace.platform_fee_pct") == 100

    def test_ttl_rejects_non_positive(self):
        from brynq.config_manager import set
        assert "error" in set("auth.session_ttl_hours", 0)
        assert "error" in set("auth.session_ttl_hours", -10)

    def test_custom_validator(self):
        from brynq.config_manager import register_validator, set
        register_validator("custom.color", lambda v: v in ("red", "blue", "green"))
        assert set("custom.color", "red")["ok"]
        assert "error" in set("custom.color", "purple")

    def test_no_validator_allows_anything(self):
        from brynq.config_manager import set
        assert set("no_validator.key", "anything")["ok"]
        assert set("no_validator.key", 12345)["ok"]
        assert set("no_validator.key", [1, 2, 3])["ok"]


# ── 5. Change History ───────────────────────────────────────────────────


class TestHistory:
    def test_set_creates_history_entry(self):
        from brynq.config_manager import set, get_history
        set("h.key", "v1", changed_by="tester")
        history = get_history()
        assert len(history) == 1
        assert history[0]["key"] == "h.key"
        assert history[0]["new_value"] == "v1"
        assert history[0]["old_value"] is None
        assert history[0]["changed_by"] == "tester"
        assert "timestamp" in history[0]

    def test_history_tracks_old_value(self):
        from brynq.config_manager import set, get_history
        set("h.key", "v1")
        set("h.key", "v2")
        history = get_history(key="h.key")
        assert len(history) == 2
        assert history[1]["old_value"] == "v1"
        assert history[1]["new_value"] == "v2"

    def test_history_filter_by_key(self):
        from brynq.config_manager import set, get_history
        set("a.key", 1)
        set("b.key", 2)
        set("a.key", 3)
        history = get_history(key="a.key")
        assert len(history) == 2
        assert all(e["key"] == "a.key" for e in history)

    def test_history_limit(self):
        from brynq.config_manager import set, get_history
        for i in range(10):
            set("counter", i)
        history = get_history(limit=3)
        assert len(history) == 3
        assert history[-1]["new_value"] == 9

    def test_failed_validation_no_history(self):
        from brynq.config_manager import set, get_history
        set("rate_limit.max_per_minute", -1)  # Should fail validation
        history = get_history(key="rate_limit.max_per_minute")
        assert len(history) == 0


# ── 6. Environment Override ──────────────────────────────────────────────


class TestEnvOverride:
    def test_load_from_env(self, monkeypatch):
        from brynq.config_manager import load_from_env, get
        monkeypatch.setenv("BRYNQ_RATE_LIMIT_MAX_PER_MINUTE", "200")
        result = load_from_env()
        assert result["count"] >= 1
        assert get("rate_limit.max_per_minute") == 200

    def test_env_override_platform_name(self, monkeypatch):
        from brynq.config_manager import load_from_env, get
        monkeypatch.setenv("BRYNQ_PLATFORM_NAME", "TestBrynq")
        load_from_env()
        assert get("platform.name") == "TestBrynq"

    def test_env_override_validation_error(self, monkeypatch):
        from brynq.config_manager import load_from_env, get
        monkeypatch.setenv("BRYNQ_RATE_LIMIT_MAX_PER_MINUTE", "-5")
        result = load_from_env()
        assert len(result["errors"]) >= 1
        # Value should still be the default
        assert get("rate_limit.max_per_minute") == 100

    def test_env_bool_parsing(self, monkeypatch):
        from brynq.config_manager import load_from_env, get
        monkeypatch.setenv("BRYNQ_SANDBOX_ENABLED", "true")
        load_from_env()
        assert get("sandbox.enabled") is True

    def test_env_custom_prefix(self, monkeypatch):
        from brynq.config_manager import load_from_env, get
        monkeypatch.setenv("MYAPP_CUSTOM_KEY", "hello")
        load_from_env(prefix="MYAPP_")
        assert get("custom.key") == "hello"


# ── 7. Export / Import ───────────────────────────────────────────────────


class TestExportImport:
    def test_export_includes_defaults(self):
        from brynq.config_manager import export_config
        cfg = export_config()
        assert cfg["rate_limit.max_per_minute"] == 100
        assert cfg["platform.name"] == "Brynq"

    def test_export_includes_explicit(self):
        from brynq.config_manager import set, export_config
        set("custom.key", "val")
        cfg = export_config()
        assert cfg["custom.key"] == "val"

    def test_import_merge(self):
        from brynq.config_manager import set, import_config, get
        set("existing.key", "keep_me")
        import_config({"new.key": "added"}, merge=True)
        assert get("existing.key") == "keep_me"
        assert get("new.key") == "added"

    def test_import_replace(self):
        from brynq.config_manager import set, import_config, get
        set("existing.key", "gone")
        import_config({"new.key": "only"}, merge=False)
        # Explicit config replaced — existing.key no longer in stored config
        # (but it was never a default, so get returns None)
        assert get("existing.key") is None
        assert get("new.key") == "only"

    def test_import_validation(self):
        from brynq.config_manager import import_config
        result = import_config({"rate_limit.max_per_minute": -5})
        assert len(result["errors"]) == 1
        assert "rate_limit.max_per_minute" not in result["applied"]

    def test_import_creates_history(self):
        from brynq.config_manager import import_config, get_history
        import_config({"a.key": 1, "b.key": 2})
        history = get_history()
        assert len(history) == 2

    def test_roundtrip(self):
        from brynq.config_manager import set, export_config, import_config, get
        set("platform.name", "RoundTrip")
        set("rate_limit.max_per_minute", 50)
        exported = export_config()
        # Wipe and re-import
        import_config({}, merge=False)
        import_config(exported, merge=False)
        assert get("platform.name") == "RoundTrip"
        assert get("rate_limit.max_per_minute") == 50
