"""Tests for proxy API key authentication -- ProxyKeyStore and AuthMiddleware."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
from fastapi import FastAPI, Request
from httpx import ASGITransport, AsyncClient

from llmhosts.proxy.auth import AuthMiddleware, ProxyKeyStore

if TYPE_CHECKING:
    from pathlib import Path

# -------------------------------------------------------------------
# Fixtures
# -------------------------------------------------------------------


@pytest.fixture()
def data_dir(tmp_path: Path) -> Path:
    """Temporary data directory for key storage."""
    d = tmp_path / ".llmhosts"
    d.mkdir()
    return d


@pytest.fixture()
def store(data_dir: Path) -> ProxyKeyStore:
    """A ProxyKeyStore using a temporary directory."""
    return ProxyKeyStore(data_dir)


def _make_app(store: ProxyKeyStore, *, require_auth: bool) -> FastAPI:
    """Build a minimal FastAPI app with AuthMiddleware for testing."""
    app = FastAPI()
    app.add_middleware(AuthMiddleware, key_store=store, require_auth=require_auth)

    @app.get("/health")
    async def health() -> dict[str, str]:
        return {"status": "ok"}

    @app.get("/v1/test")
    async def test_endpoint(request: Request) -> dict[str, object]:
        return {
            "authenticated": request.state.authenticated,
            "key_id": request.state.api_key_id,
        }

    return app


# -------------------------------------------------------------------
# ProxyKeyStore: create / validate
# -------------------------------------------------------------------


@pytest.mark.unit
def test_key_store_create_and_validate(store: ProxyKeyStore) -> None:
    """Creating a key and validating it should return the key_id."""
    key_id, raw_key = store.create_key("test-label")
    assert isinstance(key_id, str)
    assert len(key_id) > 0
    assert isinstance(raw_key, str)
    assert len(raw_key) > 0

    result = store.validate_key(raw_key)
    assert result == key_id


@pytest.mark.unit
def test_key_store_invalid_key(store: ProxyKeyStore) -> None:
    """Validating an unknown key should return None."""
    assert store.validate_key("not-a-real-key-at-all") is None


# -------------------------------------------------------------------
# ProxyKeyStore: list
# -------------------------------------------------------------------


@pytest.mark.unit
def test_key_store_list(store: ProxyKeyStore) -> None:
    """Listing keys should return metadata for all keys without raw keys."""
    key_id_1, _ = store.create_key("alpha")
    key_id_2, _ = store.create_key("beta")

    keys = store.list_keys()
    assert len(keys) == 2

    ids = {k["id"] for k in keys}
    assert key_id_1 in ids
    assert key_id_2 in ids

    labels = {k["label"] for k in keys}
    assert labels == {"alpha", "beta"}

    # Ensure no raw key or hash is exposed.
    for entry in keys:
        assert "hash" not in entry
        assert set(entry.keys()) == {"id", "label", "plan", "created_at", "last_used"}


# -------------------------------------------------------------------
# ProxyKeyStore: revoke
# -------------------------------------------------------------------


@pytest.mark.unit
def test_key_store_revoke(store: ProxyKeyStore) -> None:
    """Revoking a key should make it invalid."""
    key_id, raw_key = store.create_key("revoke-me")
    assert store.validate_key(raw_key) == key_id

    assert store.revoke_key(key_id) is True
    assert store.validate_key(raw_key) is None

    # Revoking again should return False.
    assert store.revoke_key(key_id) is False


# -------------------------------------------------------------------
# ProxyKeyStore: persistence
# -------------------------------------------------------------------


@pytest.mark.unit
def test_key_store_persistence(data_dir: Path) -> None:
    """Keys should survive a new store instance against the same directory."""
    store1 = ProxyKeyStore(data_dir)
    key_id, raw_key = store1.create_key("persistent")

    # Create a completely new instance pointing at the same directory.
    store2 = ProxyKeyStore(data_dir)
    assert store2.validate_key(raw_key) == key_id


# -------------------------------------------------------------------
# AuthMiddleware: auth disabled (require_auth=False)
# -------------------------------------------------------------------


@pytest.mark.unit
async def test_auth_disabled_allows_all(store: ProxyKeyStore) -> None:
    """When require_auth is False, requests without a header should pass."""
    app = _make_app(store, require_auth=False)

    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        resp = await client.get("/v1/test")

    assert resp.status_code == 200
    body = resp.json()
    assert body["authenticated"] is False
    assert body["key_id"] == "anonymous"


# -------------------------------------------------------------------
# AuthMiddleware: auth enabled (require_auth=True)
# -------------------------------------------------------------------


@pytest.mark.unit
async def test_auth_enabled_rejects_no_header(store: ProxyKeyStore) -> None:
    """When require_auth is True and no header is sent, expect 401."""
    app = _make_app(store, require_auth=True)

    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        resp = await client.get("/v1/test")

    assert resp.status_code == 401
    body = resp.json()
    assert body["error"]["code"] == "invalid_api_key"
    assert resp.headers.get("www-authenticate") == "Bearer"


@pytest.mark.unit
async def test_auth_enabled_rejects_invalid_key(store: ProxyKeyStore) -> None:
    """When require_auth is True and a bad key is sent, expect 401."""
    app = _make_app(store, require_auth=True)

    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        resp = await client.get("/v1/test", headers={"Authorization": "Bearer bad-key-value"})

    assert resp.status_code == 401
    body = resp.json()
    assert body["error"]["type"] == "authentication_error"


@pytest.mark.unit
async def test_auth_enabled_accepts_valid_key(store: ProxyKeyStore) -> None:
    """When require_auth is True and a valid key is sent, expect 200."""
    key_id, raw_key = store.create_key("valid-key")
    app = _make_app(store, require_auth=True)

    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        resp = await client.get("/v1/test", headers={"Authorization": f"Bearer {raw_key}"})

    assert resp.status_code == 200
    body = resp.json()
    assert body["authenticated"] is True
    assert body["key_id"] == key_id


# -------------------------------------------------------------------
# AuthMiddleware: exempt paths
# -------------------------------------------------------------------


@pytest.mark.unit
async def test_auth_skips_health(store: ProxyKeyStore) -> None:
    """The /health endpoint should be exempt even when auth is required."""
    app = _make_app(store, require_auth=True)

    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        resp = await client.get("/health")

    assert resp.status_code == 200
    assert resp.json()["status"] == "ok"
