"""Tests for Security dependency injection system."""

import msgspec
from starlette.testclient import TestClient

from crimsy import Crimsy, HTTPException, Router, Security


async def test_simple_security() -> None:
    """Test basic security dependency injection."""
    app = Crimsy()
    router = Router(prefix="/")

    async def verify_token(token: str = "") -> str:
        if token != "valid-token":
            raise HTTPException(status_code=401, message="Invalid token")
        return token

    @router.get("/")
    async def handler(token: str = Security(verify_token)) -> dict[str, str]:
        return {"token": token}

    app.add_router(router)

    client = TestClient(app)
    response = client.get("/?token=valid-token")

    assert response.status_code == 200
    assert response.json() == {"token": "valid-token"}


async def test_security_with_invalid_token() -> None:
    """Test security dependency with invalid credentials."""
    app = Crimsy()
    router = Router(prefix="/")

    async def verify_token(token: str = "") -> str:
        if token != "valid-token":
            raise HTTPException(status_code=401, message="Unauthorized")
        return token

    @router.get("/secure")
    async def handler(token: str = Security(verify_token)) -> dict[str, str]:
        return {"data": "secure"}

    app.add_router(router)

    client = TestClient(app)
    response = client.get("/secure?token=invalid")

    assert response.status_code == 401
    assert response.json() == {"error": "Unauthorized"}


async def test_security_with_user_object() -> None:
    """Test security dependency returning a user object."""
    app = Crimsy()
    router = Router(prefix="/")

    class User(msgspec.Struct):
        id: int
        name: str

    async def get_current_user(user_id: int = 1) -> User:
        if user_id == 0:
            raise HTTPException(status_code=403, message="Forbidden")
        return User(id=user_id, name=f"User{user_id}")

    @router.get("/profile")
    async def get_profile(user: User = Security(get_current_user)) -> User:
        return user

    app.add_router(router)

    client = TestClient(app)
    response = client.get("/profile?user_id=42")

    assert response.status_code == 200
    data = response.json()
    assert data["id"] == 42
    assert data["name"] == "User42"


async def test_security_openapi_schema() -> None:
    """Test that Security appears in OpenAPI schema."""
    app = Crimsy()
    router = Router(prefix="/api")

    async def verify_token(token: str = "") -> str:
        return token

    @router.get("/secure")
    async def handler(name: str, token: str = Security(verify_token)) -> dict[str, str]:
        """Secure endpoint requiring authentication."""
        return {"name": name, "token": token}

    app.add_router(router)

    client = TestClient(app)

    # Get OpenAPI schema
    response = client.get("/openapi.json")
    assert response.status_code == 200
    schema = response.json()

    # Check that the endpoint is documented
    assert "/api/secure" in schema["paths"]
    endpoint = schema["paths"]["/api/secure"]["get"]

    # Check that 'name' parameter appears but not 'token' (security dependency)
    assert "parameters" in endpoint
    param_names = [p["name"] for p in endpoint["parameters"]]
    assert "name" in param_names
    assert "token" not in param_names  # Security deps should not appear as parameters

    # Check that security requirements are present
    assert "security" in endpoint
    assert len(endpoint["security"]) > 0
    # Should have a security requirement with the function name as key
    assert "verify_token" in endpoint["security"][0]

    # Check that security schemes are in components
    assert "components" in schema
    assert "securitySchemes" in schema["components"]
    assert "verify_token" in schema["components"]["securitySchemes"]
    # Should be HTTP bearer by default
    security_scheme = schema["components"]["securitySchemes"]["verify_token"]
    assert security_scheme["type"] == "http"
    assert security_scheme["scheme"] == "bearer"


async def test_security_with_custom_scheme_name() -> None:
    """Test security with custom scheme name."""
    app = Crimsy()
    router = Router(prefix="/")

    async def verify_api_key(api_key: str = "") -> str:
        if api_key != "secret":
            raise HTTPException(status_code=403, message="Invalid API key")
        return api_key

    @router.get("/data")
    async def handler(
        key: str = Security(verify_api_key, scheme_name="ApiKeyAuth"),
    ) -> dict[str, str]:
        """Endpoint with custom security scheme name."""
        return {"data": "secure"}

    app.add_router(router)

    client = TestClient(app)

    # Get OpenAPI schema
    response = client.get("/openapi.json")
    assert response.status_code == 200
    schema = response.json()

    # Check that custom scheme name is used
    endpoint = schema["paths"]["/data"]["get"]
    assert "security" in endpoint
    assert "ApiKeyAuth" in endpoint["security"][0]

    # Check that security scheme has the custom name
    assert "ApiKeyAuth" in schema["components"]["securitySchemes"]


async def test_multiple_security_dependencies() -> None:
    """Test multiple security dependencies in a single endpoint."""
    app = Crimsy()
    router = Router(prefix="/")

    async def verify_token(token: str = "") -> str:
        return token

    async def verify_scope(scope: str = "") -> str:
        return scope

    @router.get("/admin")
    async def handler(
        token: str = Security(verify_token),
        scope: str = Security(verify_scope),
    ) -> dict[str, str]:
        return {"token": token, "scope": scope}

    app.add_router(router)

    client = TestClient(app)

    # Get OpenAPI schema
    response = client.get("/openapi.json")
    assert response.status_code == 200
    schema = response.json()

    endpoint = schema["paths"]["/admin"]["get"]

    # Should have two security requirements
    assert "security" in endpoint
    assert len(endpoint["security"]) == 2
    assert "verify_token" in endpoint["security"][0]
    assert "verify_scope" in endpoint["security"][1]

    # Both should be in security schemes
    assert "verify_token" in schema["components"]["securitySchemes"]
    assert "verify_scope" in schema["components"]["securitySchemes"]


async def test_security_with_nested_dependencies() -> None:
    """Test security dependency with nested dependencies."""
    app = Crimsy()
    router = Router(prefix="/")

    async def get_db_connection() -> str:
        return "db_connection"

    async def get_current_user(db: str = Security(get_db_connection)) -> dict[str, str]:
        return {"user": "admin", "db": db}

    @router.get("/users")
    async def handler(
        user: dict[str, str] = Security(get_current_user),
    ) -> dict[str, str]:
        return user

    app.add_router(router)

    client = TestClient(app)
    response = client.get("/users")

    assert response.status_code == 200
    data = response.json()
    assert data["user"] == "admin"
    assert data["db"] == "db_connection"


async def test_security_repr() -> None:
    """Test Security __repr__ method."""
    from crimsy.dependencies import _SecurityClass

    async def verify_token() -> str:
        return "token"

    security = _SecurityClass(verify_token)
    assert repr(security) == "Security(verify_token)"


async def test_sync_security_dependency() -> None:
    """Test synchronous security dependency function."""
    app = Crimsy()
    router = Router(prefix="/")

    def verify_api_key(api_key: str = "default") -> str:
        return api_key

    @router.get("/")
    async def handler(key: str = Security(verify_api_key)) -> dict[str, str]:
        return {"key": key}

    app.add_router(router)

    client = TestClient(app)
    response = client.get("/?api_key=test-key")

    assert response.status_code == 200
    assert response.json() == {"key": "test-key"}
