"""
Tests for AutoPIL API key authentication

Coverage:
  - Missing key returns 401
  - Invalid key returns 403
  - Revoked key returns 403
  - Valid key grants access
  - /health is exempt from auth
  - POST /v1/keys creates a usable key
  - GET  /v1/keys lists keys (no plaintext)
  - DELETE /v1/keys/{id} revokes a key
  - Revoked key is immediately rejected
"""

import uuid

import pytest
from fastapi.testclient import TestClient

from autopil.api.app import create_app
from autopil.api.key_store import KeyStore


# ── helpers ───────────────────────────────────────────────────────────────────

def unauth_client(policy_file, audit_db) -> TestClient:
    """A client with NO API key set."""
    app = create_app(policy_path=policy_file, audit_db=audit_db)
    return TestClient(app, raise_server_exceptions=True)


# ── missing / invalid key ─────────────────────────────────────────────────────

class TestUnauthorized:
    def test_missing_key_returns_401(self, policy_file, audit_db):
        c = unauth_client(policy_file, audit_db)
        r = c.get("/v1/policies")
        assert r.status_code == 401

    def test_invalid_key_returns_403(self, policy_file, audit_db):
        c = unauth_client(policy_file, audit_db)
        r = c.get("/v1/policies", headers={"X-API-Key": "apl_notavalidkey"})
        assert r.status_code == 403

    def test_garbage_key_returns_403(self, policy_file, audit_db):
        c = unauth_client(policy_file, audit_db)
        r = c.get("/v1/policies", headers={"X-API-Key": "totally-wrong"})
        assert r.status_code == 403

    def test_all_v1_routes_require_auth(self, policy_file, audit_db):
        c = unauth_client(policy_file, audit_db)
        routes = [
            ("GET",  "/v1/policies"),
            ("GET",  "/v1/audit/events"),
            ("GET",  "/v1/audit/stats"),
            ("GET",  "/v1/keys"),
        ]
        for method, path in routes:
            r = c.request(method, path)
            assert r.status_code in (401, 403), f"Expected auth error on {method} {path}, got {r.status_code}"


# ── health exempt ─────────────────────────────────────────────────────────────

class TestHealthExempt:
    def test_health_requires_no_key(self, policy_file, audit_db):
        c = unauth_client(policy_file, audit_db)
        r = c.get("/health")
        assert r.status_code == 200
        assert r.json()["status"] == "ok"


# ── valid key grants access ───────────────────────────────────────────────────

class TestValidKey:
    def test_valid_key_allows_access(self, client):
        r = client.get("/v1/policies")
        assert r.status_code == 200

    def test_valid_key_on_evaluate(self, client):
        r = client.post("/v1/context/evaluate", json={
            "query": "test",
            "agent_role": "analyst",
            "user_id": "u1",
            "source_id": "reports",
            "sensitivity_level": "low",
            "session_id": str(uuid.uuid4()),
        })
        assert r.status_code == 200


# ── key management ────────────────────────────────────────────────────────────

class TestKeyManagement:
    def test_create_key_returns_plaintext_once(self, client):
        r = client.post("/v1/keys", json={"name": "partner_a"})
        assert r.status_code == 200
        body = r.json()
        assert body["name"]    == "partner_a"
        assert body["key"].startswith("apl_")
        assert "key_id"     in body
        assert "created_at" in body

    def test_new_key_is_immediately_usable(self, policy_file, audit_db, client):
        r = client.post("/v1/keys", json={"name": "new_key"})
        new_key = r.json()["key"]

        app = create_app(policy_path=policy_file, audit_db=audit_db)
        new_client = TestClient(app, headers={"X-API-Key": new_key})
        assert new_client.get("/v1/policies").status_code == 200

    def test_list_keys_never_returns_plaintext(self, client):
        client.post("/v1/keys", json={"name": "k1"})
        keys = client.get("/v1/keys").json()
        for k in keys:
            assert "key" not in k
            assert "key_hash" not in k
            assert "key_id" in k
            assert "name"   in k
            assert "is_active" in k

    def test_list_keys_includes_all_created(self, client):
        before = len(client.get("/v1/keys").json())
        client.post("/v1/keys", json={"name": "x"})
        client.post("/v1/keys", json={"name": "y"})
        after = len(client.get("/v1/keys").json())
        assert after == before + 2

    def test_revoke_key_returns_ok(self, client):
        key_id = client.post("/v1/keys", json={"name": "to_revoke"}).json()["key_id"]
        r = client.delete(f"/v1/keys/{key_id}")
        assert r.status_code == 200
        assert r.json()["status"] == "revoked"

    def test_revoked_key_is_immediately_rejected(self, policy_file, audit_db, client):
        r = client.post("/v1/keys", json={"name": "ephemeral"})
        new_key = r.json()["key"]
        key_id  = r.json()["key_id"]

        # Confirm it works before revoking
        app = create_app(policy_path=policy_file, audit_db=audit_db)
        new_client = TestClient(app, headers={"X-API-Key": new_key})
        assert new_client.get("/v1/policies").status_code == 200

        # Revoke it
        client.delete(f"/v1/keys/{key_id}")

        # Same key should now be rejected
        assert new_client.get("/v1/policies").status_code == 403

    def test_revoke_unknown_key_returns_404(self, client):
        r = client.delete("/v1/keys/doesnotexist")
        assert r.status_code == 404

    def test_revoked_key_still_appears_in_list(self, client):
        key_id = client.post("/v1/keys", json={"name": "check_list"}).json()["key_id"]
        client.delete(f"/v1/keys/{key_id}")
        keys = client.get("/v1/keys").json()
        match = next((k for k in keys if k["key_id"] == key_id), None)
        assert match is not None
        assert match["is_active"] is False
