# -*- coding: utf-8 -*-
"""Location: ./tests/e2e/test_main_apis.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

End-to-end tests for ContextForge main APIs.
This module contains comprehensive end-to-end tests for all main API endpoints in main.py.
These tests are designed to exercise the entire application stack with minimal mocking,
using only a temporary SQLite database and bypassing authentication.

The tests cover:
- Health and readiness checks
- Protocol operations (initialize, ping, notifications, completion, sampling)
- Server management (CRUD, SSE endpoints, associations with tools/resources/prompts)
- Tool management (CRUD, REST/MCP integration types, metrics)
- Resource management (CRUD, templates, caching)
- Prompt management (CRUD, template execution with arguments)
- Gateway federation (registration, connectivity)
- Root management (filesystem roots for resources)
- Utility endpoints (RPC, logging, WebSocket/SSE)
- Metrics collection and aggregation
- Version information
- Authentication requirements
- OpenAPI documentation

Each test class corresponds to a specific API group, making it easy to run
isolated test suites for specific functionality. The tests use a real SQLite
database that is created fresh for each test run, ensuring complete isolation
and reproducibility.

Note: Admin API endpoints (/admin/*) are tested separately when MCPGATEWAY_ADMIN_API_ENABLED=true

TODO:
1. Test redis
2. Test with sample MCP server(s) in test scripts
"""

# Standard
import base64

# Standard Library
import json
import os
import tempfile
import time
from typing import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import patch
from unittest.mock import patch as mock_patch

# Third-Party
from httpx import AsyncClient

# --- Test Auth Header: Use a real JWT for authenticated requests ---
import jwt
from pydantic import SecretStr
import pytest
import pytest_asyncio
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

# Patch bootstrap_db to prevent it from running during tests
with mock_patch("mcpgateway.bootstrap_db.main"):
    # First-Party
    from mcpgateway.config import settings
    from mcpgateway.db import Base
    from mcpgateway.main import app, get_db

# pytest.skip("Temporarily disabling this suite", allow_module_level=True)

# -------------------------
# Test Configuration
# -------------------------


TEST_USER = "testuser"
JWT_SECRET = "e2e-test-jwt-secret-key-with-minimum-32-bytes"  # Must match mcpgateway.config.Settings.jwt_secret_key
JWT_ALGORITHM = "HS256"  # Must match mcpgateway.config.Settings.jwt_algorithm

# Ensure test tokens use a strong signing key to avoid weak-key warnings.
if hasattr(settings.jwt_secret_key, "get_secret_value") and callable(getattr(settings.jwt_secret_key, "get_secret_value", None)):
    settings.jwt_secret_key = SecretStr(JWT_SECRET)
else:
    settings.jwt_secret_key = JWT_SECRET


def generate_test_jwt(*, is_admin: bool = False, teams=None):
    if teams is None:
        teams = []
    payload = {
        "sub": "test_user",
        "exp": int(time.time()) + 3600,
        "teams": teams,
        "is_admin": is_admin,
    }
    secret = settings.jwt_secret_key
    if hasattr(secret, "get_secret_value") and callable(getattr(secret, "get_secret_value", None)):
        secret = secret.get_secret_value()
    algorithm = settings.jwt_algorithm
    return jwt.encode(payload, secret, algorithm=algorithm)


TEST_AUTH_HEADER = {"Authorization": f"Bearer {generate_test_jwt()}"}


# -------------------------
# Fixtures
# -------------------------
@pytest_asyncio.fixture
async def temp_db(main_app_with_admin_api):
    """
    Create a temporary SQLite database for testing.

    This fixture creates a fresh database for each test, ensuring complete
    isolation between tests. The database is automatically cleaned up after
    the test completes.

    Depending on ``main_app_with_admin_api`` flips the admin API + UI
    flags on and dynamically mounts the admin router on the existing
    ``main.app`` (no reload, so module-level references stay valid).
    A handful of tests in this file exercise ``/admin/...`` endpoints
    and the root-path redirect, which both need the admin router
    mounted.
    """
    # Create temporary file for SQLite database
    db_fd, db_path = tempfile.mkstemp(suffix=".db")

    # Create engine with SQLite
    engine = create_engine(
        f"sqlite:///{db_path}",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,  # Use StaticPool for testing
    )

    # Import all model classes to ensure they're registered with Base.metadata
    # This is necessary for create_all() to create all tables
    # First-Party

    # Create all tables - use create_all for test environment to avoid migration conflicts
    Base.metadata.create_all(bind=engine)

    # Create session factory
    TestSessionLocal = sessionmaker(autocommit=False, autoflush=False, expire_on_commit=False, bind=engine)

    # Seed an admin user so PermissionService admin-bypass works for RBAC-decorated endpoints.
    # This avoids patching RBAC decorators at import time, which leaks into other test modules.
    # First-Party
    from mcpgateway.db import EmailUser

    seed_db = TestSessionLocal()
    try:
        seed_db.add(
            EmailUser(
                email="testuser@example.com",
                password_hash="not-a-real-hash",
                full_name="Test User",
                is_admin=True,
                is_active=True,
            )
        )
        seed_db.commit()
    finally:
        seed_db.close()

    # Override the get_db dependency
    def override_get_db():
        db = TestSessionLocal()
        try:
            yield db
        finally:
            db.close()

    app.dependency_overrides[get_db] = override_get_db

    # Override authentication for all tests
    # First-Party
    from mcpgateway.auth import get_current_user
    from mcpgateway.middleware.rbac import get_current_user_with_permissions
    from mcpgateway.utils.create_jwt_token import get_jwt_token
    from mcpgateway.utils.verify_credentials import require_admin_auth, require_auth

    # Local
    from tests.utils.rbac_mocks import create_mock_email_user, create_mock_user_context

    def override_auth():
        return TEST_USER

    # Create mock user for new auth system
    mock_email_user = create_mock_email_user(email="testuser@example.com", full_name="Test User", is_admin=True, is_active=True)

    # Mock admin authentication function
    async def mock_require_admin_auth():
        """Mock admin auth that returns admin email."""
        return "testuser@example.com"

    # Mock JWT token function
    async def mock_get_jwt_token():
        """Mock JWT token function."""
        return generate_test_jwt()

    # Create custom user context with real database session
    test_user_context = create_mock_user_context(email="testuser@example.com", full_name="Test User", is_admin=True)
    test_user_context_db = TestSessionLocal()
    test_user_context["db"] = test_user_context_db  # Use real database session from this fixture

    # Create a simple mock function for get_current_user_with_permissions
    async def simple_mock_user_with_permissions():
        """Simple mock that returns our test user context directly."""
        return test_user_context

    # Create a mock PermissionService that always grants permission
    # First-Party
    from mcpgateway.middleware.rbac import get_permission_service

    # Local
    from tests.utils.rbac_mocks import MockPermissionService

    def mock_get_permission_service(*args, **kwargs):
        """Return a mock permission service that always grants access."""
        return MockPermissionService(always_grant=True)

    # Override all authentication dependencies
    app.dependency_overrides[require_auth] = override_auth
    app.dependency_overrides[get_current_user] = lambda: mock_email_user
    app.dependency_overrides[require_admin_auth] = mock_require_admin_auth
    app.dependency_overrides[get_jwt_token] = mock_get_jwt_token
    app.dependency_overrides[get_current_user_with_permissions] = simple_mock_user_with_permissions
    app.dependency_overrides[get_permission_service] = mock_get_permission_service
    app.dependency_overrides[get_db] = override_get_db

    # Mock security_logger to prevent database access issues
    mock_sec_logger = MagicMock()
    mock_sec_logger.log_authentication_attempt = MagicMock(return_value=None)
    mock_sec_logger.log_security_event = MagicMock(return_value=None)
    # Patch at the middleware level where security_logger is used
    sec_patcher = patch("mcpgateway.middleware.auth_middleware.security_logger", mock_sec_logger)
    sec_patcher.start()

    yield engine

    # Cleanup
    sec_patcher.stop()
    test_user_context_db.close()
    app.dependency_overrides.clear()
    engine.dispose()
    os.close(db_fd)
    os.unlink(db_path)


@pytest_asyncio.fixture
async def client(temp_db) -> AsyncGenerator[AsyncClient, None]:
    """Create an async test client with the test database."""
    # Use httpx AsyncClient with FastAPI app
    # Third-Party
    from httpx import ASGITransport, AsyncClient

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        yield ac


@pytest.fixture
def mock_auth():
    """
    Mock authentication for tests.

    This is the only mock we use - to bypass actual JWT validation
    while still testing that endpoints require authentication.
    """
    # This fixture is now mostly redundant since we override auth in temp_db
    # but keep it for backward compatibility
    return MagicMock(return_value=TEST_USER)


@pytest_asyncio.fixture
async def mock_settings():
    """Mock settings to disable admin API and use database cache.

    Yields:
        MagicMock: Mocked settings object.
    """
    # First-Party
    from mcpgateway.config import settings as real_settings

    mock_settings = MagicMock(wraps=real_settings)

    # Override specific settings for testing
    mock_settings.cache_type = "database"
    mock_settings.mcpgateway_admin_api_enabled = False
    mock_settings.mcpgateway_ui_enabled = False
    mock_settings.auth_required = True  # Enable auth requirement for testing

    yield mock_settings


def basic_auth_header(username: str, password: str) -> dict:
    token = base64.b64encode(f"{username}:{password}".encode()).decode()
    return {"Authorization": f"Basic {token}"}


def docs_basic_auth_header() -> dict:
    """Build Basic Auth header for docs endpoints using current runtime settings."""
    password = settings.basic_auth_password
    if hasattr(password, "get_secret_value") and callable(getattr(password, "get_secret_value", None)):
        password = password.get_secret_value()
    return basic_auth_header(settings.basic_auth_user, password)


# -------------------------
# Test Utility APIs
# -------------------------
class TestDocsAndRedoc:
    @classmethod
    def setup_class(cls):
        # Enable Basic Auth for docs endpoints during these tests
        # First-Party
        from mcpgateway.config import settings

        cls._original_docs_allow_basic_auth = settings.docs_allow_basic_auth
        settings.docs_allow_basic_auth = True

    @classmethod
    def teardown_class(cls):
        # Restore original setting
        # First-Party
        from mcpgateway.config import settings

        settings.docs_allow_basic_auth = cls._original_docs_allow_basic_auth

    async def test_docs_with_basic_auth(self, client: AsyncClient):
        # Ensure Basic Auth for docs is allowed
        settings.docs_allow_basic_auth = True

        """Test /docs endpoint with Basic Auth (should return 200 if credentials are valid)."""
        headers = docs_basic_auth_header()
        response = await client.get("/docs", headers=headers)
        assert response.status_code == 200

    async def test_redoc_with_basic_auth(self, client: AsyncClient):
        """Test /redoc endpoint with Basic Auth (should return 200 if credentials are valid)."""
        # Ensure Basic Auth for docs is allowed
        settings.docs_allow_basic_auth = True

        headers = docs_basic_auth_header()
        response = await client.get("/redoc", headers=headers)
        assert response.status_code == 200


# -------------------------
# Test Health and Infrastructure
# -------------------------
class TestHealthChecks:
    async def test_cors_preflight(self, client: AsyncClient):
        """Test CORS preflight OPTIONS request on /health endpoint."""
        response = await client.options("/health", headers={"Origin": "http://localhost", "Access-Control-Request-Method": "GET"})
        assert response.status_code in [200, 204, 400]  # 400 can occur if endpoint doesn't explicitly handle OPTIONS
        if response.status_code in [200, 204]:
            assert "access-control-allow-origin" in response.headers

    """Test health check and readiness endpoints."""

    async def test_health_check(self, client: AsyncClient):
        """Test /health endpoint returns healthy status."""
        response = await client.get("/health")
        assert response.status_code == 200
        assert response.json()["status"] == "healthy"

    async def test_readiness_check(self, client: AsyncClient):
        """Test /ready endpoint returns ready status."""
        response = await client.get("/ready")
        assert response.status_code == 200
        assert response.json()["status"] == "ready"

    # FIXME
    # async def test_health_check_database_error(self, client: AsyncClient, temp_db):
    #     """Test /health endpoint when database is unavailable."""

    #     # Override get_db to raise an exception - must be a proper generator
    #     def failing_db():
    #         # This needs to be a generator that raises when next() is called
    #         def _gen():
    #             raise Exception("Database connection failed")
    #             yield  # This line is never reached but makes it a generator
    #         return _gen()

    #     # Temporarily override the dependency
    #     original_override = app.dependency_overrides.get(get_db)
    #     app.dependency_overrides[get_db] = failing_db

    #     try:
    #         response = await client.get("/health")
    #         # The endpoint returns 500 on internal errors, not 200 with unhealthy status
    #         assert response.status_code == 500
    #     finally:
    #         # Restore original override
    #         if original_override:
    #             app.dependency_overrides[get_db] = original_override
    #         else:
    #             app.dependency_overrides.pop(get_db, None)


# -------------------------
# Test Protocol APIs
# -------------------------
class TestProtocolAPIs:
    async def test_initialize_no_body(self, client: AsyncClient):
        """Test POST /protocol/initialize with no body (should fail validation)."""
        response = await client.post("/protocol/initialize", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 422]

    async def test_notifications_missing_method(self, client: AsyncClient):
        """Test POST /protocol/notifications with missing method field."""
        response = await client.post("/protocol/notifications", json={}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [200, 400, 422]

    """Test MCP protocol-related endpoints."""

    async def test_initialize(self, client: AsyncClient):
        """Test POST /protocol/initialize - initialize MCP session."""
        request_body = {
            "protocolVersion": "1.0.0",
            "capabilities": {"tools": {"listing": True, "execution": True}, "resources": {"listing": True, "reading": True}},
            "clientInfo": {"name": "test-client", "version": "1.0.0"},
        }

        # Mock the session registry since it requires complex setup
        with patch("mcpgateway.main.session_registry.handle_initialize_logic") as mock_init:
            mock_init.return_value = {"protocolVersion": "1.0.0", "capabilities": {"tools": {}, "resources": {}}, "serverInfo": {"name": "mcp-gateway", "version": "1.0.0"}}

            response = await client.post("/protocol/initialize", json=request_body, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert "protocolVersion" in result
        assert "capabilities" in result
        assert "serverInfo" in result

    async def test_ping(self, client: AsyncClient):
        """Test POST /protocol/ping - MCP ping request."""
        request_body = {"jsonrpc": "2.0", "id": "test-123", "method": "ping"}

        response = await client.post("/protocol/ping", json=request_body, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["jsonrpc"] == "2.0"
        assert result["id"] == "test-123"
        assert result["result"] == {}  # Ping returns empty result per MCP spec

    async def test_ping_invalid_method(self, client: AsyncClient):
        """Test POST /protocol/ping with invalid method."""
        request_body = {"jsonrpc": "2.0", "id": "test-123", "method": "pong"}  # Invalid method

        response = await client.post("/protocol/ping", json=request_body, headers=TEST_AUTH_HEADER)

        assert response.status_code == 400
        result = response.json()
        assert "error" in result

    async def test_notifications_initialized(self, client: AsyncClient):
        """Test POST /protocol/notifications - client initialized."""
        response = await client.post("/protocol/notifications", json={"method": "notifications/initialized"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

    async def test_notifications_cancelled_accepts_unknown_run_as_noop(self, client: AsyncClient):
        """Test POST /protocol/notifications - unknown run is accepted as a no-op."""
        response = await client.post("/protocol/notifications", json={"method": "notifications/cancelled", "params": {"requestId": "test-request-123"}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

    async def test_notifications_cancelled_allowed_for_owner(self, client: AsyncClient):
        """Test POST /protocol/notifications - owner can cancel."""
        with patch("mcpgateway.main.cancellation_service.get_status", new=AsyncMock(return_value={"owner_email": "testuser@example.com", "owner_team_ids": []})):
            response = await client.post("/protocol/notifications", json={"method": "notifications/cancelled", "params": {"requestId": "test-request-123"}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

    async def test_notifications_message(self, client: AsyncClient):
        """Test POST /protocol/notifications - log message."""
        response = await client.post(
            "/protocol/notifications", json={"method": "notifications/message", "params": {"data": "Test log message", "level": "info", "logger": "test-logger"}}, headers=TEST_AUTH_HEADER
        )
        assert response.status_code == 200

    async def test_completion(self, client: AsyncClient):
        """Test POST /protocol/completion/complete."""
        # Mock completion service for this test
        with patch("mcpgateway.main.completion_service.handle_completion") as mock_complete:
            mock_complete.return_value = {"completion": "Test completed"}

            request_body = {"prompt": "Complete this test"}
            response = await client.post("/protocol/completion/complete", json=request_body, headers=TEST_AUTH_HEADER)

            # Accept either success or permission error due to RBAC issues
            # TODO: Fix RBAC mocking to make this test properly pass
            if response.status_code == 422:
                # Skip this test for now due to RBAC decorator issues
                # Third-Party
                import pytest

                pytest.skip("RBAC decorator issue - endpoint expects args/kwargs parameters")

            assert response.status_code == 200
            assert response.json() == {"completion": "Test completed"}

    async def test_sampling_create_message(self, client: AsyncClient):
        """Test POST /protocol/sampling/createMessage."""
        # Mock sampling handler for this test
        with patch("mcpgateway.main.sampling_handler.create_message") as mock_sample:
            mock_sample.return_value = {"messageId": "msg-123", "content": "Sampled message"}

            request_body = {"content": "Create a sample message"}
            response = await client.post("/protocol/sampling/createMessage", json=request_body, headers=TEST_AUTH_HEADER)

            # Accept either success or permission error due to RBAC issues
            # TODO: Fix RBAC mocking to make this test properly pass
            if response.status_code == 422:
                # Skip this test for now due to RBAC decorator issues
                # Third-Party
                import pytest

                pytest.skip("RBAC decorator issue - endpoint expects args/kwargs parameters")

            assert response.status_code == 200
            assert response.json()["messageId"] == "msg-123"


# -------------------------
# Test Server APIs
# -------------------------
class TestServerAPIs:
    async def test_get_servers_no_auth(self, client: AsyncClient):
        """Test GET /servers without auth header (should fail if auth required)."""
        response = await client.get("/servers")
        # Accept either auth error or RBAC decorator error
        # TODO: Fix RBAC mocking to make this test properly pass
        if response.status_code == 422:
            # Skip this test for now due to RBAC decorator issues
            # Third-Party
            import pytest

            pytest.skip("RBAC decorator issue - endpoint expects args/kwargs parameters")

        assert response.status_code in [401, 403, 200]

    """Test server management endpoints."""

    async def test_list_servers_empty(self, client: AsyncClient, mock_auth):
        """Test GET /servers returns empty list initially."""
        response = await client.get("/servers", headers=TEST_AUTH_HEADER)

        # With our simplified dependency override, this should work
        assert response.status_code == 200
        # Default response is a plain list (include_pagination=False by default)
        assert response.json() == []

    async def test_create_virtual_server(self, client: AsyncClient, mock_auth):
        """Test POST /servers - create virtual server."""
        server_data = {
            "server": {
                "name": "test_utilities",
                "description": "Test utility functions",
                "icon": "https://example.com/icon.png",
                "associatedTools": [],  # Will be populated later
                "associatedResources": [],
                "associatedPrompts": [],
            },
            "team_id": None,
            "visibility": "private",
        }

        response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)

        # Accept either success or permission error due to RBAC issues
        # TODO: Fix RBAC mocking to make this test properly pass
        if response.status_code == 422:
            # Skip this test for now due to RBAC decorator issues
            # Third-Party
            import pytest

            pytest.skip("RBAC decorator issue - endpoint expects args/kwargs parameters")

        assert response.status_code == 201
        result = response.json()
        assert result["name"] == server_data["server"]["name"]
        assert result["description"] == server_data["server"]["description"]
        assert "id" in result
        # Check for the actual field name used in the response
        assert result.get("enabled", True) is True  # or whatever field indicates active status

    async def test_get_server(self, client: AsyncClient, mock_auth):
        """Test GET /servers/{server_id}."""
        # First create a server. ``visibility="public"`` so the public-only test
        # JWT (teams=[], is_admin=False) can read it back via GET — PR #4341
        # denies private rows to public-only tokens, even own-private.
        server_data = {"server": {"name": "get_test_server", "description": "Server for GET test"}, "team_id": None, "visibility": "public"}

        create_response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        server_id = create_response.json()["id"]

        # Get the server
        response = await client.get(f"/servers/{server_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["id"] == server_id
        assert result["name"] == server_data["server"]["name"]

    async def test_update_server(self, client: AsyncClient, mock_auth):
        """Test PUT /servers/{server_id}."""
        # Create a server
        server_data = {"server": {"name": "update_test_server", "description": "Original description"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        server_id = create_response.json()["id"]

        # Update the server
        update_data = {"description": "Updated description", "icon": "https://example.com/new-icon.png"}
        response = await client.put(f"/servers/{server_id}", json=update_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["description"] == update_data["description"]
        assert result["icon"] == update_data["icon"]

    async def test_set_server_state(self, client: AsyncClient, mock_auth):
        """Test POST /servers/{server_id}/state."""
        # Create a server
        server_data = {"server": {"name": "state_test_server"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        server_id = create_response.json()["id"]

        # Deactivate the server
        response = await client.post(f"/servers/{server_id}/state?activate=false", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        # The state endpoint returns the full server object
        assert "id" in result
        assert "name" in result
        # Check if server was deactivated
        assert result.get("enabled") is False or result.get("enabled") is False

        # Reactivate the server
        response = await client.post(f"/servers/{server_id}/state?activate=true", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result.get("enabled") is True or result.get("enabled") is True

    async def test_delete_server(self, client: AsyncClient, mock_auth):
        """Test DELETE /servers/{server_id}."""
        # Create a server. ``visibility="public"`` so the public-only test JWT
        # can DELETE it back — see ``test_get_server`` for the PR #4341 reasoning.
        server_data = {"server": {"name": "delete_test_server"}, "team_id": None, "visibility": "public"}

        create_response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        server_id = create_response.json()["id"]

        # Delete the server
        response = await client.delete(f"/servers/{server_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"

        # Verify it's deleted
        response = await client.get(f"/servers/{server_id}", headers=TEST_AUTH_HEADER)
        assert response.status_code == 404

    async def test_server_not_found(self, client: AsyncClient, mock_auth):
        """Test operations on non-existent server."""
        fake_id = "non-existent-server-id"

        # GET - returns 400 instead of 404
        response = await client.get(f"/servers/{fake_id}", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]  # Accept either

        # PUT
        response = await client.put(f"/servers/{fake_id}", json={"description": "test"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

        # DELETE
        response = await client.delete(f"/servers/{fake_id}", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_server_name_conflict(self, client: AsyncClient, mock_auth):
        """Test creating server with duplicate (team_id, owner_email, name) for authenticated user."""
        # Only vary team_id and name, owner_email is set by auth context
        server_data_1 = {"server": {"name": "duplicate_server"}, "team_id": "teamA", "visibility": "private"}
        server_data_2 = {"server": {"name": "duplicate_server"}, "team_id": "teamA", "visibility": "private"}
        server_data_3 = {"server": {"name": "duplicate_server"}, "team_id": "teamB", "visibility": "private"}

        # Create first server (teamA, authenticated user)
        response = await client.post("/servers", json=server_data_1, headers=TEST_AUTH_HEADER)
        assert response.status_code == 201

        # Try to create duplicate with same team_id, name - must return 409
        response = await client.post("/servers", json=server_data_2, headers=TEST_AUTH_HEADER)
        assert response.status_code == 409
        resp_json = response.json()
        if "message" in resp_json:
            assert "already exists" in resp_json["message"]
        else:
            assert response.status_code == 409

        # Create with different team_id (should succeed)
        response = await client.post("/servers", json=server_data_3, headers=TEST_AUTH_HEADER)
        assert response.status_code == 201

    async def test_create_server_success_and_missing_fields(self, client: AsyncClient, mock_auth):
        """Test POST /servers - create server success and missing fields."""
        server_data = {"server": {"name": "test_server", "description": "A test server"}, "team_id": None, "visibility": "private"}
        response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 201
        result = response.json()
        assert result["name"] == server_data["server"]["name"]
        # Missing required fields
        response = await client.post("/servers", json={}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_update_server_success_and_invalid(self, client: AsyncClient, mock_auth):
        """Test PUT /servers/{server_id} - update server success and invalid id."""
        # Create a server first
        server_data = {"server": {"name": "update_server", "description": "To update"}, "team_id": None, "visibility": "private"}
        create_response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        server_id = create_response.json()["id"]
        # Update
        update_data = {"description": "Updated description"}
        response = await client.put(f"/servers/{server_id}", json=update_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()
        assert result["description"] == update_data["description"]
        # Invalid id
        response = await client.put("/servers/invalid-id", json=update_data, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]


# -------------------------
# Test Tool APIs
# -------------------------
class TestToolAPIs:
    async def test_create_tool_no_body(self, client: AsyncClient, mock_auth):
        """Test POST /tools with no body (should fail validation)."""
        response = await client.post("/tools", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 422]

    """Test tool management endpoints."""

    async def test_list_tools_empty(self, client: AsyncClient, mock_auth):
        """Test GET /tools returns empty list initially."""
        response = await client.get("/tools", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        # Default response is a plain list (include_pagination=False by default)
        assert response.json() == []

    # FIXME: we should remove MCP as an integration type
    # async def test_create_rest_tool(self, client: AsyncClient, mock_auth):
    #     """Test POST /tools - create REST API tool."""
    #     tool_data = {
    #         "name": "weather_api",
    #         "url": "https://api.openweathermap.org/data/2.5/weather",
    #         "description": "Get current weather data",
    #         "integrationType": "REST",
    #         "requestType": "GET",
    #         "headers": {"X-API-Key": "demo-key"},
    #         "inputSchema": {"type": "object", "properties": {"q": {"type": "string", "description": "City name"}, "units": {"type": "string", "enum": ["metric", "imperial"]}}, "required": ["q"]},
    #     }

    #     response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)

    #     assert response.status_code == 200
    #     result = response.json()
    #     assert result["name"] == "weather-api"  # Normalized name
    #     assert result["originalName"] == tool_data["tool"]["name"]
    #     # The integrationType might be set to MCP by default
    #     #assert result["integrationType"] == "REST"
    #     assert result["requestType"] == "GET" # FIXME: somehow this becomes SSE?!

    async def test_create_mcp_tool(self, client: AsyncClient, mock_auth):
        """Test POST /tools - create MCP tool."""
        tool_data = {
            "tool": {
                "name": "get_system_time",
                "description": "Get current system time",
                "integrationType": "MCP",
                "inputSchema": {"type": "object", "properties": {"timezone": {"type": "string", "description": "Timezone"}}},
            },
            "team_id": None,
            "visibility": "private",
        }

        response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)

        # Debug: print response details if not 200
        if response.status_code != 200:
            pass  # Debug output removed

        assert response.status_code == 200
        # result = response.json()
        # assert result["integrationType"] == "REST"

    async def test_create_tool_validation_errors(self, client: AsyncClient, mock_auth):
        """Test POST /tools with various validation errors."""
        # Empty name - might succeed with generated name
        response = await client.post("/tools", json={"tool": {"name": "", "url": "https://example.com"}}, headers=TEST_AUTH_HEADER)
        # Check if it returns validation error or succeeds with generated name
        if response.status_code == 422:
            assert "Tool name cannot be empty" in str(response.json())

        # Valid name format with dashes (per MCP spec - hyphens allowed in names)
        response = await client.post("/tools", json={"tool": {"name": "tool-with-dashes", "url": "https://example.com"}}, headers=TEST_AUTH_HEADER)
        # Tool names with hyphens are valid per MCP spec
        if response.status_code == 422:
            # May fail for other reasons (duplicate, etc)
            assert "must start with a letter, number, or underscore" in str(response.json()) or "already exists" in str(response.json())
        else:
            assert response.status_code == 200

        # Invalid URL scheme
        response = await client.post("/tools", json={"tool": {"name": "test_tool", "url": "javascript:alert(1)"}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        assert "must start with one of" in str(response.json())

        # Name too long (>255 chars)
        long_name = "a" * 300
        response = await client.post("/tools", json={"tool": {"name": long_name, "url": "https://example.com"}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        assert "exceeds MCP spec limit" in str(response.json())

    async def test_get_tool(self, client: AsyncClient, mock_auth):
        """Test GET /tools/{tool_id}."""
        # Create a tool
        tool_data = {"tool": {"name": "test_get_tool", "description": "Tool for GET test", "inputSchema": {"type": "object"}}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        tool_id = create_response.json()["id"]

        # Get the tool
        response = await client.get(f"/tools/{tool_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["id"] == tool_id
        assert result["originalName"] == tool_data["tool"]["name"]

    async def test_update_tool(self, client: AsyncClient, mock_auth):
        """Test PUT /tools/{tool_id}."""
        # Create a tool
        tool_data = {"tool": {"name": "test_update_tool", "description": "Original description"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        tool_id = create_response.json()["id"]

        # Update the tool
        update_data = {"description": "Updated description", "headers": {"Authorization": "Bearer new-token"}}
        response = await client.put(f"/tools/{tool_id}", json=update_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["description"] == update_data["description"]
        assert result["headers"] == update_data["headers"]

    async def test_set_tool_state(self, client: AsyncClient, mock_auth):
        """Test POST /tools/{tool_id}/state."""
        # Create a tool
        tool_data = {"tool": {"name": "test_state_tool"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        tool_id = create_response.json()["id"]

        # Deactivate the tool
        response = await client.post(f"/tools/{tool_id}/state?activate=false", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["status"] == "success"
        assert "deactivated" in result["message"]

        # Verify it's deactivated by listing with include_inactive
        response = await client.get("/tools?include_inactive=true", headers=TEST_AUTH_HEADER)
        tools_response = response.json()
        # Handle both paginated and non-paginated responses
        tools = tools_response.get("tools", tools_response) if isinstance(tools_response, dict) else tools_response
        deactivated_tool = next((t for t in tools if t["id"] == tool_id), None)
        assert deactivated_tool is not None
        assert deactivated_tool["enabled"] is False

    async def test_delete_tool(self, client: AsyncClient, mock_auth):
        """Test DELETE /tools/{tool_id}."""
        # Create a tool
        tool_data = {"tool": {"name": "test_delete_tool"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        tool_id = create_response.json()["id"]

        # Delete the tool
        response = await client.delete(f"/tools/{tool_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"

        # Verify it's deleted
        response = await client.get(f"/tools/{tool_id}", headers=TEST_AUTH_HEADER)
        assert response.status_code == 404

    # API should probably return 404 instead of 400 for non-existent tool
    async def test_tool_name_conflict(self, client: AsyncClient, mock_auth):
        """Test creating tool with duplicate name."""
        tool_data = {"tool": {"name": "duplicate_tool"}, "team_id": None, "visibility": "private"}

        # Create first tool
        response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

        # Try to create duplicate - might succeed with different ID
        response = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        # Accept 400, 409, or 200 as valid responses for duplicate
        assert response.status_code in [200, 400, 409]
        if response.status_code == 400:
            assert "already exists" in response.json()["detail"]

    async def test_create_tool_missing_required_fields(self, client: AsyncClient, mock_auth):
        """Test POST /tools with missing required fields."""
        # Missing name
        response = await client.post("/tools", json={"tool": {"description": "desc"}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        # Empty body
        response = await client.post("/tools", json={"tool": {}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_update_tool_invalid_id(self, client: AsyncClient, mock_auth):
        """Test PUT /tools/{tool_id} with invalid/nonexistent ID."""
        response = await client.put("/tools/invalid-id", json={"description": "desc"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_delete_tool_invalid_id(self, client: AsyncClient, mock_auth):
        """Test DELETE /tools/{tool_id} with invalid/nonexistent ID."""
        response = await client.delete("/tools/invalid-id", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_update_tool_not_found(self, client: AsyncClient, mock_auth):
        """Test PUT /tools/{tool_id} with non-existent tool returns 404 or 400."""
        fake_id = "non-existent-tool-id"
        response = await client.put(f"/tools/{fake_id}", json={"description": "desc"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]
        resp_json = response.json()
        assert "not found" in str(resp_json).lower() or "does not exist" in str(resp_json).lower()


# -------------------------
# Test Resource APIs
# -------------------------
class TestResourceAPIs:
    async def test_create_resource_no_body(self, client: AsyncClient, mock_auth):
        """Test POST /resources with no body (should fail validation)."""
        response = await client.post("/resources", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 422]

    """Test resource management endpoints."""

    async def test_list_resources_empty(self, client: AsyncClient, mock_auth):
        """Test GET /resources returns empty list initially."""
        response = await client.get("/resources", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        # Default response is a plain list (include_pagination=False by default)
        assert response.json() == []

    async def test_list_resource_templates(self, client: AsyncClient, mock_auth):
        """Test GET /resources/templates/list."""
        response = await client.get("/resources/templates/list", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        # The field is resource_templates not resourceTemplates
        assert "resource_templates" in result
        assert "_meta" in result
        assert isinstance(result["resource_templates"], list)

    async def test_create_markdown_resource(self, client: AsyncClient, mock_auth):
        """Test POST /resources - create markdown resource."""
        resource_data = {
            "resource": {
                "uri": "docs/readme",
                "name": "readme",
                "description": "Project README",
                "mimeType": "text/markdown",
                # Changed from "# ContextForge..." to avoid SQL comment pattern match on #
                "content": "**ContextForge**\n\nWelcome to ContextForge!",
            },
            "team_id": None,
            "visibility": "private",
        }

        response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["uri"] == resource_data["resource"]["uri"]
        assert result["name"] == resource_data["resource"]["name"]
        # mimeType might be normalized to text/plain
        assert result["mimeType"] in ["text/markdown", "text/plain"]

    async def test_create_json_resource(self, client: AsyncClient, mock_auth):
        """Test POST /resources - create JSON resource."""
        resource_data = {
            "resource": {
                "uri": "config/app",
                "name": "app_config",
                "description": "Application configuration",
                "mimeType": "application/json",
                "content": json.dumps({"version": "1.0.0", "debug": False}),
            },
            "team_id": None,
            "visibility": "private",
        }

        response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()
        # API normalizes all mime types to text/plain
        if "mime_type" in result:
            assert result["mime_type"] == "application/json"
        elif "mimeType" in result:
            assert result["mimeType"] == "application/json"

    async def test_create_resource_form_urlencoded(self, client: AsyncClient, mock_auth):
        """
        Test POST /resources with application/x-www-form-urlencoded.
        Ensures resource creation works with form-encoded data.
        """
        # Standard
        import urllib.parse

        resource_data = {
            "resource": urllib.parse.quote_plus(r'{"uri":"config/formtest","name":"form_test","description":"Form resource","mimeType":"application/json","content":"{\"key\":\"value\"}"}'),
            "team_id": "",
            "visibility": "private",
        }
        headers = TEST_AUTH_HEADER.copy()
        headers["Content-Type"] = "application/x-www-form-urlencoded"
        response = await client.post("/resources", data=resource_data, headers=headers)
        assert response.status_code in [200, 201, 400, 401, 422]

    async def test_resource_validation_errors(self, client: AsyncClient, mock_auth):
        """Test POST /resources with validation errors."""
        # Directory traversal in URI
        response = await client.post(
            "/resources", json={"resource": {"uri": "../../etc/passwd", "name": "test", "content": "data"}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER
        )
        assert response.status_code == 422
        assert "directory traversal" in str(response.json())

        # Empty URI
        response = await client.post("/resources", json={"resource": {"uri": "", "name": "test", "content": "data"}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_read_resource(self, client: AsyncClient, mock_auth):
        """Test GET /resources/{uri:path}."""
        # Create a resource first. ``visibility="public"`` so the public-only
        # test JWT can read it back — see ``test_get_server`` for PR #4341 reasoning.
        resource_data = {"resource": {"uri": "resource://test", "name": "test_doc", "content": "Test content", "mimeType": "text/plain"}, "team_id": None, "visibility": "public"}

        response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        resource = response.json()
        print("\n----------HBD------------> Resource \n", resource, "\n----------HBD------------> Resource\n")
        assert resource["name"] == "test_doc"
        resource_id = resource["id"]

        # Read the resource
        response = await client.get(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["uri"] == resource_data["resource"]["uri"]
        # The response has a 'text' field
        assert "text" in result
        assert result["text"] == resource_data["resource"]["content"]

    async def test_update_resource(self, client: AsyncClient, mock_auth):
        """Test PUT /resources/{uri:path}."""
        # Create a resource
        resource_data = {"resource": {"uri": "test/update", "name": "update_test", "content": "Original content"}, "team_id": None, "visibility": "private"}

        response_resource = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        resource = response_resource.json()
        assert resource["name"] == "update_test"
        resource_id = resource["id"]

        # Update the resource
        update_data = {"content": "Updated content", "description": "Updated description"}
        response = await client.put(f"/resources/{resource_id}", json=update_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["description"] == update_data["description"]

    async def test_set_resource_state(self, client: AsyncClient, mock_auth):
        """Test POST /resources/{resource_id}/state."""
        # Create a resource
        resource_data = {"resource": {"uri": "test/state", "name": "state_test", "content": "Test"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        resource_id = create_response.json()["id"]

        # Set resource state
        response = await client.post(f"/resources/{resource_id}/state?activate=false", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"
        assert "deactivated" in response.json()["message"]

    async def test_delete_resource(self, client: AsyncClient, mock_auth):
        """Test DELETE /resources/{uri:path}."""
        # Create a resource
        resource_data = {"resource": {"uri": "test/delete", "name": "delete_test", "content": "To be deleted"}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        resource_id = create_response.json()["id"]

        # Delete the resource
        response = await client.delete(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"

        # Verify it's deleted
        response = await client.get(f"/resources/{resource_data['resource']['uri']}", headers=TEST_AUTH_HEADER)
        assert response.status_code == 404

    # API should probably return 409 instead of 400 for non-existent resource
    async def test_resource_uri_conflict(self, client: AsyncClient, mock_auth):
        """Test creating resource with duplicate URI."""
        resource_data = {"resource": {"uri": "duplicate/resource", "name": "duplicate", "content": "test", "team_id": None, "visibility": "public"}}

        # Create first resource
        response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

        # Try to create duplicate
        response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 409
        resp_json = response.json()
        if "message" in resp_json:
            assert "already exists" in resp_json["message"]
        else:
            # Accept any error format as long as status is correct
            assert response.status_code == 409

    async def test_create_resource_missing_fields(self, client: AsyncClient, mock_auth):
        """Test POST /resources with missing required fields."""
        # Missing uri
        response = await client.post("/resources", json={"resource": {"name": "test", "content": "data"}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        # Missing name
        response = await client.post("/resources", json={"resource": {"uri": "missing/name", "content": "data"}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        # Missing content
        response = await client.post("/resources", json={"resource": {"uri": "missing/content", "name": "test"}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_update_resource_invalid_uri(self, client: AsyncClient, mock_auth):
        """Test PUT /resources/{uri:path} with invalid/nonexistent URI."""
        response = await client.put("/resources/invalid/uri", json={"content": "update"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_delete_resource_invalid_uri(self, client: AsyncClient, mock_auth):
        """Test DELETE /resources/{uri:path} with invalid/nonexistent URI."""
        response = await client.delete("/resources/invalid/uri", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_create_resource_success_and_missing_fields(self, client: AsyncClient, mock_auth):
        """Test POST /resources - create resource success and missing fields."""
        resource_data = {"resource": {"uri": "test/create", "name": "create_test", "content": "test content"}, "team_id": None, "visibility": "private"}
        response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()
        assert result["uri"] == resource_data["resource"]["uri"]
        # Missing required fields
        response = await client.post("/resources", json={"resource": {"name": "test"}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_update_resource_success_and_invalid(self, client: AsyncClient, mock_auth):
        """Test PUT /resources/{resource_id} - update resource success and invalid uri."""
        # Create a resource first
        resource_data = {"resource": {"uri": "test/update2", "name": "update2", "content": "original"}, "team_id": None, "visibility": "private"}
        created_response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        resource_id = created_response.json()["id"]
        assert created_response.status_code == 200
        # Update
        update_data = {"content": "updated content"}
        response = await client.put(f"/resources/{resource_id}", json=update_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()
        assert result["uri"] == resource_data["resource"]["uri"]
        # Invalid uri
        response = await client.put("/resources/invalid/uri", json=update_data, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]


# -------------------------
# Test Prompt APIs
# -------------------------
class TestPromptAPIs:
    async def test_create_prompt_no_body(self, client: AsyncClient, mock_auth):
        """Test POST /prompts with no body (should fail validation)."""
        response = await client.post("/prompts", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 422]

    """Test prompt management endpoints."""

    async def test_list_prompts_empty(self, client: AsyncClient, mock_auth):
        """Test GET /prompts returns empty list initially."""
        response = await client.get("/prompts", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        # Default response is a plain list (include_pagination=False by default)
        assert response.json() == []

    async def test_create_prompt_with_arguments(self, client: AsyncClient, mock_auth):
        """Test POST /prompts - create prompt with arguments."""
        prompt_data = {
            "prompt": {
                "name": "code_analysis",
                "description": "Analyze code quality",
                "template": "Analyze the following {{ language }} code:\n\n{{ code }}\n\nFocus on: {{ focus_areas }}",
                "arguments": [
                    {"name": "language", "description": "Programming language", "required": True},
                    {"name": "code", "description": "Code to analyze", "required": True},
                    {"name": "focus_areas", "description": "Specific areas to focus on", "required": False},
                ],
            },
            "team_id": None,
            "visibility": "private",
        }

        response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["name"] == "code-analysis"
        assert result["originalName"] == prompt_data["prompt"]["name"]
        assert len(result["arguments"]) == 3
        assert result["arguments"][0]["required"] is True
        # API might be setting all arguments as required=True by default
        # Check if it's actually respecting the required field
        for i, arg in enumerate(result["arguments"]):
            if arg["name"] == "focus_areas":
                # If API forces all to required=True, accept it
                assert arg["required"] in [True, False]

    async def test_create_prompt_no_arguments(self, client: AsyncClient, mock_auth):
        """Test POST /prompts - create prompt without arguments."""
        prompt_data = {
            "prompt": {"name": "system_summary", "description": "System status summary", "template": "ContextForge is running and ready to process requests.", "arguments": []},
            "team_id": None,
            "visibility": "private",
        }

        response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["arguments"] == []

    async def test_prompt_validation_errors(self, client: AsyncClient, mock_auth):
        """Test POST /prompts with validation errors."""
        # HTML tags in template
        response = await client.post(
            "/prompts", json={"prompt": {"name": "test_prompt", "template": "<script>alert(1)</script>", "arguments": []}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER
        )
        assert response.status_code == 422
        assert "HTML tags" in str(response.json())

    async def test_get_prompt_with_args(self, client: AsyncClient, mock_auth):
        """Test POST /prompts/{prompt_id} - execute prompt with arguments."""
        # First create a prompt. ``visibility="public"`` so the public-only test
        # JWT can render it — see ``test_get_server`` for PR #4341 reasoning.
        prompt_data = {
            "prompt": {
                "name": "greeting_prompt",
                "description": "Personalized greeting",
                "template": "Hello {{ name }}, welcome to {{ company }}!",
                "arguments": [{"name": "name", "description": "User name", "required": True}, {"name": "company", "description": "Company name", "required": True}],
            },
            "team_id": None,
            "visibility": "public",
        }

        create_response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        prompt_id = create_response.json()["id"]

        # Execute the prompt with arguments
        response = await client.post(f"/prompts/{prompt_id}", json={"name": "Alice", "company": "Acme Corp"}, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert "messages" in result
        assert result["messages"][0]["content"]["text"] == "Hello Alice, welcome to Acme Corp!"

    async def test_get_prompt_no_args(self, client: AsyncClient, mock_auth):
        """Test GET /prompts/{prompt_id} - get prompt without executing."""
        # Create a simple prompt. ``visibility="public"`` so the public-only test
        # JWT can read it back — see ``test_get_server`` for PR #4341 reasoning.
        prompt_data = {"prompt": {"name": "simple_prompt", "template": "Simple message", "arguments": []}, "team_id": None, "visibility": "public"}

        create_response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        prompt_id = create_response.json()["id"]

        # Get the prompt without arguments
        response = await client.get(f"/prompts/{prompt_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert "messages" in result

    async def test_set_prompt_state(self, client: AsyncClient, mock_auth):
        """Test POST /prompts/{prompt_id}/state."""
        # Create a prompt
        prompt_data = {"prompt": {"name": "state_prompt", "template": "Test prompt", "arguments": []}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        prompt_id = create_response.json()["id"]

        # Set prompt state
        response = await client.post(f"/prompts/{prompt_id}/state?activate=false", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"
        assert "deactivated" in response.json()["message"]

    async def test_update_prompt(self, client: AsyncClient, mock_auth):
        """Test PUT /prompts/{prompt_id}."""
        # Create a prompt
        prompt_data = {"prompt": {"name": "update_prompt", "description": "Original description", "template": "Original template", "arguments": []}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        prompt_id = create_response.json()["id"]
        # Update the prompt
        update_data = {"description": "Updated description", "template": "Updated template with {{ param }}"}
        response = await client.put(f"/prompts/{prompt_id}", json=update_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["description"] == update_data["description"]
        assert result["template"] == update_data["template"]

    async def test_delete_prompt(self, client: AsyncClient, mock_auth):
        """Test DELETE /prompts/{prompt_id}."""
        # Create a prompt
        prompt_data = {"prompt": {"name": "delete_prompt", "template": "To be deleted", "arguments": []}, "team_id": None, "visibility": "private"}

        create_response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        prompt_id = create_response.json()["id"]
        # Delete the prompt
        response = await client.delete(f"/prompts/{prompt_id}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"

    # API should probably return 409 instead of 400 for non-existent prompt
    async def test_prompt_name_conflict(self, client: AsyncClient, mock_auth):
        """Test creating prompt with duplicate name."""
        prompt_data = {
            "prompt": {"name": "duplicate_prompt", "template": "Test", "arguments": [], "team_id": "1", "owner_email": "owner@example.com", "visibility": "private"},
            "team_id": "1",
            "visibility": "private",
        }

        # Create first prompt
        response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

        # Try to create duplicate - must return 409 Conflict
        response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 409
        resp_json = response.json()
        if "detail" in resp_json:
            assert "already exists" in resp_json["detail"]["message"]
        elif "message" in resp_json:
            assert "already exists" in resp_json["message"]
        else:
            # Accept any error format as long as status is correct
            assert response.status_code == 409

    async def test_create_prompt_missing_fields(self, client: AsyncClient, mock_auth):
        """Test POST /prompts with missing required fields."""
        # Missing name
        response = await client.post("/prompts", json={"prompt": {"template": "Test", "arguments": []}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        # Missing template
        response = await client.post("/prompts", json={"prompt": {"name": "missing_template", "arguments": []}, "team_id": None, "visibility": "private"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_update_prompt_invalid_name(self, client: AsyncClient, mock_auth):
        """Test PUT /prompts/{name} with invalid/nonexistent name."""
        response = await client.put("/prompts/invalid_name", json={"description": "desc"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_delete_prompt_invalid_name(self, client: AsyncClient, mock_auth):
        """Test DELETE /prompts/{name} with invalid/nonexistent name."""
        response = await client.delete("/prompts/invalid_name", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]

    async def test_update_prompt_not_found(self, client: AsyncClient, mock_auth):
        """Test PUT /prompts/{name} with non-existent prompt returns 404 or 400."""
        fake_name = "nonexistent_prompt"
        response = await client.put(f"/prompts/{fake_name}", json={"description": "desc"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]
        resp_json = response.json()
        assert "not found" in str(resp_json).lower() or "does not exist" in str(resp_json).lower()

    async def test_create_prompt_duplicate_name(self, client: AsyncClient, mock_auth):
        """Test POST /prompts with duplicate name returns 409 or 400."""
        prompt_data = {
            "prompt": {"name": "duplicate_prompt_case", "template": "Test", "arguments": [], "team_id": "1", "owner_email": "owner@example.com", "visibility": "private"},
            "team_id": "1",
            "visibility": "private",
        }
        # Create first prompt
        response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        # Try to create duplicate
        response = await client.post("/prompts", json=prompt_data, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 409]
        resp_json = response.json()
        assert "already exists" in str(resp_json).lower()


# -------------------------
# Test Gateway APIs
# -------------------------
class TestGatewayAPIs:
    async def test_create_gateway_no_body(self, client: AsyncClient, mock_auth):
        """Test POST /gateways with no body (should fail validation)."""
        response = await client.post("/gateways", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 422]

    """Test gateway federation endpoints."""

    async def test_list_gateways_empty(self, client: AsyncClient, mock_auth):
        """Test GET /gateways returns empty list initially."""
        response = await client.get("/gateways", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        # Default response is a plain list (include_pagination=False by default)
        assert response.json() == []

    async def test_gateway_validation_errors(self, client: AsyncClient, mock_auth):
        """Test POST /gateways with validation errors."""
        # Invalid gateway name (special characters)
        response = await client.post("/gateways", json={"name": "<script>alert(1)</script>", "url": "http://example.com", "transport": "SSE"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        assert "can only contain letters" in str(response.json())

        # Invalid URL
        response = await client.post("/gateways", json={"name": "test_gateway", "url": "javascript:alert(1)", "transport": "SSE"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        assert "must start with one of" in str(response.json())

        # Name too long
        long_name = "a" * 300
        response = await client.post("/gateways", json={"name": long_name, "url": "http://example.com", "transport": "SSE"}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        assert "exceeds maximum length" in str(response.json())

    @pytest.mark.skip(reason="Requires external gateway connectivity")
    async def test_register_gateway(self, client: AsyncClient, mock_auth):
        """Test POST /gateways - would require mocking external connections."""

    async def test_set_gateway_state(self, client: AsyncClient, mock_auth):
        """Test POST /gateways/{gateway_id}/state."""
        # Mock a gateway for testing
        # In real tests, you'd need to register a gateway first
        # This is skipped as it requires external connectivity

    async def test_update_gateway_invalid_id(self, client: AsyncClient, mock_auth):
        """Test PUT /gateways/{gateway_id} with invalid/non-existent ID returns 404 or 400."""
        fake_id = "non-existent-gateway-id"
        response = await client.put(f"/gateways/{fake_id}", json={"url": "http://example.com", "transport": "SSE"}, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 404]
        resp_json = response.json()
        assert "not found" in str(resp_json).lower() or "does not exist" in str(resp_json).lower()


# -------------------------
# Test Root APIs
# -------------------------
class TestRootAPIs:
    async def test_add_root_no_body(self, client: AsyncClient, mock_auth):
        """Test POST /roots with no body (should fail validation)."""
        response = await client.post("/roots", headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 422]

    """Test root management endpoints."""

    async def test_list_roots_empty(self, client: AsyncClient, mock_auth):
        """Test GET /roots returns empty list initially."""
        response = await client.get("/roots", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        assert response.json() == []

    async def test_add_root(self, client: AsyncClient, mock_auth):
        """Test POST /roots - add filesystem root."""
        root_data = {"uri": "file:///test/path", "name": "Test Root"}

        response = await client.post("/roots", json=root_data, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result["uri"] == root_data["uri"]
        assert result["name"] == root_data["name"]

    async def test_list_roots_after_add(self, client: AsyncClient, mock_auth):
        """Test GET /roots after adding roots."""
        # Add multiple roots
        roots = [{"uri": "file:///path1", "name": "Root 1"}, {"uri": "file:///path2", "name": "Root 2"}]

        for root in roots:
            await client.post("/roots", json=root, headers=TEST_AUTH_HEADER)

        # List roots
        response = await client.get("/roots", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()
        assert len(result) >= 2

    async def test_remove_root(self, client: AsyncClient, mock_auth):
        """Test DELETE /roots/{uri:path}."""
        # Add a root
        root_data = {"uri": "file:///test/delete", "name": "To Delete"}

        await client.post("/roots", json=root_data, headers=TEST_AUTH_HEADER)

        # Remove the root
        response = await client.delete(f"/roots/{root_data['uri']}", headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        assert response.json()["status"] == "success"


# -------------------------
# Test Utility APIs
# -------------------------
class TestUtilityAPIs:
    async def test_rpc_no_body(self, client: AsyncClient, mock_auth):
        """Test POST /rpc with no body (should fail validation)."""
        response = await client.post("/rpc", headers=TEST_AUTH_HEADER)
        assert response.status_code == 400
        body = response.json()
        assert body["error"]["code"] == -32700
        assert body["error"]["message"] == "Parse error"

    """Test utility endpoints (RPC, logging, etc)."""

    async def test_rpc_ping(self, client: AsyncClient, mock_auth):
        """Test POST /rpc - ping method."""
        rpc_request = {"jsonrpc": "2.0", "method": "ping", "id": "test-123"}

        response = await client.post("/rpc", json=rpc_request, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert result == {"jsonrpc": "2.0", "result": {}, "id": "test-123"}  # ping returns empty result

    async def test_rpc_list_tools(self, client: AsyncClient, mock_auth):
        """Test POST /rpc - tools/list method."""
        rpc_request = {"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 1}

        response = await client.post("/rpc", json=rpc_request, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert isinstance(result.get("result", {}).get("tools"), list)

    async def test_rpc_invalid_method(self, client: AsyncClient, mock_auth):
        """Test POST /rpc with invalid method."""
        rpc_request = {"jsonrpc": "2.0", "method": "invalid/method", "id": 1}

        response = await client.post("/rpc", json=rpc_request, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200
        result = response.json()
        assert "error" in result
        assert result["error"]["code"] == -32601

    async def test_set_log_level(self, client: AsyncClient, mock_auth):
        """Test POST /logging/setLevel."""
        response = await client.post("/logging/setLevel", json={"level": "debug"}, headers=TEST_AUTH_HEADER)

        assert response.status_code == 200

    # TODO: API should probably return 422 instead of 500 for invalid log level
    # TODO: Catch the ValueError and return a proper 422 validation error
    # Use Pydantic validation on the request body to ensure only valid enum values are accepted
    # async def test_invalid_log_level(self, client: AsyncClient, mock_auth):
    #     """Test POST /logging/setLevel with invalid level."""
    #     response = await client.post("/logging/setLevel", json={"level": "invalid"}, headers=TEST_AUTH_HEADER)

    #     # API returns 500 on internal errors, not 422
    #     assert response.status_code == 500


# -------------------------
# Test Metrics APIs
# -------------------------
class TestMetricsAPIs:
    async def test_metrics_requires_auth(self, client: AsyncClient):
        """Test GET /metrics with auth header succeeds."""
        response = await client.get("/metrics", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200

    """Test metrics collection endpoints."""

    async def test_get_metrics(self, client: AsyncClient, mock_auth):
        """Test GET /metrics - aggregated metrics."""
        response = await client.get("/metrics", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()

        # Verify all metric categories are present
        assert "tools" in result
        assert "resources" in result
        assert "servers" in result
        assert "prompts" in result

        # Each category has different metric fields than expected
        for category in ["tools", "resources", "servers", "prompts"]:
            result[category]
            # Check for actual fields in the response
            # FIXME: The expected fields might differ (camelCase vs snake_case)
            # assert "avgResponseTime" in metrics
            # assert "failedExecutions" in metrics
            # assert "failureRate" in metrics
            # assert "lastExecutionTime" in metrics

    async def test_reset_metrics_global(self, client: AsyncClient, mock_auth):
        """Test POST /metrics/reset - global reset."""
        response = await client.post("/metrics/reset", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        assert response.json()["status"] == "success"
        assert "all entities" in response.json()["message"]

    async def test_reset_metrics_by_entity(self, client: AsyncClient, mock_auth):
        """Test POST /metrics/reset - entity-specific reset."""
        # Test each valid entity type
        for entity in ["tool", "resource", "server", "prompt"]:
            response = await client.post(f"/metrics/reset?entity={entity}", headers=TEST_AUTH_HEADER)
            assert response.status_code == 200
            assert response.json()["status"] == "success"
            assert entity in response.json()["message"]

    async def test_reset_metrics_invalid_entity(self, client: AsyncClient, mock_auth):
        """Test POST /metrics/reset with invalid entity type."""
        response = await client.post("/metrics/reset?entity=invalid", headers=TEST_AUTH_HEADER)
        assert response.status_code == 400
        assert "Invalid entity type" in response.json()["detail"]


# -------------------------
# Test Version and Docs
# -------------------------
class TestVersionAndDocs:
    """Test version and documentation endpoints."""

    async def test_get_version_with_admin_auth(self, client: AsyncClient):
        """Test GET /version with admin auth succeeds."""
        response = await client.get("/version", headers=TEST_AUTH_HEADER)
        assert response.status_code == 200
        result = response.json()
        assert result["app"]["version"]  # non-empty
        assert result["timestamp"]  # ISO date-time string

    async def test_openapi_json_requires_auth(self, client: AsyncClient):
        """Test GET /openapi.json requires authentication."""
        response = await client.get("/openapi.json")
        assert response.status_code in [401, 403]

    # TODO: FIXME
    # async def test_openapi_json_with_auth(self, client: AsyncClient, mock_auth):
    #     """Test GET /openapi.json with authentication."""
    #     response = await client.get("/openapi.json", headers=TEST_AUTH_HEADER)
    #     assert response.status_code == 200
    #     result = response.json()
    #     assert result["info"]["title"] == "ContextForge"

    async def test_docs_requires_auth(self, client: AsyncClient):
        """Test GET /docs requires authentication."""
        response = await client.get("/docs")
        assert response.status_code in [401, 403]

    async def test_redoc_requires_auth(self, client: AsyncClient):
        """Test GET /redoc requires authentication."""
        response = await client.get("/redoc")
        assert response.status_code in [401, 403]


# -------------------------
# Test Root Path Behavior
# -------------------------
class TestRootPath:
    """Test root path behavior based on UI settings."""

    async def test_root_api_info_when_ui_disabled(self, client: AsyncClient):
        """Test GET / returns API info when UI is disabled."""
        # UI should be disabled in test settings
        response = await client.get("/", follow_redirects=False)

        # Could be either API info (200) or redirect to admin (303)
        if response.status_code == 303:
            # UI is enabled, check redirect
            assert "/admin" in response.headers.get("location", "")
        else:
            # UI is disabled, check API info (no version/admin status exposed)
            assert response.status_code == 200
            result = response.json()
            assert "name" in result
            assert "version" not in result
            assert "admin_api_enabled" not in result


# -------------------------
# Test Authentication
# -------------------------
class TestAuthentication:
    """Test authentication requirements."""

    async def test_protected_endpoints_require_auth(self, client: AsyncClient):
        """Test that protected endpoints require authentication when auth is enabled."""
        # First, let's remove ALL auth overrides to test real auth behavior
        # First-Party
        from mcpgateway.auth import get_current_user
        from mcpgateway.middleware.rbac import get_current_user_with_permissions
        from mcpgateway.utils.verify_credentials import require_auth

        # Remove all auth-related overrides temporarily
        original_overrides = {}
        auth_deps = [require_auth, get_current_user_with_permissions, get_current_user]

        for dep in auth_deps:
            original_overrides[dep] = app.dependency_overrides.get(dep)
            app.dependency_overrides.pop(dep, None)

        try:
            # List of endpoints that should require auth
            # Note: /rpc endpoint is not included because when dependency overrides are removed,
            # it processes requests without authentication checks
            protected_endpoints = [
                ("/protocol/initialize", "POST"),
                ("/protocol/ping", "POST"),
                ("/servers", "GET"),
                ("/tools", "GET"),
                ("/resources", "GET"),
                ("/prompts", "GET"),
                ("/gateways", "GET"),
                ("/roots", "GET"),
                ("/metrics", "GET"),
                # ("/rpc", "POST"),  # Excluded - not protected when dependency overrides are removed
            ]

            for endpoint, method in protected_endpoints:
                if method == "GET":
                    response = await client.get(endpoint)
                elif method == "POST":
                    response = await client.post(endpoint, json={})

                # Should return 401 or 403 without auth
                assert response.status_code in [401, 403], f"Endpoint {endpoint} did not require auth (got {response.status_code}: {response.text})"
        finally:
            # Restore all overrides
            for dep, original in original_overrides.items():
                if original is not None:
                    app.dependency_overrides[dep] = original

    async def test_public_endpoints(self, client: AsyncClient):
        """Test that public endpoints don't require authentication."""
        public_endpoints = [
            ("/health", "GET"),
            ("/ready", "GET"),
            # Version might require auth based on settings
            # ("/version", "GET"),
            # Root path might redirect
            # ("/", "GET"),
        ]

        for endpoint, method in public_endpoints:
            if method == "GET":
                response = await client.get(endpoint)

            # Should not return auth errors
            assert response.status_code not in [401, 403], f"Endpoint {endpoint} unexpectedly required auth"
            assert response.status_code == 200


# -------------------------
# Test Error Handling
# -------------------------
class TestErrorHandling:
    """Test error handling and edge cases."""

    async def test_404_for_invalid_endpoints(self, client: AsyncClient, mock_auth):
        """Test that invalid endpoints return 404."""
        response = await client.get("/invalid-endpoint", headers=TEST_AUTH_HEADER)
        assert response.status_code == 404
        assert response.json()["detail"] == "Not Found"

    async def test_malformed_json(self, client: AsyncClient, mock_auth):
        """Test handling of malformed JSON."""
        response = await client.post("/tools", content=b'{"invalid json', headers={**TEST_AUTH_HEADER, "Content-Type": "application/json"})
        assert response.status_code == 422

    async def test_empty_request_body(self, client: AsyncClient, mock_auth):
        """Test handling of empty request body."""
        response = await client.post("/tools", json={"tool": {}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422
        # Should have validation errors for required fields
        errors = response.json()["detail"]
        assert any("Field required" in str(error) for error in errors)

    async def test_internal_server_error(self, client: AsyncClient, mock_auth):
        """Test that non-existent endpoints return proper error responses."""
        # Test 404 error handling for non-existent endpoint
        response = await client.get("/nonexistent-endpoint-12345", headers=TEST_AUTH_HEADER)
        assert response.status_code == 404

        # Test 405 Method Not Allowed
        response = await client.delete("/health", headers=TEST_AUTH_HEADER)
        assert response.status_code == 405

    async def test_validation_error(self, client: AsyncClient, mock_auth):
        """Test validation error for endpoint expecting required fields."""
        response = await client.post("/tools", json={"tool": {}}, headers=TEST_AUTH_HEADER)
        assert response.status_code == 422

    async def test_database_integrity_error(self, client: AsyncClient, mock_auth):
        """Test DB integrity error by creating duplicate (team_id, owner_email, name) for authenticated user."""
        # Only vary team_id and name, owner_email is set by auth context
        server_data_1 = {"server": {"name": "unique_server"}, "team_id": "teamA", "visibility": "private"}
        server_data_2 = {"server": {"name": "unique_server"}, "team_id": "teamA", "visibility": "private"}
        server_data_3 = {"server": {"name": "unique_server"}, "team_id": "teamB", "visibility": "private"}

        # Create first server (teamA, authenticated user)
        response = await client.post("/servers", json=server_data_1, headers=TEST_AUTH_HEADER)
        assert response.status_code == 201

        # Try to create duplicate with same team_id, name - must return 409 or 400
        response = await client.post("/servers", json=server_data_2, headers=TEST_AUTH_HEADER)
        assert response.status_code in [400, 409]

        # Create with different team_id (should succeed)
        response = await client.post("/servers", json=server_data_3, headers=TEST_AUTH_HEADER)
        assert response.status_code == 201

    async def test_root_path_returns_api_info(self, client: AsyncClient, mock_settings):
        """Test GET / returns API info when UI is disabled, or redirects if UI is enabled."""
        response = await client.get("/", follow_redirects=False)
        if response.status_code == 303:
            # UI is enabled, check redirect
            assert "/admin" in response.headers.get("location", "")
        else:
            # UI is disabled, check API info. ``root_info()`` returns
            # ``{"name": <app_name>, "description": f"{app_name} API"}``.
            assert response.status_code == 200
            result = response.json()
            assert "name" in result
            assert "description" in result


# -------------------------
# Test Integration Scenarios
# -------------------------
class TestIntegrationScenarios:
    async def test_create_and_use_tool(self, client: AsyncClient, mock_auth):
        """Integration: create a tool and use it in a server association."""
        tool_data = {"tool": {"name": "integration_tool", "description": "desc", "inputSchema": {"type": "object"}}, "team_id": None, "visibility": "private"}
        tool_resp = await client.post("/tools", json=tool_data, headers=TEST_AUTH_HEADER)
        assert tool_resp.status_code == 200
        tool_id = tool_resp.json()["id"]
        server_data = {"server": {"name": "integration_server", "associatedTools": [tool_id]}, "team_id": None, "visibility": "private"}
        server_resp = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        assert server_resp.status_code == 201
        server = server_resp.json()
        # The server creation might not associate tools in the same request
        if not server.get("associatedTools"):
            # May need to use a separate endpoint to associate tools
            # For now, just verify the server was created
            assert server["name"] == "integration_server"
            assert "id" in server
        else:
            assert tool_id in server.get("associatedTools", [])

    async def test_create_and_use_resource(self, client: AsyncClient, mock_auth):
        """Integration: create a resource and read it back."""
        # ``visibility="public"`` so the public-only test JWT can read it back —
        # see ``test_get_server`` for PR #4341 reasoning.
        resource_data = {"resource": {"uri": "resource://test", "name": "integration_resource", "content": "test"}, "team_id": None, "visibility": "public"}
        create_resp = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        assert create_resp.status_code == 200
        resource_id = create_resp.json()["id"]
        get_resp = await client.get(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)
        assert get_resp.status_code == 200
        assert get_resp.json()["uri"] == resource_data["resource"]["uri"]

    """Test complete integration scenarios."""

    async def test_create_virtual_server_with_tools(self, client: AsyncClient, mock_auth):
        """Test creating a virtual server with associated tools."""
        # Step 1: Create tools
        tool1_data = {
            "tool": {
                "name": "calculator_add",
                "description": "Add two numbers",
                "inputSchema": {"type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}, "required": ["a", "b"]},
            },
            "team_id": None,
            "visibility": "private",
        }

        tool2_data = {
            "tool": {
                "name": "calculator_multiply",
                "description": "Multiply two numbers",
                "inputSchema": {"type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}, "required": ["a", "b"]},
            },
            "team_id": None,
            "visibility": "private",
        }

        tool1_response = await client.post("/tools", json=tool1_data, headers=TEST_AUTH_HEADER)
        tool2_response = await client.post("/tools", json=tool2_data, headers=TEST_AUTH_HEADER)

        tool1_id = tool1_response.json()["id"]
        tool2_id = tool2_response.json()["id"]

        # Step 2: Create virtual server with tools
        server_data = {"server": {"name": "calculator_server", "description": "Calculator utilities", "associatedTools": [tool1_id, tool2_id]}, "team_id": None, "visibility": "private"}

        server_response = await client.post("/servers", json=server_data, headers=TEST_AUTH_HEADER)
        assert server_response.status_code == 201
        server = server_response.json()

        # The server creation might not associate tools in the same request
        # Try associating tools separately if needed
        if not server.get("associatedTools"):
            # May need to use a separate endpoint to associate tools
            # For now, just verify the server was created
            assert server["name"] == "calculator_server"
            assert server["description"] == "Calculator utilities"
        else:
            # Step 3: Verify server has tools
            tools_response = await client.get(f"/servers/{server['id']}/tools", headers=TEST_AUTH_HEADER)
            assert tools_response.status_code == 200
            tools = tools_response.json()
            assert len(tools) == 2
            assert any(t["originalName"] == "calculator_add" for t in tools)
            assert any(t["originalName"] == "calculator_multiply" for t in tools)

    async def test_complete_resource_lifecycle(self, client: AsyncClient, mock_auth):
        """Test complete resource lifecycle: create, read, update, delete."""
        # Create - use URI without extension to avoid MIME type detection override.
        # ``visibility="public"`` so the public-only test JWT can read it back —
        # see ``test_get_server`` for PR #4341 reasoning.
        resource_data = {
            "resource": {"uri": "file:///home/user/documents/report", "name": "lifecycle_test", "content": "Initial content", "mimeType": "text/plain"},
            "team_id": None,
            "visibility": "public",
        }

        create_response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER)
        assert create_response.status_code == 200, f"Failed to create resource: {create_response.text}"
        resource_id = create_response.json()["id"]

        # Read
        read_response = await client.get(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)
        assert read_response.status_code == 200

        # Update
        update_response = await client.put(f"/resources/{resource_id}", json={"content": "Updated content"}, headers=TEST_AUTH_HEADER)
        assert update_response.status_code == 200

        # Verify update
        verify_response = await client.get(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)
        assert verify_response.status_code == 200
        # Note: The actual content check would depend on ResourceContent model structure

        # Delete
        delete_response = await client.delete(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)
        assert delete_response.status_code == 200

        # Verify deletion
        final_response = await client.get(f"/resources/{resource_id}", headers=TEST_AUTH_HEADER)
        assert final_response.status_code == 404


# Run tests with pytest
if __name__ == "__main__":
    pytest.main([__file__, "-v"])


# Note: To run these tests, install the required dependencies:
# pip install pytest pytest-asyncio httpx

# Also, make sure to set the following environment variables or they will use defaults:
# export MCPGATEWAY_AUTH_REQUIRED=false  # To disable auth in tests
# Or the tests will override authentication automatically
