"""HTTP clients for wl-registry API.

Four client classes organized by authentication level:

- WlRegistryClient: Public endpoints (async)
- WlRegistryScannerClient: Scanner-authenticated endpoints (async)
- WlRegistryAgentClient: Agent-authenticated endpoints (async)
- WlRegistryAdminClient: Admin-authenticated endpoints (async)

Example:
    >>> async with WlRegistryClient("http://localhost:8080/api/v1") as client:
    ...     servers = await client.list_servers()
    ...     print(f"Found {servers.total} servers")
"""

from __future__ import annotations

import logging
from typing import Any

import httpx

from .exceptions import (
    AuthenticationError,
    ConflictError,
    NotFoundError,
    ServiceUnavailable,
    ValidationError,
)
from .models import (
    AgentDetail,
    AgentStatsResponse,
    CreateAgentRequest,
    CreateAgentResponse,
    CreateRegistrationTokenRequest,
    CreateScannerRequest,
    CreateScannerResponse,
    GrantPermissionRequest,
    HealthResponse,
    HeartbeatRequest,
    HeartbeatResponse,
    ListAccessibleServersResponse,
    ListAgentsResponse,
    ListPermissionsResponse,
    ListRegistrationTokensResponse,
    ListScannersResponse,
    ListServersResponse,
    PermissionListItem,
    RegisterServerRequest,
    RegisterServerResponse,
    RegistrationConfigResponse,
    RegistrationTokenResponse,
    RotateKeyResponse,
    SelfRegisterAgentRequest,
    SelfRegisterAgentResponse,
    ServerDetail,
    UpdateAgentRequest,
    UpdateTrustRequest,
    UpdateTrustResponse,
)

logger = logging.getLogger("wl_registry")


def _handle_error(response: httpx.Response) -> None:
    """Handle HTTP error responses."""
    if response.status_code < 400:
        return
    try:
        data = response.json()
        msg = data.get("error", data.get("message", response.text))
        details = data.get("details")
    except Exception:
        msg = response.text
        details = None

    if response.status_code in (401, 403):
        raise AuthenticationError(str(msg))
    elif response.status_code == 404:
        raise NotFoundError(str(msg))
    elif response.status_code in (400, 422):
        raise ValidationError(str(msg), details=str(details) if details else None)
    elif response.status_code == 409:
        raise ConflictError(str(msg))
    else:
        response.raise_for_status()


# =============================================================================
# Public Client (no auth required)
# =============================================================================


class WlRegistryClient:
    """Async client for public wl-registry API endpoints.

    No authentication required.

    Example:
        >>> async with WlRegistryClient() as client:
        ...     config = await client.get_registration_config()
        ...     if config.mode == "open":
        ...         resp = await client.register_agent(
        ...             SelfRegisterAgentRequest(name="my-agent", agent_type="autonomous")
        ...         )
        ...         print(f"Agent ID: {resp.id}, API key: {resp.api_key}")
    """

    def __init__(
        self,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> WlRegistryClient:
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=self.timeout,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _ensure_client(self) -> httpx.AsyncClient:
        if self._client is None:
            raise RuntimeError(
                "Client not initialized. Use 'async with WlRegistryClient() as client:'"
            )
        return self._client

    async def health(self) -> bool:
        """Check if the registry service is healthy."""
        client = self._ensure_client()
        try:
            response = await client.get("/health")
            return response.status_code == 200
        except Exception:
            return False

    async def get_health(self) -> HealthResponse:
        """Get health status details."""
        client = self._ensure_client()
        try:
            response = await client.get("/health")
            response.raise_for_status()
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return HealthResponse(**response.json())

    async def get_registration_config(self) -> RegistrationConfigResponse:
        """Get agent registration configuration."""
        client = self._ensure_client()
        try:
            response = await client.get("/agents/registration-config")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RegistrationConfigResponse(**response.json())

    async def register_agent(
        self,
        request: SelfRegisterAgentRequest,
    ) -> SelfRegisterAgentResponse:
        """Self-register an agent."""
        client = self._ensure_client()
        try:
            response = await client.post(
                "/agents/register",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return SelfRegisterAgentResponse(**response.json())


# =============================================================================
# Scanner Client (x-scanner-id + x-api-key)
# =============================================================================


class WlRegistryScannerClient:
    """Async client for scanner-authenticated wl-registry API endpoints.

    Example:
        >>> async with WlRegistryScannerClient(
        ...     scanner_id="uuid-here",
        ...     api_key="key-here",
        ... ) as client:
        ...     resp = await client.register_server(request)
        ...     print(f"Server {resp.server_id} registered")
    """

    def __init__(
        self,
        scanner_id: str,
        api_key: str,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.scanner_id = scanner_id
        self.api_key = api_key
        self.timeout = timeout
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> WlRegistryScannerClient:
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            headers={
                "x-scanner-id": self.scanner_id,
                "x-api-key": self.api_key,
            },
            timeout=self.timeout,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _ensure_client(self) -> httpx.AsyncClient:
        if self._client is None:
            raise RuntimeError(
                "Client not initialized. Use 'async with WlRegistryScannerClient() as client:'"
            )
        return self._client

    async def register_server(
        self,
        request: RegisterServerRequest,
    ) -> RegisterServerResponse:
        """Register an MCP server."""
        client = self._ensure_client()
        try:
            response = await client.post(
                "/servers/register",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RegisterServerResponse(**response.json())

    async def heartbeat(self, server_id: str, lease_ttl_seconds: int = 60) -> HeartbeatResponse:
        """Send a server heartbeat to renew its lease."""
        client = self._ensure_client()
        request = HeartbeatRequest(lease_ttl_seconds=lease_ttl_seconds)
        try:
            response = await client.post(
                f"/servers/{server_id}/heartbeat",
                json=request.model_dump(),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return HeartbeatResponse(**response.json())

    async def update_trust(
        self,
        server_id: str,
        trust_state: str,
    ) -> UpdateTrustResponse:
        """Update a server's trust state."""
        client = self._ensure_client()
        request = UpdateTrustRequest(trust_state=trust_state)
        try:
            response = await client.post(
                f"/servers/{server_id}/trust",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return UpdateTrustResponse(**response.json())


# =============================================================================
# Agent Client (x-agent-id + x-api-key)
# =============================================================================


class WlRegistryAgentClient:
    """Async client for agent-authenticated wl-registry API endpoints.

    Example:
        >>> async with WlRegistryAgentClient(
        ...     agent_id="uuid-here",
        ...     api_key="key-here",
        ... ) as client:
        ...     servers = await client.get_accessible_servers()
        ...     for s in servers.servers:
        ...         print(f"Can access: {s.server_name}")
    """

    def __init__(
        self,
        agent_id: str,
        api_key: str,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.agent_id = agent_id
        self.api_key = api_key
        self.timeout = timeout
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> WlRegistryAgentClient:
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            headers={
                "x-agent-id": self.agent_id,
                "x-api-key": self.api_key,
            },
            timeout=self.timeout,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _ensure_client(self) -> httpx.AsyncClient:
        if self._client is None:
            raise RuntimeError(
                "Client not initialized. Use 'async with WlRegistryAgentClient() as client:'"
            )
        return self._client

    async def get_accessible_servers(self) -> ListAccessibleServersResponse:
        """Get servers accessible to this agent."""
        client = self._ensure_client()
        try:
            response = await client.get(f"/agents/{self.agent_id}/servers")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListAccessibleServersResponse(**response.json())

    async def heartbeat(self) -> None:
        """Send an agent heartbeat."""
        client = self._ensure_client()
        try:
            response = await client.post(f"/agents/{self.agent_id}/heartbeat")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e


# =============================================================================
# Admin Client (x-admin-key or session auth)
# =============================================================================


class WlRegistryAdminClient:
    """Async client for admin wl-registry API endpoints.

    Example:
        >>> async with WlRegistryAdminClient(admin_key="key-here") as client:
        ...     agents = await client.list_agents()
        ...     print(f"Total agents: {agents.total}")
    """

    def __init__(
        self,
        admin_key: str,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.admin_key = admin_key
        self.timeout = timeout
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> WlRegistryAdminClient:
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            headers={"x-admin-key": self.admin_key},
            timeout=self.timeout,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _ensure_client(self) -> httpx.AsyncClient:
        if self._client is None:
            raise RuntimeError(
                "Client not initialized. Use 'async with WlRegistryAdminClient() as client:'"
            )
        return self._client

    # --- Servers ---

    async def list_servers(
        self,
        trust_state: str | None = None,
        status: str | None = None,
        limit: int = 50,
        cursor: str | None = None,
    ) -> ListServersResponse:
        """List registered servers."""
        client = self._ensure_client()
        params: dict[str, Any] = {"limit": limit}
        if trust_state:
            params["trust_state"] = trust_state
        if status:
            params["status"] = status
        if cursor:
            params["cursor"] = cursor
        try:
            response = await client.get("/servers", params=params)
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListServersResponse(**response.json())

    async def get_server(self, server_id: str) -> ServerDetail:
        """Get detailed server information."""
        client = self._ensure_client()
        try:
            response = await client.get(f"/servers/{server_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ServerDetail(**response.json())

    async def update_server_trust(
        self,
        server_id: str,
        trust_state: str,
    ) -> UpdateTrustResponse:
        """Update a server's trust state."""
        client = self._ensure_client()
        request = UpdateTrustRequest(trust_state=trust_state)
        try:
            response = await client.post(
                f"/admin/servers/{server_id}/trust",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return UpdateTrustResponse(**response.json())

    # --- Agents ---

    async def list_agents(
        self,
        status: str | None = None,
        agent_type: str | None = None,
        trust_state: str | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> ListAgentsResponse:
        """List registered agents."""
        client = self._ensure_client()
        params: dict[str, Any] = {"limit": limit, "offset": offset}
        if status:
            params["status"] = status
        if agent_type:
            params["agent_type"] = agent_type
        if trust_state:
            params["trust_state"] = trust_state
        try:
            response = await client.get("/admin/agents", params=params)
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListAgentsResponse(**response.json())

    async def create_agent(self, request: CreateAgentRequest) -> CreateAgentResponse:
        """Create a new agent."""
        client = self._ensure_client()
        try:
            response = await client.post(
                "/admin/agents",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return CreateAgentResponse(**response.json())

    async def get_agent(self, agent_id: str) -> AgentDetail:
        """Get detailed agent information."""
        client = self._ensure_client()
        try:
            response = await client.get(f"/admin/agents/{agent_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return AgentDetail(**response.json())

    async def update_agent(self, agent_id: str, request: UpdateAgentRequest) -> AgentDetail:
        """Update an agent."""
        client = self._ensure_client()
        try:
            response = await client.put(
                f"/admin/agents/{agent_id}",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return AgentDetail(**response.json())

    async def delete_agent(self, agent_id: str) -> None:
        """Delete (deactivate) an agent."""
        client = self._ensure_client()
        try:
            response = await client.delete(f"/admin/agents/{agent_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e

    async def update_agent_trust(
        self,
        agent_id: str,
        trust_state: str,
        reason: str | None = None,
    ) -> UpdateTrustResponse:
        """Update an agent's trust state."""
        client = self._ensure_client()
        request = UpdateTrustRequest(trust_state=trust_state, reason=reason)
        try:
            response = await client.post(
                f"/admin/agents/{agent_id}/trust",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return UpdateTrustResponse(**response.json())

    async def rotate_agent_key(self, agent_id: str) -> RotateKeyResponse:
        """Rotate an agent's API key."""
        client = self._ensure_client()
        try:
            response = await client.post(f"/admin/agents/{agent_id}/rotate-key")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RotateKeyResponse(**response.json())

    async def get_agent_stats(self) -> AgentStatsResponse:
        """Get agent statistics."""
        client = self._ensure_client()
        try:
            response = await client.get("/admin/agents/stats")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return AgentStatsResponse(**response.json())

    # --- Permissions ---

    async def list_permissions(self, agent_id: str) -> ListPermissionsResponse:
        """List an agent's permissions."""
        client = self._ensure_client()
        try:
            response = await client.get(f"/admin/agents/{agent_id}/permissions")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListPermissionsResponse(**response.json())

    async def grant_permission(
        self,
        agent_id: str,
        request: GrantPermissionRequest,
    ) -> PermissionListItem:
        """Grant an agent permission to access a server."""
        client = self._ensure_client()
        try:
            response = await client.post(
                f"/admin/agents/{agent_id}/permissions",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return PermissionListItem(**response.json())

    async def revoke_permission(self, agent_id: str, server_id: str) -> None:
        """Revoke an agent's permission to a server."""
        client = self._ensure_client()
        try:
            response = await client.delete(
                f"/admin/agents/{agent_id}/permissions/{server_id}"
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e

    # --- Scanners ---

    async def list_scanners(self) -> ListScannersResponse:
        """List registered scanners."""
        client = self._ensure_client()
        try:
            response = await client.get("/admin/scanners")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListScannersResponse(**response.json())

    async def create_scanner(self, request: CreateScannerRequest) -> CreateScannerResponse:
        """Create a new scanner."""
        client = self._ensure_client()
        try:
            response = await client.post(
                "/admin/scanners",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return CreateScannerResponse(**response.json())

    async def delete_scanner(self, scanner_id: str) -> None:
        """Delete a scanner."""
        client = self._ensure_client()
        try:
            response = await client.delete(f"/admin/scanners/{scanner_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e

    async def rotate_scanner_key(self, scanner_id: str) -> RotateKeyResponse:
        """Rotate a scanner's API key."""
        client = self._ensure_client()
        try:
            response = await client.post(f"/admin/scanners/{scanner_id}/rotate-key")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RotateKeyResponse(**response.json())

    # --- Registration Tokens ---

    async def list_registration_tokens(self) -> ListRegistrationTokensResponse:
        """List agent registration tokens."""
        client = self._ensure_client()
        try:
            response = await client.get("/admin/agents/registration-tokens")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListRegistrationTokensResponse(**response.json())

    async def create_registration_token(
        self,
        request: CreateRegistrationTokenRequest,
    ) -> RegistrationTokenResponse:
        """Create an agent registration token."""
        client = self._ensure_client()
        try:
            response = await client.post(
                "/admin/agents/registration-tokens",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RegistrationTokenResponse(**response.json())

    async def delete_registration_token(self, token_id: str) -> None:
        """Delete a registration token."""
        client = self._ensure_client()
        try:
            response = await client.delete(f"/admin/agents/registration-tokens/{token_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e


# =============================================================================
# Sync Clients
# =============================================================================


def _handle_error_sync(response: httpx.Response) -> None:
    """Handle HTTP error responses (sync variant, delegates to shared logic)."""
    _handle_error(response)


class WlRegistrySyncClient:
    """Synchronous client for public wl-registry API endpoints.

    Example:
        >>> with WlRegistrySyncClient() as client:
        ...     config = client.get_registration_config()
        ...     if config.mode == "open":
        ...         resp = client.register_agent(
        ...             SelfRegisterAgentRequest(name="my-agent")
        ...         )
    """

    def __init__(
        self,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self._client = httpx.Client(base_url=self.base_url, timeout=timeout)

    def __enter__(self) -> WlRegistrySyncClient:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def close(self) -> None:
        """Close the HTTP client."""
        self._client.close()

    def health(self) -> bool:
        """Check if the registry service is healthy."""
        try:
            response = self._client.get("/health")
            return response.status_code == 200
        except Exception:
            return False

    def get_health(self) -> HealthResponse:
        """Get health status details."""
        try:
            response = self._client.get("/health")
            response.raise_for_status()
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return HealthResponse(**response.json())

    def get_registration_config(self) -> RegistrationConfigResponse:
        """Get agent registration configuration."""
        try:
            response = self._client.get("/agents/registration-config")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RegistrationConfigResponse(**response.json())

    def register_agent(
        self,
        request: SelfRegisterAgentRequest,
    ) -> SelfRegisterAgentResponse:
        """Self-register an agent."""
        try:
            response = self._client.post(
                "/agents/register",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return SelfRegisterAgentResponse(**response.json())


class WlRegistryScannerSyncClient:
    """Synchronous client for scanner-authenticated wl-registry API endpoints.

    Example:
        >>> with WlRegistryScannerSyncClient(
        ...     scanner_id="uuid", api_key="key"
        ... ) as client:
        ...     resp = client.register_server(request)
    """

    def __init__(
        self,
        scanner_id: str,
        api_key: str,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.scanner_id = scanner_id
        self.api_key = api_key
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={"x-scanner-id": scanner_id, "x-api-key": api_key},
            timeout=timeout,
        )

    def __enter__(self) -> WlRegistryScannerSyncClient:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def close(self) -> None:
        """Close the HTTP client."""
        self._client.close()

    def register_server(self, request: RegisterServerRequest) -> RegisterServerResponse:
        """Register an MCP server."""
        try:
            response = self._client.post(
                "/servers/register",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RegisterServerResponse(**response.json())

    def heartbeat(self, server_id: str, lease_ttl_seconds: int = 60) -> HeartbeatResponse:
        """Send a server heartbeat to renew its lease."""
        request = HeartbeatRequest(lease_ttl_seconds=lease_ttl_seconds)
        try:
            response = self._client.post(
                f"/servers/{server_id}/heartbeat",
                json=request.model_dump(),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return HeartbeatResponse(**response.json())

    def update_trust(self, server_id: str, trust_state: str) -> UpdateTrustResponse:
        """Update a server's trust state."""
        request = UpdateTrustRequest(trust_state=trust_state)
        try:
            response = self._client.post(
                f"/servers/{server_id}/trust",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return UpdateTrustResponse(**response.json())


class WlRegistryAgentSyncClient:
    """Synchronous client for agent-authenticated wl-registry API endpoints.

    Example:
        >>> with WlRegistryAgentSyncClient(
        ...     agent_id="uuid", api_key="key"
        ... ) as client:
        ...     servers = client.get_accessible_servers()
    """

    def __init__(
        self,
        agent_id: str,
        api_key: str,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.agent_id = agent_id
        self.api_key = api_key
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={"x-agent-id": agent_id, "x-api-key": api_key},
            timeout=timeout,
        )

    def __enter__(self) -> WlRegistryAgentSyncClient:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def close(self) -> None:
        """Close the HTTP client."""
        self._client.close()

    def get_accessible_servers(self) -> ListAccessibleServersResponse:
        """Get servers accessible to this agent."""
        try:
            response = self._client.get(f"/agents/{self.agent_id}/servers")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListAccessibleServersResponse(**response.json())

    def heartbeat(self) -> None:
        """Send an agent heartbeat."""
        try:
            response = self._client.post(f"/agents/{self.agent_id}/heartbeat")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e


class WlRegistryAdminSyncClient:
    """Synchronous client for admin wl-registry API endpoints.

    Example:
        >>> with WlRegistryAdminSyncClient(admin_key="key") as client:
        ...     agents = client.list_agents()
        ...     print(f"Total: {agents.total}")
    """

    def __init__(
        self,
        admin_key: str,
        base_url: str = "http://localhost:8080/api/v1",
        timeout: float = 10.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.admin_key = admin_key
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={"x-admin-key": admin_key},
            timeout=timeout,
        )

    def __enter__(self) -> WlRegistryAdminSyncClient:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def close(self) -> None:
        """Close the HTTP client."""
        self._client.close()

    # --- Servers ---

    def list_servers(
        self,
        trust_state: str | None = None,
        status: str | None = None,
        limit: int = 50,
        cursor: str | None = None,
    ) -> ListServersResponse:
        """List registered servers."""
        params: dict[str, Any] = {"limit": limit}
        if trust_state:
            params["trust_state"] = trust_state
        if status:
            params["status"] = status
        if cursor:
            params["cursor"] = cursor
        try:
            response = self._client.get("/servers", params=params)
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListServersResponse(**response.json())

    def get_server(self, server_id: str) -> ServerDetail:
        """Get detailed server information."""
        try:
            response = self._client.get(f"/servers/{server_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ServerDetail(**response.json())

    def update_server_trust(self, server_id: str, trust_state: str) -> UpdateTrustResponse:
        """Update a server's trust state."""
        request = UpdateTrustRequest(trust_state=trust_state)
        try:
            response = self._client.post(
                f"/admin/servers/{server_id}/trust",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return UpdateTrustResponse(**response.json())

    # --- Agents ---

    def list_agents(
        self,
        status: str | None = None,
        agent_type: str | None = None,
        trust_state: str | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> ListAgentsResponse:
        """List registered agents."""
        params: dict[str, Any] = {"limit": limit, "offset": offset}
        if status:
            params["status"] = status
        if agent_type:
            params["agent_type"] = agent_type
        if trust_state:
            params["trust_state"] = trust_state
        try:
            response = self._client.get("/admin/agents", params=params)
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListAgentsResponse(**response.json())

    def create_agent(self, request: CreateAgentRequest) -> CreateAgentResponse:
        """Create a new agent."""
        try:
            response = self._client.post(
                "/admin/agents",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return CreateAgentResponse(**response.json())

    def get_agent(self, agent_id: str) -> AgentDetail:
        """Get detailed agent information."""
        try:
            response = self._client.get(f"/admin/agents/{agent_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return AgentDetail(**response.json())

    def update_agent(self, agent_id: str, request: UpdateAgentRequest) -> AgentDetail:
        """Update an agent."""
        try:
            response = self._client.put(
                f"/admin/agents/{agent_id}",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return AgentDetail(**response.json())

    def delete_agent(self, agent_id: str) -> None:
        """Delete (deactivate) an agent."""
        try:
            response = self._client.delete(f"/admin/agents/{agent_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e

    def update_agent_trust(
        self,
        agent_id: str,
        trust_state: str,
        reason: str | None = None,
    ) -> UpdateTrustResponse:
        """Update an agent's trust state."""
        request = UpdateTrustRequest(trust_state=trust_state, reason=reason)
        try:
            response = self._client.post(
                f"/admin/agents/{agent_id}/trust",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return UpdateTrustResponse(**response.json())

    def rotate_agent_key(self, agent_id: str) -> RotateKeyResponse:
        """Rotate an agent's API key."""
        try:
            response = self._client.post(f"/admin/agents/{agent_id}/rotate-key")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RotateKeyResponse(**response.json())

    def get_agent_stats(self) -> AgentStatsResponse:
        """Get agent statistics."""
        try:
            response = self._client.get("/admin/agents/stats")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return AgentStatsResponse(**response.json())

    # --- Permissions ---

    def list_permissions(self, agent_id: str) -> ListPermissionsResponse:
        """List an agent's permissions."""
        try:
            response = self._client.get(f"/admin/agents/{agent_id}/permissions")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListPermissionsResponse(**response.json())

    def grant_permission(
        self,
        agent_id: str,
        request: GrantPermissionRequest,
    ) -> PermissionListItem:
        """Grant an agent permission to access a server."""
        try:
            response = self._client.post(
                f"/admin/agents/{agent_id}/permissions",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return PermissionListItem(**response.json())

    def revoke_permission(self, agent_id: str, server_id: str) -> None:
        """Revoke an agent's permission to a server."""
        try:
            response = self._client.delete(
                f"/admin/agents/{agent_id}/permissions/{server_id}"
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e

    # --- Scanners ---

    def list_scanners(self) -> ListScannersResponse:
        """List registered scanners."""
        try:
            response = self._client.get("/admin/scanners")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListScannersResponse(**response.json())

    def create_scanner(self, request: CreateScannerRequest) -> CreateScannerResponse:
        """Create a new scanner."""
        try:
            response = self._client.post(
                "/admin/scanners",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return CreateScannerResponse(**response.json())

    def delete_scanner(self, scanner_id: str) -> None:
        """Delete a scanner."""
        try:
            response = self._client.delete(f"/admin/scanners/{scanner_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e

    def rotate_scanner_key(self, scanner_id: str) -> RotateKeyResponse:
        """Rotate a scanner's API key."""
        try:
            response = self._client.post(f"/admin/scanners/{scanner_id}/rotate-key")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RotateKeyResponse(**response.json())

    # --- Registration Tokens ---

    def list_registration_tokens(self) -> ListRegistrationTokensResponse:
        """List agent registration tokens."""
        try:
            response = self._client.get("/admin/agents/registration-tokens")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return ListRegistrationTokensResponse(**response.json())

    def create_registration_token(
        self,
        request: CreateRegistrationTokenRequest,
    ) -> RegistrationTokenResponse:
        """Create an agent registration token."""
        try:
            response = self._client.post(
                "/admin/agents/registration-tokens",
                json=request.model_dump(exclude_none=True),
            )
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return RegistrationTokenResponse(**response.json())

    def delete_registration_token(self, token_id: str) -> None:
        """Delete a registration token."""
        try:
            response = self._client.delete(f"/admin/agents/registration-tokens/{token_id}")
            _handle_error(response)
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
