# Copyright 2024 Heinrich Krupp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Tests for OAuth middleware and API key authentication.

Validates X-API-Key header, query parameter, and Bearer token authentication methods.
"""

import pytest
import pytest_asyncio
from fastapi import Request
from fastapi.testclient import TestClient
import os

@pytest_asyncio.fixture
async def temp_storage(temp_db_path):
    """Create and initialize temporary storage."""
    from mcp_memory_service.storage.sqlite_vec import SqliteVecMemoryStorage
    from mcp_memory_service.web.dependencies import set_storage

    db_path = os.path.join(temp_db_path, "test.db")
    storage = SqliteVecMemoryStorage(db_path)
    await storage.initialize()
    set_storage(storage)
    yield storage
    await storage.close()


@pytest.fixture
def client(temp_storage):
    """Create FastAPI test client with initialized storage and auth config."""
    import importlib
    from mcp_memory_service import config
    from mcp_memory_service.web.app import app

    # Store original values
    original_api_key = config.API_KEY
    original_oauth_enabled = config.OAUTH_ENABLED
    original_allow_anonymous = config.ALLOW_ANONYMOUS_ACCESS

    # Set test configuration directly on the module
    config.API_KEY = "test-secret-key-12345"
    config.OAUTH_ENABLED = False
    config.ALLOW_ANONYMOUS_ACCESS = False

    # Reload the middleware module to pick up new config
    from mcp_memory_service.web.oauth import middleware
    importlib.reload(middleware)

    # Reload the app to pick up new middleware
    from mcp_memory_service.web import app as app_module
    importlib.reload(app_module)

    # Get the reloaded app
    from mcp_memory_service.web.app import app as reloaded_app

    yield TestClient(reloaded_app)

    # Restore original configuration
    config.API_KEY = original_api_key
    config.OAUTH_ENABLED = original_oauth_enabled
    config.ALLOW_ANONYMOUS_ACCESS = original_allow_anonymous


class TestAPIKeyAuthentication:
    """Tests for API key authentication methods."""

    @pytest.mark.asyncio
    async def test_no_auth_returns_401(self, client):
        """Test that requests without authentication return 401."""
        response = client.get("/api/memories")
        assert response.status_code == 401
        assert "error_description" in response.json()["detail"]
        assert "API key" in response.json()["detail"]["error_description"]

    @pytest.mark.asyncio
    async def test_x_api_key_header_success(self, client):
        """Test successful authentication with X-API-Key header."""
        response = client.get(
            "/api/memories",
            headers={"X-API-Key": "test-secret-key-12345"}
        )
        assert response.status_code == 200
        data = response.json()
        assert "memories" in data or "total" in data

    @pytest.mark.asyncio
    async def test_x_api_key_header_wrong_key(self, client):
        """Test that wrong API key returns 401."""
        response = client.get(
            "/api/memories",
            headers={"X-API-Key": "wrong-key"}
        )
        assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_query_parameter_success(self, client):
        """Test successful authentication with query parameter."""
        response = client.get("/api/memories?api_key=test-secret-key-12345")
        assert response.status_code == 200
        data = response.json()
        assert "memories" in data or "total" in data

    @pytest.mark.asyncio
    async def test_query_parameter_wrong_key(self, client):
        """Test that wrong query parameter returns 401."""
        response = client.get("/api/memories?api_key=wrong-key")
        assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_bearer_token_fallback(self, client):
        """Test backward compatible Bearer token authentication."""
        response = client.get(
            "/api/memories",
            headers={"Authorization": "Bearer test-secret-key-12345"}
        )
        assert response.status_code == 200
        data = response.json()
        assert "memories" in data or "total" in data

    @pytest.mark.asyncio
    async def test_bearer_token_wrong_key(self, client):
        """Test that wrong Bearer token returns 401."""
        response = client.get(
            "/api/memories",
            headers={"Authorization": "Bearer wrong-key"}
        )
        assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_case_sensitive_header(self, client):
        """Test that X-API-Key header is case-insensitive (HTTP standard)."""
        # FastAPI/Starlette normalizes headers to lowercase
        response = client.get(
            "/api/memories",
            headers={"x-api-key": "test-secret-key-12345"}  # lowercase
        )
        assert response.status_code == 200


class TestAuthenticationPriority:
    """Tests for authentication method priority."""

    @pytest.mark.asyncio
    async def test_x_api_key_preferred_over_query_param(self, client):
        """Test that X-API-Key header is checked before query parameter."""
        # Send both, but header has correct key
        response = client.get(
            "/api/memories?api_key=wrong-key",
            headers={"X-API-Key": "test-secret-key-12345"}
        )
        assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_bearer_token_before_no_credentials(self, client):
        """Test Bearer token is checked first if provided."""
        response = client.get(
            "/api/memories",
            headers={"Authorization": "Bearer test-secret-key-12345"}
        )
        assert response.status_code == 200


class TestMultipleEndpoints:
    """Test that authentication works across different API endpoints."""

    @pytest.mark.parametrize("endpoint,method", [
        ("/api/health", "get"),
        ("/api/memories", "get"),
        ("/api/memories", "post"),
    ])
    @pytest.mark.asyncio
    async def test_auth_on_various_endpoints(self, client, endpoint, method):
        """Test authentication works on various endpoints."""
        if method == "get":
            response = client.get(
                endpoint,
                headers={"X-API-Key": "test-secret-key-12345"}
            )
        else:
            response = client.post(
                endpoint,
                headers={"X-API-Key": "test-secret-key-12345"},
                json={"content": "test", "tags": ["test"]}
            )

        # Health endpoint might not require auth, but others should work
        assert response.status_code in [200, 201, 422]  # 422 = validation error, not auth


class TestSecurityHeaders:
    """Test security-related headers and responses."""

    @pytest.mark.asyncio
    async def test_401_includes_www_authenticate(self, client):
        """Test that 401 responses include WWW-Authenticate header when Bearer used."""
        response = client.get(
            "/api/memories",
            headers={"Authorization": "Bearer wrong-key"}
        )
        assert response.status_code == 401
        # WWW-Authenticate header is added for Bearer token failures


class TestAnonymousAccess:
    """Test anonymous access behavior (when enabled)."""

    @pytest.mark.asyncio
    async def test_anonymous_disabled_by_default(self, client):
        """Test that anonymous access is disabled by default in test env."""
        response = client.get("/api/memories")
        assert response.status_code == 401


@pytest.mark.integration
class TestIntegrationScenarios:
    """Integration tests for real-world authentication scenarios."""

    @pytest.mark.asyncio
    async def test_api_only_deployment(self, client):
        """Test API-key-only deployment scenario (no OAuth)."""
        # This is the main use case for Issue #407
        response = client.get(
            "/api/memories",
            headers={"X-API-Key": "test-secret-key-12345"}
        )
        assert response.status_code == 200

        # Verify WebUI dashboard is accessible (no auth required for HTML)
        response = client.get("/")
        assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_script_access_with_query_param(self, client):
        """Test convenient query parameter access for scripts."""
        # Useful for quick scripts, though less secure
        response = client.get("/api/memories?api_key=test-secret-key-12345")
        assert response.status_code == 200
