"""End-to-end coverage of /api/settings-meta (the agent-editable Settings tool).

Drives the real FastAPI route with a real in-memory AgentSession so the guard
runs against an actual run's model, exactly as it will in production. The unit
invariant lives in test_settings_meta_guard.py; this test proves the wiring:
redaction on read, the three write refusals, and a benign write actually landing.
"""

from __future__ import annotations

import pytest
from fastapi.testclient import TestClient

from backend.main import app


@pytest.mark.asyncio
async def test_second_wall_restores_protected_credential_even_if_body_blanks_it():
    """Defense in depth: even if a write reaches apply_settings_update with the
    live credential blanked (a guard slip upstream), the second-wall restore puts
    it back. Proves the api-key guard isn't a single point of failure."""
    from backend.apps.settings.settings import (
        apply_settings_update, settings_write_lock, load_settings, save_settings,
    )
    original = load_settings().model_copy(deep=True)
    try:
        s = load_settings()
        s.anthropic_api_key = "sk-live-KEEP-ME"
        save_settings(s)
        # A body that (as if a guard bug let it through) clears the live key.
        body = load_settings()
        body.anthropic_api_key = ""
        async with settings_write_lock():
            saved = await apply_settings_update(body, protect_fields={"anthropic_api_key"})
        assert saved.anthropic_api_key == "sk-live-KEEP-ME", "second wall failed to restore"
        assert load_settings().anthropic_api_key == "sk-live-KEEP-ME"
        # And a NON-protected blank still goes through (only the protected one is restored).
        body2 = load_settings()
        body2.openai_api_key = ""
        async with settings_write_lock():
            await apply_settings_update(body2, protect_fields={"anthropic_api_key"})
        assert not load_settings().openai_api_key
    finally:
        save_settings(original)


@pytest.fixture
def client():
    import backend.auth as auth_mod
    if not auth_mod.TOKEN:
        import secrets
        auth_mod.TOKEN = secrets.token_urlsafe(32)
    return TestClient(app, headers={"Authorization": f"Bearer {auth_mod.TOKEN}"})


@pytest.fixture
def reset_settings():
    from backend.apps.settings.settings import load_settings, save_settings
    original = load_settings().model_copy(deep=True)
    yield
    save_settings(original)


@pytest.fixture
def session_on_anthropic_key():
    """A live run on opus-4-8 in own_key mode with an Anthropic key set: the
    Anthropic key powers it. Registered in agent_manager so the guard sees it."""
    from backend.apps.agents.agent_manager import agent_manager
    from backend.apps.agents.core.models import AgentSession
    from backend.apps.settings.settings import load_settings, save_settings

    s = load_settings()
    s.connection_mode = "own_key"
    s.anthropic_api_key = "sk-ant-test-LIVE"
    s.openai_api_key = "sk-openai-test-OTHER"
    save_settings(s)

    sess = AgentSession(id="settings-meta-test", name="t", model="opus-4-8")
    agent_manager.sessions["settings-meta-test"] = sess
    yield "settings-meta-test"
    agent_manager.sessions.pop("settings-meta-test", None)


def test_read_redacts_every_secret(client, reset_settings):
    r = client.post("/api/settings-meta/read", json={})
    assert r.status_code == 200, r.text
    settings = r.json()["settings"]
    # Secret fields come back as state, never a raw string value.
    for field in ("anthropic_api_key", "openai_api_key", "claude_subscription_token", "openswarm_bearer_token"):
        if field in settings:
            assert isinstance(settings[field], dict), f"{field} leaked as a raw value"
            assert "configured" in settings[field]
    # A non-secret field is passed through untouched.
    assert settings["theme"] in ("dark", "light")


def test_benign_write_applies(client, reset_settings):
    r = client.post("/api/settings-meta/write", json={"changes": {"theme": "light"}})
    assert r.status_code == 200, r.text
    assert r.json()["outcomes"]["theme"]["status"] == "applied"
    from backend.apps.settings.settings import load_settings
    assert load_settings().theme == "light"


def test_unknown_and_server_owned_fields_are_refused(client, reset_settings):
    r = client.post("/api/settings-meta/write", json={"changes": {
        "not_a_real_field": 1,
        "connection_mode": "openswarm-pro",
        "openswarm_bearer_token": "forged",
    }})
    assert r.status_code == 200, r.text
    out = r.json()["outcomes"]
    assert out["not_a_real_field"]["status"] == "unknown"
    assert out["connection_mode"]["status"] == "refused"
    assert out["openswarm_bearer_token"]["status"] == "refused"
    # And the server-owned field is genuinely untouched on disk.
    from backend.apps.settings.settings import load_settings
    assert load_settings().connection_mode != "openswarm-pro"


def test_cannot_suicide_but_disconnects_others(client, reset_settings, session_on_anthropic_key):
    """The spec scenario over HTTP: run on the Anthropic key, asked to clear
    every model key + flip a benign setting. It must refuse the live key,
    clear the other one, and apply the benign change, all in one call."""
    sid = session_on_anthropic_key
    r = client.post("/api/settings-meta/write", json={
        "parent_session_id": sid,
        "changes": {
            "anthropic_api_key": "",
            "openai_api_key": "",
            "theme": "light",
        },
    })
    assert r.status_code == 200, r.text
    out = r.json()["outcomes"]
    assert out["anthropic_api_key"]["status"] == "refused", "blanked the live credential!"
    assert "powering this run" in out["anthropic_api_key"]["reason"]
    assert out["openai_api_key"]["status"] == "applied"
    assert out["theme"]["status"] == "applied"

    from backend.apps.settings.settings import load_settings
    s = load_settings()
    assert s.anthropic_api_key == "sk-ant-test-LIVE", "live key was cleared despite refusal"
    assert not s.openai_api_key, "the other provider's key should have been cleared"
    assert s.theme == "light"
