"""E2E integration tests for MCPGateway Python SDK.

These tests run against a live backend (http://localhost:8000) with
AUTO_BOOTSTRAP_LOCAL_AUTH=true, which auto-creates a dev owner user
and prints the API key to docker logs.

Note: LOCAL_DEV_MODE only controls server transports/execution.
It does NOT bypass authentication.

Run with:
    E2E_TESTS_ENABLED=true SDK_API_KEY=<key-from-logs> uv run pytest tests/integration/test_sdk_e2e.py -v
"""

from __future__ import annotations

import os

import pytest

from mcpgateway_sdk import AuthError, GatewayError, MCPGateway, NotFoundError

GATEWAY_URL = os.environ.get("GATEWAY_URL", "http://localhost:8000")
SDK_API_KEY = os.environ.get("SDK_API_KEY", "")

pytestmark = [
    pytest.mark.asyncio,
    pytest.mark.skipif(
        not os.environ.get("E2E_TESTS_ENABLED"),
        reason="E2E tests disabled (set E2E_TESTS_ENABLED=true)",
    ),
]


# ------------------------------------------------------------------
# Fixtures
# ------------------------------------------------------------------


@pytest.fixture
async def gw():
    """Provide an authenticated MCPGateway client, closed after the test."""
    async with MCPGateway(api_key=SDK_API_KEY, url=GATEWAY_URL) as client:
        yield client


# ------------------------------------------------------------------
# Test 1: Connect and list servers
# ------------------------------------------------------------------


async def test_connect_and_list_servers(gw: MCPGateway) -> None:
    """User connects to gateway and lists available servers."""
    servers = await gw.servers.list()

    assert isinstance(servers, list)
    # May be empty -- just verify the SDK call succeeds and returns the right type


# ------------------------------------------------------------------
# Test 2: Browse server catalog
# ------------------------------------------------------------------


async def test_browse_server_catalog(gw: MCPGateway) -> None:
    """User browses the MCP server catalog."""
    results = await gw.servers.catalog_search()

    assert isinstance(results, list)
    # Catalog should have entries if the backend was seeded
    if results:
        entry = results[0]
        assert isinstance(entry, dict)
        assert "registry_id" in entry or "name" in entry


# ------------------------------------------------------------------
# Test 3: Import server from catalog and list its tools
# ------------------------------------------------------------------


async def test_import_server_and_list_tools(gw: MCPGateway) -> None:
    """User imports a server from catalog, then lists its tools.

    The catalog import endpoint requires ``registry``, ``registry_id``, and
    ``name`` fields.  We extract these from the catalog search response.
    """
    # Arrange: find a catalog entry (e.g. "time" -- simple, no credentials needed)
    catalog = await gw.servers.catalog_search("time")
    if not catalog:
        pytest.skip("No 'time' catalog entries found -- catalog may not be seeded")

    # Pick the first entry that has both registry and registry_id
    entry = None
    for candidate in catalog:
        if candidate.get("registry") and candidate.get("registry_id"):
            entry = candidate
            break

    if entry is None:
        pytest.skip("No catalog entry with registry + registry_id fields found")

    # Act: import using the SDK's (registry, registry_id, name) signature.
    server = await gw.servers.import_from_catalog(
        registry=entry["registry"],
        registry_id=entry["registry_id"],
        name=entry.get("name", "sdk-e2e-import"),
    )

    # Assert: server created correctly
    assert server.id is not None
    assert server.name

    try:
        # List its tools (may be empty if server hasn't synced yet -- that's ok)
        tools = await gw.servers.list_tools(server.id)
        assert isinstance(tools, list)
    finally:
        # Cleanup: always delete the imported server
        await gw.servers.delete(server.id)


# ------------------------------------------------------------------
# Test 4: List skills
# ------------------------------------------------------------------


async def test_list_skills(gw: MCPGateway) -> None:
    """User lists available skills."""
    skills = await gw.skills.list()

    assert isinstance(skills, list)
    # If seeded, there should be starter skills
    if skills:
        skill = skills[0]
        assert skill.id is not None
        assert skill.name


# ------------------------------------------------------------------
# Test 5: List skill templates
# ------------------------------------------------------------------


async def test_list_skill_templates(gw: MCPGateway) -> None:
    """User browses skill templates."""
    templates = await gw.skills.templates()

    assert isinstance(templates, list)


# ------------------------------------------------------------------
# Test 6: Browse skill catalog
# ------------------------------------------------------------------


async def test_browse_skill_catalog(gw: MCPGateway) -> None:
    """User browses the built-in skills catalog."""
    try:
        catalog = await gw.skills.catalog("builtin")
        assert isinstance(catalog, list)
    except GatewayError:
        pytest.skip("Skill catalog unavailable (may need specific config or GitHub token)")


# ------------------------------------------------------------------
# Test 7: List sandbox images
# ------------------------------------------------------------------


async def test_list_sandbox_images(gw: MCPGateway) -> None:
    """User checks available sandbox images."""
    try:
        images = await gw.sandboxes.list_images()
        assert isinstance(images, list)
    except GatewayError:
        pytest.skip("Sandbox images endpoint unavailable (may need K8s runtime)")


# ------------------------------------------------------------------
# Test 8: API key lifecycle (create, list, get, revoke)
# ------------------------------------------------------------------


async def test_api_key_lifecycle(gw: MCPGateway) -> None:
    """User creates, lists, gets, and revokes an API key.

    Requires a valid SDK_API_KEY from AUTO_BOOTSTRAP_LOCAL_AUTH.
    If the backend returns 401, the test is skipped gracefully.
    """
    try:
        # Create
        key = await gw.auth.create_api_key(name="sdk-e2e-test")
    except AuthError:
        pytest.skip("API key endpoints require real auth — set SDK_API_KEY")

    assert key.id is not None
    assert key.name == "sdk-e2e-test"

    try:
        # List -- the new key should appear
        keys = await gw.auth.list_api_keys()
        assert any(k.id == key.id for k in keys)

        # Get by ID
        fetched = await gw.auth.get_api_key(key.id)
        assert fetched.name == "sdk-e2e-test"
        assert fetched.id == key.id
    finally:
        # Revoke (cleanup)
        await gw.auth.revoke_api_key(key.id)

    # Verify the key is gone (or inactive)
    remaining = await gw.auth.list_api_keys()
    assert not any(k.id == key.id and k.is_active for k in remaining)


# ------------------------------------------------------------------
# Test 9: Error handling -- NotFoundError for missing resource
# ------------------------------------------------------------------


async def test_not_found_raises_error(gw: MCPGateway) -> None:
    """SDK raises NotFoundError for missing resources."""
    with pytest.raises(NotFoundError):
        await gw.servers.get("00000000-0000-0000-0000-000000000000")


# ------------------------------------------------------------------
# Test 10: Tool search (semantic)
# ------------------------------------------------------------------


async def test_tool_search(gw: MCPGateway) -> None:
    """User searches for tools across all servers."""
    try:
        results = await gw.tools.search("get current time")
        assert isinstance(results, list)
        # Results may be empty if no servers with embeddings are running
    except GatewayError:
        pytest.skip("Tool search unavailable (may need embedding provider configured)")


# ------------------------------------------------------------------
# Test 11: Full server lifecycle (import → start → health → stop → delete)
# ------------------------------------------------------------------


async def test_server_full_lifecycle(gw: MCPGateway) -> None:
    """User imports a server, starts it, checks health, stops it, deletes it."""
    catalog = await gw.servers.catalog_search("time")
    if not catalog:
        pytest.skip("No catalog entries found")

    entry = None
    for candidate in catalog:
        if candidate.get("registry") and candidate.get("registry_id"):
            entry = candidate
            break
    if entry is None:
        pytest.skip("No usable catalog entry")

    server = await gw.servers.import_from_catalog(
        registry=entry["registry"],
        registry_id=entry["registry_id"],
        name="sdk-lifecycle-test",
    )
    assert server.id is not None

    try:
        # Start the server
        start_result = await gw.servers.start(server.id)
        assert isinstance(start_result, dict)

        # Check health
        health = await gw.servers.health(server.id)
        assert isinstance(health, dict)

        # Get server details
        details = await gw.servers.get(server.id)
        assert details.id == server.id
        assert details.name == "sdk-lifecycle-test"

        # Stop the server
        stop_result = await gw.servers.stop(server.id)
        assert isinstance(stop_result, dict)
    except GatewayError:
        pass  # Server may not support start/stop in dev mode
    finally:
        await gw.servers.delete(server.id)


# ------------------------------------------------------------------
# Test 12: Skill detail and templates
# ------------------------------------------------------------------


async def test_skill_detail(gw: MCPGateway) -> None:
    """User gets skill details including SKILL.md content."""
    skills = await gw.skills.list()
    if not skills:
        pytest.skip("No skills available")

    skill = await gw.skills.get(skills[0].id)
    assert skill.id == skills[0].id
    assert skill.name == skills[0].name


# ------------------------------------------------------------------
# Test 13: Sandbox image templates and package catalog
# ------------------------------------------------------------------


async def test_sandbox_image_templates(gw: MCPGateway) -> None:
    """User browses sandbox image templates."""
    try:
        templates = await gw.sandboxes.image_templates()
        assert isinstance(templates, list)
    except GatewayError:
        pytest.skip("Image templates endpoint unavailable")


async def test_sandbox_package_catalog(gw: MCPGateway) -> None:
    """User browses sandbox package catalog."""
    try:
        packages = await gw.sandboxes.package_catalog()
        assert isinstance(packages, list)
    except GatewayError:
        pytest.skip("Package catalog endpoint unavailable")


# ------------------------------------------------------------------
# Test 15: Server catalog detail
# ------------------------------------------------------------------


async def test_server_catalog_detail(gw: MCPGateway) -> None:
    """User gets detailed info about a specific catalog entry."""
    catalog = await gw.servers.catalog_search()
    if not catalog:
        pytest.skip("No catalog entries")

    entry = catalog[0]
    registry = entry.get("registry")
    registry_id = entry.get("registry_id")
    if not registry or not registry_id:
        pytest.skip("Catalog entry missing registry info")

    detail = await gw.servers.catalog_get(registry, registry_id)
    assert isinstance(detail, dict)


# ------------------------------------------------------------------
# Test 16: Cache operations
# ------------------------------------------------------------------


async def test_cache_get_miss_returns_none(gw: MCPGateway) -> None:
    """Cache miss returns None, not an error."""
    try:
        result = await gw.cache.get("nonexistent-key-e2e-test")
        assert result is None
    except (AuthError, GatewayError):
        pytest.skip("Cache endpoint requires real auth — set SDK_API_KEY")


# ------------------------------------------------------------------
# Test 17: Connection error gives clear GatewayError
# ------------------------------------------------------------------


async def test_connection_error_raises_gateway_error() -> None:
    """Connecting to a non-existent gateway raises GatewayError, not httpx error."""
    async with MCPGateway(api_key="unused", url="http://localhost:19999") as gw:
        with pytest.raises(GatewayError) as exc_info:
            await gw.servers.list()
        assert (
            "connect" in exc_info.value.message.lower()
            or "Cannot connect" in exc_info.value.message
        )


# ------------------------------------------------------------------
# Test 18: Tool execution (requires running server with tools)
# ------------------------------------------------------------------


async def test_tool_execute(gw: MCPGateway) -> None:
    """User executes a tool on a running server."""
    servers = await gw.servers.list()
    running = [s for s in servers if s.status == "running" and s.tool_count > 0]
    if not running:
        pytest.skip("No running servers with tools available")

    # Find a safe tool to execute (prefer 'time' server)
    server = next((s for s in running if "time" in s.name.lower()), running[0])
    tools = await gw.servers.list_tools(server.id)
    if not tools:
        pytest.skip("Server has no tools listed")

    # Pick the first tool
    tool = tools[0]
    tool_name = tool.get("name", "")
    if not tool_name:
        pytest.skip("Tool has no name")

    try:
        # Use server_id (more reliable across backend versions)
        result = await gw.tools.execute(tool_name, {"timezone": "UTC"}, server_id=str(server.id))
        assert result.tool_name is not None
        assert result.duration_ms >= 0
    except GatewayError as e:
        if e.status_code == 422:
            pytest.skip(f"Tool requires specific arguments: {e.message}")
        raise


# ------------------------------------------------------------------
# Test 19: Tool lookup by name
# ------------------------------------------------------------------


async def test_tool_get_by_name(gw: MCPGateway) -> None:
    """User looks up a specific tool by server and tool name."""
    servers = await gw.servers.list()
    running = [s for s in servers if s.status == "running" and s.tool_count > 0]
    if not running:
        pytest.skip("No running servers with tools available")

    server = next((s for s in running if "time" in s.name.lower()), running[0])
    tools = await gw.servers.list_tools(server.id)
    if not tools:
        pytest.skip("No tools on server")

    tool_name = tools[0].get("name", "")
    if not tool_name:
        pytest.skip("Tool has no name")

    try:
        lookup = await gw.tools.get_by_name(server.name, tool_name)
        assert lookup.tool_name == tool_name
        assert lookup.server_name == server.name
    except NotFoundError:
        pytest.skip(
            f"Tool lookup failed for server_name={server.name!r}, tool_name={tool_name!r} "
            "— server may use a different internal name"
        )


# ------------------------------------------------------------------
# Test 20: Server update
# ------------------------------------------------------------------


async def test_server_update(gw: MCPGateway) -> None:
    """User updates server display name."""
    catalog = await gw.servers.catalog_search("time")
    if not catalog:
        pytest.skip("No catalog entries")

    entry = None
    for c in catalog:
        if c.get("registry") and c.get("registry_id"):
            entry = c
            break
    if not entry:
        pytest.skip("No usable catalog entry")

    server = await gw.servers.import_from_catalog(
        registry=entry["registry"],
        registry_id=entry["registry_id"],
        name="sdk-update-test",
    )
    try:
        updated = await gw.servers.update(server.id, display_name="Updated SDK Test")
        assert updated.id == server.id
    finally:
        await gw.servers.delete(server.id)


# ------------------------------------------------------------------
# Test 21: Bulk server health check
# ------------------------------------------------------------------


async def test_bulk_health_check(gw: MCPGateway) -> None:
    """User checks health of all servers at once."""
    try:
        result = await gw.servers.health_all()
        assert isinstance(result, dict)
    except GatewayError:
        pytest.skip("Bulk health endpoint unavailable")
