"""Tests from the acme-crm-mcp contract tests. Run: pytest test_tenant_practice.py"""
from tenant_practice import AUDIENCE, ISSUER, RateLimiter, authenticate, error_rate, handle

NOW = 1_000_000.0


def claims(**over):
    c = {"iss": ISSUER, "aud": AUDIENCE, "exp": NOW + 3600, "org_id": "acme",
         "client_id": "acme-agent", "scope": "crm:read crm:write"}
    c.update(over)
    return c


def test_tenant_comes_from_the_token():
    status, auth = authenticate(True, claims(), NOW)
    assert status == 200
    assert auth == {"tenant": "acme", "client": "acme-agent", "scopes": ["crm:read", "crm:write"]}


def test_every_bad_token_is_a_401():
    assert authenticate(True, None, NOW)[0] == 401                                      # no token
    assert authenticate(True, claims(exp=NOW - 60), NOW)[0] == 401                     # expired
    assert authenticate(True, claims(aud="https://other-api.example"), NOW)[0] == 401  # other server
    assert authenticate(False, claims(), NOW)[0] == 401                                # forged
    assert authenticate(True, claims(iss="https://evil.example"), NOW)[0] == 401       # other issuer
    no_org = claims()
    del no_org["org_id"]
    assert authenticate(True, no_org, NOW)[0] == 401


def test_burst_then_429_with_retry_after():
    rl = RateLimiter(per_minute=120, burst=20)
    results = [rl.allow("acme", NOW) for _ in range(25)]
    assert [ok for ok, _ in results] == [True] * 20 + [False] * 5
    assert results[-1] == (False, 1)                       # 2 tokens a second: retry in 1 s
    assert rl.allow("globex", NOW) == (True, 0)            # a noisy neighbour doesn't starve globex
    assert rl.allow("acme", NOW + 0.5) == (True, 0)        # half a second refills one token


def test_the_demo_tests_burst_of_5():
    rl = RateLimiter(per_minute=120, burst=5)
    assert [rl.allow("acme", NOW)[0] for _ in range(8)] == [True] * 5 + [False] * 3


def test_pipeline_order_and_audit():
    rl, audit = RateLimiter(burst=20), []
    readonly = claims(client_id="acme-readonly", scope="crm:read")
    assert handle(True, readonly, "search_contacts", rl, NOW, audit) == 200
    assert handle(True, readonly, "create_contact", rl, NOW, audit) == 403
    assert audit == [{"tenant": "acme", "client": "acme-readonly", "tool": "create_contact", "outcome": "denied"}]
    # an unknown tool passes the scope check: the MCP server answers it with a tool error
    assert handle(True, claims(), "drop_all_tables", rl, NOW, audit) == 200
    assert len(audit) == 1


def test_unauthenticated_calls_dont_spend_a_tenants_budget():
    rl, audit = RateLimiter(burst=2), []
    for _ in range(10):
        assert handle(False, claims(), "search_contacts", rl, NOW, audit) == 401
    assert handle(True, claims(), "search_contacts", rl, NOW, audit) == 200
    assert handle(True, claims(), "search_contacts", rl, NOW, audit) == 200
    assert handle(True, claims(), "search_contacts", rl, NOW, audit) == 429


def test_error_rate_counts_tool_errors_returned_as_200():
    window = [(200, False)] * 95 + [(401, False), (429, False), (200, True), (200, True), (403, False)]
    assert error_rate(window) == 0.05
    assert error_rate([(200, False)] * 99 + [(200, True)]) == 0.01
    assert error_rate([]) == 0.0
    # 2% is the paging line
    assert error_rate([(200, False)] * 97 + [(200, True)] * 3) > 0.02
