"""The outbound funnel discipline — every central call is captured, nothing escapes.

All talk to central goes through the single `bw_auth._post_json` funnel, which the
autouse `bw_calls` fixture replaces with a recorder — so no test can touch the
network, and a test can assert WHAT would have been sent. These tests prove the
discipline holds across a representative admin flow, that owner-only /sync fans out
the report_* calls, and that removing an account is IMMEDIATE: the user loses every
capability and central is told to clear them (report_access with level None).
"""

from tests.conftest import HAS_INSTANCES, as_user

# Every path the app is allowed to funnel to central. Anything outside this set (or
# not under /app/) would mean an un-stubbed or unexpected outbound call.
ALLOWED_OUTBOUND = {
    "/app/invite-user", "/app/send-reset", "/app/search-users",
    "/app/report-access", "/app/report-instances", "/app/report-impersonation",
    "/app/user-access", "/app/userinfo", "/app/notify-added", "/app/reset-password",
}


def _paths(bw_calls):
    return [path for path, _ in bw_calls]


def test_every_outbound_is_captured_and_recognized(client, kit, bw_calls):
    """Drive a representative admin flow, then assert every recorded outbound is a
    known /app/* path — i.e. it went to the recorder, not the network."""
    kit.member("alice", "admin")
    as_user(client, "rian")
    # invite (→ invite-user + report-*), a self-reset, a directory search.
    client.post("/api/bw/accounts/invite",
                json={"username": "carol", "level": "member",
                      "email": "carol@example.com"})
    client.post("/api/bw/my/send-reset")
    client.get("/api/bw/users/search", params={"q": "al"})

    assert bw_calls, "expected some outbound calls to have been recorded"
    for path in _paths(bw_calls):
        assert path.startswith("/app/"), path
        assert path in ALLOWED_OUTBOUND, path


def test_owner_sync_fans_out_report_calls(client, kit, bw_calls):
    kit.member("bob", "member")
    as_user(client, "rian")
    before = len(bw_calls)
    r = client.post("/api/bw/sync")
    assert r.status_code == 200
    new_paths = [path for path, _ in bw_calls[before:]]
    # a member exists, so their access is (re)reported
    assert "/app/report-access" in new_paths
    # the instance catalog is (re)reported only when the app has instances
    if HAS_INSTANCES:
        assert "/app/report-instances" in new_paths


def test_sync_is_owner_only(client, kit):
    kit.member("bob", "member")
    as_user(client, "bob")
    r = client.post("/api/bw/sync")
    assert r.status_code == 403
    body = r.json()
    assert body["detail"]["error_code"] == "FORBIDDEN"


def test_account_removal_is_immediate(client, kit, bw_calls):
    """Before: an admin-level member has capabilities. After DELETE: the same user
    has none (no longer a member) AND central was told to clear them."""
    kit.member("mallory", "admin")

    as_user(client, "mallory")
    assert client.get("/api/bw/me").json()["can_manage_accounts"] is True

    as_user(client, "rian")
    r = client.delete("/api/bw/accounts/mallory")
    assert r.status_code == 200

    # The user immediately has no capabilities (member row is gone).
    as_user(client, "mallory")
    me = client.get("/api/bw/me").json()
    assert me["is_owner"] is False
    assert me["can_manage_accounts"] is False
    assert me["can_add_accounts"] is False
    assert me["assignable_levels"] == []

    # Central was told to clear this user (report_access with level None).
    cleared = [payload for path, payload in bw_calls
               if path == "/app/report-access"
               and payload.get("username") == "mallory"
               and payload.get("level") is None]
    assert cleared, "expected a report_access(mallory, None) to central"
