"""Tests for FastAPI middleware."""

import pytest
from fastapi import Depends, FastAPI, Request
from httpx import ASGITransport, AsyncClient
from pytest_httpx import HTTPXMock

from vikdata_auth.middleware import (
    AuthDependency,
    JWTValidationMiddleware,
    get_current_user,
    get_tenant_id,
    get_user_id,
)
from vikdata_auth.jwt import TokenClaims


class TestJWTValidationMiddleware:
    """Tests for JWTValidationMiddleware."""

    @pytest.fixture
    def jwks_url(self):
        return "https://api.vikdata.com/.well-known/jwks.json"

    @pytest.fixture
    def app_with_middleware(self, jwks_url):
        """Create FastAPI app with JWT middleware."""
        app = FastAPI()
        app.add_middleware(
            JWTValidationMiddleware,
            jwks_url=jwks_url,
            exclude_paths=["/health"],
            exclude_prefixes=["/public"],
        )

        @app.get("/health")
        async def health():
            return {"status": "ok"}

        @app.get("/public/docs")
        async def public_docs():
            return {"docs": "public"}

        @app.get("/protected")
        async def protected(request: Request):
            return {
                "tenant_id": request.state.tenant_id,
                "user_id": request.state.user_id,
                "permissions": request.state.permissions,
            }

        @app.get("/user")
        async def get_user(request: Request):
            user = request.state.user
            return {
                "sub": user.sub,
                "email": user.email,
            }

        return app

    @pytest.mark.asyncio
    async def test_excluded_path_skips_auth(self, app_with_middleware):
        """Health endpoint should not require auth."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_middleware),
            base_url="http://test",
        ) as client:
            response = await client.get("/health")
            assert response.status_code == 200
            assert response.json() == {"status": "ok"}

    @pytest.mark.asyncio
    async def test_excluded_prefix_skips_auth(self, app_with_middleware):
        """Public prefix should not require auth."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_middleware),
            base_url="http://test",
        ) as client:
            response = await client.get("/public/docs")
            assert response.status_code == 200
            assert response.json() == {"docs": "public"}

    @pytest.mark.asyncio
    async def test_missing_auth_header(self, app_with_middleware):
        """Protected endpoint rejects missing auth header."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_middleware),
            base_url="http://test",
        ) as client:
            response = await client.get("/protected")
            assert response.status_code == 401
            assert "Missing Authorization" in response.json()["message"]

    @pytest.mark.asyncio
    async def test_invalid_auth_header_format(self, app_with_middleware):
        """Protected endpoint rejects invalid header format."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_middleware),
            base_url="http://test",
        ) as client:
            response = await client.get(
                "/protected",
                headers={"Authorization": "Basic dXNlcjpwYXNz"},
            )
            assert response.status_code == 401
            assert "Invalid Authorization header" in response.json()["message"]

    @pytest.mark.asyncio
    async def test_valid_token_populates_state(
        self, app_with_middleware, jwks_url, jwks, valid_token, httpx_mock: HTTPXMock
    ):
        """Valid token populates request.state."""
        httpx_mock.add_response(url=jwks_url, json=jwks)

        async with AsyncClient(
            transport=ASGITransport(app=app_with_middleware),
            base_url="http://test",
        ) as client:
            response = await client.get(
                "/protected",
                headers={"Authorization": f"Bearer {valid_token}"},
            )
            assert response.status_code == 200
            data = response.json()
            assert "tenant_id" in data
            assert "user_id" in data
            assert "permissions" in data

    @pytest.mark.asyncio
    async def test_user_object_in_state(
        self, app_with_middleware, jwks_url, jwks, create_token, httpx_mock: HTTPXMock
    ):
        """Full TokenClaims object is available in request.state.user."""
        httpx_mock.add_response(url=jwks_url, json=jwks)

        token = create_token(email="test@example.com")

        async with AsyncClient(
            transport=ASGITransport(app=app_with_middleware),
            base_url="http://test",
        ) as client:
            response = await client.get(
                "/user",
                headers={"Authorization": f"Bearer {token}"},
            )
            assert response.status_code == 200
            data = response.json()
            assert data["email"] == "test@example.com"


class TestAuthDependency:
    """Tests for AuthDependency."""

    @pytest.fixture
    def jwks_url(self):
        return "https://api.vikdata.com/.well-known/jwks.json"

    @pytest.fixture
    def app_with_dependency(self, jwks_url):
        """Create FastAPI app with auth dependency."""
        app = FastAPI()
        auth = AuthDependency(jwks_url=jwks_url)

        @app.get("/protected")
        async def protected(claims: TokenClaims = Depends(auth)):
            return {"user_id": claims.user_id, "tenant_id": claims.tenant_id}

        return app

    @pytest.mark.asyncio
    async def test_missing_auth_raises_401(self, app_with_dependency):
        """Missing auth raises 401."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_dependency),
            base_url="http://test",
        ) as client:
            response = await client.get("/protected")
            assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_valid_token_returns_claims(
        self, app_with_dependency, jwks_url, jwks, valid_token, httpx_mock: HTTPXMock
    ):
        """Valid token returns claims."""
        httpx_mock.add_response(url=jwks_url, json=jwks)

        async with AsyncClient(
            transport=ASGITransport(app=app_with_dependency),
            base_url="http://test",
        ) as client:
            response = await client.get(
                "/protected",
                headers={"Authorization": f"Bearer {valid_token}"},
            )
            assert response.status_code == 200
            data = response.json()
            assert "user_id" in data
            assert "tenant_id" in data


class TestHelperDependencies:
    """Tests for helper dependencies."""

    @pytest.fixture
    def app_with_helpers(self):
        """Create FastAPI app with helper dependencies."""
        app = FastAPI()

        @app.get("/user")
        async def get_user_endpoint(user: TokenClaims = Depends(get_current_user)):
            return {"user_id": user.user_id}

        @app.get("/tenant")
        async def get_tenant_endpoint(tenant_id: str = Depends(get_tenant_id)):
            return {"tenant_id": tenant_id}

        @app.get("/user-id")
        async def get_user_id_endpoint(user_id: str = Depends(get_user_id)):
            return {"user_id": user_id}

        return app

    @pytest.mark.asyncio
    async def test_get_current_user_not_authenticated(self, app_with_helpers):
        """get_current_user raises 401 when not authenticated."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_helpers),
            base_url="http://test",
        ) as client:
            response = await client.get("/user")
            assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_get_tenant_id_not_authenticated(self, app_with_helpers):
        """get_tenant_id raises 401 when not authenticated."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_helpers),
            base_url="http://test",
        ) as client:
            response = await client.get("/tenant")
            assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_get_user_id_not_authenticated(self, app_with_helpers):
        """get_user_id raises 401 when not authenticated."""
        async with AsyncClient(
            transport=ASGITransport(app=app_with_helpers),
            base_url="http://test",
        ) as client:
            response = await client.get("/user-id")
            assert response.status_code == 401
