"""Tests for FastAPI middleware components."""

import pytest
from unittest.mock import AsyncMock, Mock
from starlette.requests import Request
from starlette.responses import JSONResponse

from vikdata import VikdataClient
from vikdata.middleware import JWTValidationMiddleware, VikdataMiddleware, UsageMiddleware
from vikdata.usage import UsageReporter
from vikdata.exceptions import AuthenticationError
from vikdata.types import TokenClaims


@pytest.fixture
def mock_client():
    """Mock VikdataClient."""
    client = Mock(spec=VikdataClient)
    client.validate_token = AsyncMock()
    return client


@pytest.fixture
def mock_usage_reporter():
    """Mock UsageReporter."""
    reporter = Mock(spec=UsageReporter)
    reporter.record = AsyncMock()
    return reporter


@pytest.fixture
def mock_request():
    """Mock Starlette request."""
    request = Mock(spec=Request)
    request.url = Mock()
    request.url.path = "/api/v1/vessels"
    request.method = "GET"
    request.headers = {}
    request.state = Mock()
    return request


@pytest.fixture
def mock_call_next():
    """Mock call_next function."""
    async def call_next(request):
        response = Mock()
        response.status_code = 200
        return response
    return call_next


@pytest.mark.asyncio
async def test_jwt_validation_middleware_valid_token(mock_client, mock_request, mock_call_next):
    """Test JWT validation middleware with valid token."""
    # Setup
    mock_request.headers = {"Authorization": "Bearer valid-token"}
    claims = TokenClaims(
        sub="user-123",
        tenant_id="tenant-456",
        email="test@example.com",
        permissions=["vessels:read"],
        exp=9999999999,
        iat=1234567890,
        iss="vikdata",
        aud="vikdata-api",
    )
    mock_client.validate_token.return_value = claims

    # Create middleware
    app = Mock()
    middleware = JWTValidationMiddleware(app, client=mock_client)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert response.status_code == 200
    mock_client.validate_token.assert_called_once_with("valid-token")
    assert mock_request.state.user == claims
    assert mock_request.state.tenant_id == "tenant-456"
    assert mock_request.state.user_id == "user-123"
    assert mock_request.state.permissions == ["vessels:read"]


@pytest.mark.asyncio
async def test_jwt_validation_middleware_missing_header(mock_client, mock_request, mock_call_next):
    """Test JWT validation middleware with missing Authorization header."""
    # Setup - no Authorization header
    mock_request.headers = {}

    # Create middleware
    app = Mock()
    middleware = JWTValidationMiddleware(app, client=mock_client)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert isinstance(response, JSONResponse)
    assert response.status_code == 401
    mock_client.validate_token.assert_not_called()


@pytest.mark.asyncio
async def test_jwt_validation_middleware_invalid_token(mock_client, mock_request, mock_call_next):
    """Test JWT validation middleware with invalid token."""
    # Setup
    mock_request.headers = {"Authorization": "Bearer invalid-token"}
    mock_client.validate_token.side_effect = AuthenticationError("Invalid token")

    # Create middleware
    app = Mock()
    middleware = JWTValidationMiddleware(app, client=mock_client)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert isinstance(response, JSONResponse)
    assert response.status_code == 401


@pytest.mark.asyncio
async def test_jwt_validation_middleware_excluded_paths(mock_client, mock_request, mock_call_next):
    """Test JWT validation middleware skips excluded paths."""
    # Setup
    mock_request.url.path = "/health"
    mock_request.headers = {}  # No authorization

    # Create middleware with excluded paths
    app = Mock()
    middleware = JWTValidationMiddleware(app, client=mock_client, exclude_paths=["/health", "/metrics"])

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify - should pass through without validation
    assert response.status_code == 200
    mock_client.validate_token.assert_not_called()


@pytest.mark.asyncio
async def test_vikdata_middleware_with_usage_tracking(mock_client, mock_usage_reporter, mock_request, mock_call_next):
    """Test combined middleware with usage tracking."""
    # Setup
    mock_request.headers = {"Authorization": "Bearer valid-token"}
    claims = TokenClaims(
        sub="user-123",
        tenant_id="tenant-456",
        email="test@example.com",
        permissions=["vessels:read"],
        exp=9999999999,
        iat=1234567890,
        iss="vikdata",
        aud="vikdata-api",
    )
    mock_client.validate_token.return_value = claims

    # Create middleware
    app = Mock()
    middleware = VikdataMiddleware(app, client=mock_client, usage_reporter=mock_usage_reporter)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert response.status_code == 200
    mock_client.validate_token.assert_called_once()
    mock_usage_reporter.record.assert_called_once()

    # Check usage recording call
    call_args = mock_usage_reporter.record.call_args
    assert call_args.kwargs["tenant_id"] == "tenant-456"
    assert call_args.kwargs["endpoint"] == "/api/v1/vessels"
    assert call_args.kwargs["method"] == "GET"
    assert "latency_ms" in call_args.kwargs
    assert call_args.kwargs["status_code"] == 200


@pytest.mark.asyncio
async def test_vikdata_middleware_without_usage_tracking(mock_client, mock_request, mock_call_next):
    """Test combined middleware without usage tracking."""
    # Setup
    mock_request.headers = {"Authorization": "Bearer valid-token"}
    claims = TokenClaims(
        sub="user-123",
        tenant_id="tenant-456",
        email="test@example.com",
        permissions=["vessels:read"],
        exp=9999999999,
        iat=1234567890,
        iss="vikdata",
        aud="vikdata-api",
    )
    mock_client.validate_token.return_value = claims

    # Create middleware without usage reporter
    app = Mock()
    middleware = VikdataMiddleware(app, client=mock_client, usage_reporter=None)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert response.status_code == 200
    mock_client.validate_token.assert_called_once()


@pytest.mark.asyncio
async def test_usage_middleware_tracks_requests(mock_usage_reporter, mock_request, mock_call_next):
    """Test usage middleware tracks requests."""
    # Setup
    mock_request.state.tenant_id = "tenant-456"

    # Create middleware
    app = Mock()
    middleware = UsageMiddleware(app, usage_reporter=mock_usage_reporter)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert response.status_code == 200
    mock_usage_reporter.record.assert_called_once()

    # Check recording call
    call_args = mock_usage_reporter.record.call_args
    assert call_args.kwargs["tenant_id"] == "tenant-456"
    assert call_args.kwargs["endpoint"] == "/api/v1/vessels"
    assert call_args.kwargs["method"] == "GET"


@pytest.mark.asyncio
async def test_usage_middleware_without_tenant_id(mock_usage_reporter, mock_request, mock_call_next):
    """Test usage middleware skips tracking without tenant_id."""
    # Setup - no tenant_id in request.state
    mock_request.state = Mock(spec=[])  # Empty state

    # Create middleware
    app = Mock()
    middleware = UsageMiddleware(app, usage_reporter=mock_usage_reporter)

    # Execute
    response = await middleware.dispatch(mock_request, mock_call_next)

    # Verify
    assert response.status_code == 200
    mock_usage_reporter.record.assert_not_called()
