"""HTTP client wrapper for communicating with FastAPI backend."""

from __future__ import annotations

import asyncio
import logging
import sys
from typing import Any

import httpx

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

from .config import MCPConfig
from .registry import ServerRegistry

__all__ = ["FastAPIClient"]

logger = logging.getLogger(__name__)


class FastAPIClient:
    """Async HTTP client for FastAPI backend.

    Handles connection pooling, retries, and error handling for
    communication with the GDSFactory+ FastAPI server.

    Supports multi-project routing via the server registry.
    """

    def __init__(self, base_url: str | None = None) -> None:
        """Initialize the FastAPI client.

        Args:
            base_url: Base URL for the FastAPI server (default from config)
                     If not provided, will use project-based routing via registry
        """
        self.base_url = base_url or MCPConfig.get_api_url(None)
        self.timeout = MCPConfig.get_timeout()
        self._client: httpx.AsyncClient | None = None
        self._registry = ServerRegistry()

    async def __aenter__(self) -> Self:
        """Enter async context."""
        await self.start()
        return self

    async def __aexit__(self, *args: object) -> None:
        """Exit async context."""
        await self.close()

    async def start(self) -> None:
        """Start the HTTP client with connection pooling."""
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                timeout=httpx.Timeout(self.timeout),
                limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
            )
            logger.debug("HTTP client started with base URL: %s", self.base_url)

    async def close(self) -> None:
        """Close the HTTP client."""
        if self._client is not None:
            await self._client.aclose()
            self._client = None
            logger.debug("HTTP client closed")

    def _resolve_base_url(self, project: str | None = None) -> str:
        """Resolve the base URL for a request.

        Args:
            project: Optional project name/path to route to specific server

        Returns:
            Base URL for the request

        Raises:
            ValueError: If project not found in registry
        """
        # If no project specified, use default base_url
        if project is None:
            return self.base_url

        # Look up project in registry
        server_info = self._registry.get_server_by_project(project)
        if server_info is None:
            msg = (
                f"Project '{project}' not found in registry. "
                "Make sure the server is running for this project."
            )
            raise ValueError(msg)

        return f"http://localhost:{server_info.port}"

    async def request(
        self,
        method: str,
        path: str,
        params: dict[str, Any] | None = None,
        json_data: dict[str, Any] | None = None,
        data: dict[str, Any] | None = None,
        project: str | None = None,
    ) -> Any:
        """Make an HTTP request with retry logic.

        Args:
            method: HTTP method (GET, POST, etc.)
            path: API endpoint path
            params: Query parameters
            json_data: JSON body data
            data: Form data
            project: Optional project name to route to specific server

        Returns:
            Response data (parsed JSON or raw content)

        Raises:
            httpx.HTTPError: If request fails after retries
            ValueError: If project not found in registry
        """
        if self._client is None:
            await self.start()

        # Resolve the base URL for this request
        base_url = self._resolve_base_url(project)

        last_error = None
        backoff = MCPConfig.RETRY_BACKOFF

        for attempt in range(MCPConfig.MAX_RETRIES):
            try:
                logger.debug(
                    "Request attempt %d/%d: %s %s (project=%s, base=%s)",
                    attempt + 1,
                    MCPConfig.MAX_RETRIES,
                    method,
                    path,
                    project or "default",
                    base_url,
                )

                # Build full URL with the resolved base URL
                full_url = f"{base_url}{path}"

                response = await self._client.request(  # type: ignore[union-attr]
                    method=method,
                    url=full_url,
                    params=params,
                    json=json_data,
                    data=data,
                )
                response.raise_for_status()

                # Try to parse JSON, fall back to text
                try:
                    return response.json()
                except (ValueError, TypeError):
                    return response.text

            except httpx.HTTPError as e:
                last_error = e
                logger.warning("Request failed (attempt %d): %s", attempt + 1, e)

                # Don't retry on client errors (4xx)
                if (
                    isinstance(e, httpx.HTTPStatusError)
                    and 400 <= e.response.status_code < 500
                ):
                    raise

                # Exponential backoff for retries
                if attempt < MCPConfig.MAX_RETRIES - 1:
                    await asyncio.sleep(backoff)
                    backoff *= 2

        # All retries failed
        logger.error("All %d attempts failed", MCPConfig.MAX_RETRIES)
        raise last_error  # type: ignore[misc]

    async def get(
        self,
        path: str,
        params: dict[str, Any] | None = None,
    ) -> Any:
        """Make a GET request.

        Args:
            path: API endpoint path
            params: Query parameters

        Returns:
            Response data
        """
        return await self.request("GET", path, params=params)

    async def post(
        self,
        path: str,
        json_data: dict[str, Any] | None = None,
        data: dict[str, Any] | None = None,
    ) -> Any:
        """Make a POST request.

        Args:
            path: API endpoint path
            json_data: JSON body data
            data: Form data

        Returns:
            Response data
        """
        return await self.request("POST", path, json_data=json_data, data=data)

    async def health_check(self, project: str | None = None) -> bool:
        """Check if the FastAPI server is reachable.

        .. deprecated::
            This method is deprecated and no longer called during MCP startup.
            Use list_projects() for server discovery instead.

        Args:
            project: Optional project name to check specific server

        Returns:
            True if server is healthy, False otherwise
        """
        try:
            await self.request("GET", "/health", project=project)
        except Exception:
            logger.exception("FastAPI server health check failed")
            return False
        else:
            base_url = self._resolve_base_url(project)
            logger.info("FastAPI server is healthy at %s", base_url)
            return True

    def list_projects(self) -> list[dict[str, Any]]:
        """List all active projects from the registry.

        Returns:
            List of project information dictionaries
        """
        servers = self._registry.list_servers()
        return [
            {
                "project_name": server.project_name,
                "project_path": server.project_path,
                "port": server.port,
                "pid": server.pid,
                "pdk": server.pdk,
                "started_at": server.started_at,
            }
            for server in servers
        ]

    async def get_project_info(self, project: str) -> dict[str, Any]:
        """Get detailed information about a specific project.

        Args:
            project: Project name or path

        Returns:
            Project information from the server's /info endpoint

        Raises:
            ValueError: If project not found
        """
        return await self.request("GET", "/info", project=project)
