"""Sync and async HTTP clients for the Cerebe SDK."""

from __future__ import annotations

import asyncio
import time
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .test_helpers import AsyncTestHelpers, TestHelpers

import httpx

from ._config import CerebeConfig
from ._errors import raise_for_status
from ._response import APIResponse
from .resources.agent import AgentResource, AsyncAgentResource
from .resources.graph import AsyncGraphResource, GraphResource
from .resources.knowledge import AsyncKnowledgeResource, KnowledgeResource
from .resources.memory import AsyncMemoryResource, MemoryResource
from .resources.meta_learning import AsyncMetaLearningResource, MetaLearningResource
from .resources.session import AsyncSessionResource, SessionResource
from .resources.storage import AsyncStorageResource, StorageResource

_TEST_ENVS = frozenset({"test", "dev"})


def _detect_environment(api_key: str) -> str:
    """Parse environment from key prefix (ck_{env}_{random})."""
    if not api_key:
        return "unknown"
    parts = api_key.split("_", 2)
    if len(parts) >= 2 and parts[0] == "ck":
        env = parts[1]
        if env in ("live", "test", "dev", "staging"):
            return env
    return "live"


class _BaseClient:
    """Shared configuration for sync and async clients."""

    def __init__(
        self,
        api_key: str = "",
        project: str = "",
        base_url: str = "https://api.cerebe.ai",
        timeout: float = 30.0,
        max_retries: int = 3,
        headers: dict[str, str] | None = None,
        environment: str | None = None,
    ) -> None:
        self.config = CerebeConfig(
            api_key=api_key,
            project=project,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            headers=headers or {},
        )
        self.config.validate()

        # Environment detection
        detected = _detect_environment(self.config.api_key)
        if environment and environment != detected:
            from ._errors import CerebeError

            raise CerebeError(
                f"Environment mismatch: key prefix indicates '{detected}' "
                f"but explicit environment is '{environment}'. "
                f"Use a ck_{environment}_ key or remove the environment parameter."
            )
        self._environment = detected

        if self._environment in _TEST_ENVS:
            import warnings

            warnings.warn(
                f"Cerebe: Using {self._environment} mode. Data is isolated from production.",
                stacklevel=3,
            )

    @property
    def environment(self) -> str:
        """The detected environment (live, test, dev, staging)."""
        return self._environment

    @property
    def is_test_mode(self) -> bool:
        """Whether this client is operating in test or dev mode."""
        return self._environment in _TEST_ENVS

    def _build_headers(self) -> dict[str, str]:
        """Build default request headers."""
        headers = {
            "X-API-Key": self.config.api_key,
            "Content-Type": "application/json",
            "User-Agent": "cerebe-python/0.2.0",
        }
        if self.config.project:
            headers["X-Cerebe-Project"] = self.config.project
        headers.update(self.config.headers)
        return headers

    def _build_url(self, path: str) -> str:
        """Build full URL from path."""
        if path.startswith("http"):
            return path
        return f"{self.config.base_url}{path}"


class Cerebe(_BaseClient):
    """Synchronous Cerebe client.

    Usage:
        client = Cerebe(api_key="ck_live_xxx", project="proj_xxx")
        results = client.memory.search(query="preferences", session_id="sess_123")
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self._http = httpx.Client(
            headers=self._build_headers(),
            timeout=self.config.timeout,
        )

        # Resource namespaces
        self.memory = MemoryResource(self)
        self.knowledge = KnowledgeResource(self)
        self.storage = StorageResource(self)
        self.meta_learning = MetaLearningResource(self)
        self.agents = AgentResource(self)
        self.sessions = SessionResource(self)
        self.graph = GraphResource(self)

        # Test helpers (only available in test/dev mode)
        self._test: TestHelpers | None = None

    @property
    def test(self) -> TestHelpers:
        """Test data lifecycle helpers. Only available with test/dev keys."""
        if not self.is_test_mode:
            from ._errors import CerebeError

            raise CerebeError("Test helpers are only available with test or dev API keys.")
        if self._test is None:
            from .test_helpers import TestHelpers

            self._test = TestHelpers(self)
        return self._test

    def request(
        self,
        method: str,
        path: str,
        *,
        json: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
    ) -> APIResponse[Any]:
        """Make an HTTP request with retry logic."""
        url = self._build_url(path)
        last_error: Exception | None = None

        for attempt in range(self.config.max_retries + 1):
            try:
                response = self._http.request(method, url, json=json, params=params)
                body = response.json() if response.content else {}

                if response.status_code == 429:
                    retry_after = float(response.headers.get("Retry-After", self.config.retry_delay * (2**attempt)))
                    if attempt < self.config.max_retries:
                        time.sleep(retry_after)
                        continue
                    raise_for_status(response.status_code, body)

                if response.status_code >= 500 and attempt < self.config.max_retries:
                    time.sleep(self.config.retry_delay * (2**attempt))
                    continue

                raise_for_status(response.status_code, body)
                return APIResponse.from_response(body)

            except (httpx.ConnectError, httpx.TimeoutException) as e:
                last_error = e
                if attempt < self.config.max_retries:
                    time.sleep(self.config.retry_delay * (2**attempt))
                    continue
                raise

        if last_error:
            raise last_error
        raise RuntimeError("Unexpected retry loop exit")

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

    def __enter__(self) -> Cerebe:
        return self

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


class AsyncCerebe(_BaseClient):
    """Asynchronous Cerebe client.

    Usage:
        async with AsyncCerebe(api_key="ck_live_xxx") as client:
            results = await client.memory.search(query="preferences", session_id="sess_123")
    """

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self._http = httpx.AsyncClient(
            headers=self._build_headers(),
            timeout=self.config.timeout,
        )

        # Resource namespaces
        self.memory = AsyncMemoryResource(self)
        self.knowledge = AsyncKnowledgeResource(self)
        self.storage = AsyncStorageResource(self)
        self.meta_learning = AsyncMetaLearningResource(self)
        self.agents = AsyncAgentResource(self)
        self.sessions = AsyncSessionResource(self)
        self.graph = AsyncGraphResource(self)

        # Test helpers (only available in test/dev mode)
        self._test: AsyncTestHelpers | None = None

    @property
    def test(self) -> AsyncTestHelpers:
        """Test data lifecycle helpers. Only available with test/dev keys."""
        if not self.is_test_mode:
            from ._errors import CerebeError

            raise CerebeError("Test helpers are only available with test or dev API keys.")
        if self._test is None:
            from .test_helpers import AsyncTestHelpers

            self._test = AsyncTestHelpers(self)
        return self._test

    async def request(
        self,
        method: str,
        path: str,
        *,
        json: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
        timeout: float | None = None,
        headers: dict[str, str] | None = None,
    ) -> APIResponse[Any]:
        """Make an async HTTP request with retry logic."""
        url = self._build_url(path)
        last_error: Exception | None = None
        request_timeout = httpx.Timeout(timeout) if timeout else None

        for attempt in range(self.config.max_retries + 1):
            try:
                response = await self._http.request(
                    method,
                    url,
                    json=json,
                    params=params,
                    timeout=request_timeout,
                    headers=headers,
                )
                body = response.json() if response.content else {}

                if response.status_code == 429:
                    retry_after = float(response.headers.get("Retry-After", self.config.retry_delay * (2**attempt)))
                    if attempt < self.config.max_retries:
                        await asyncio.sleep(retry_after)
                        continue
                    raise_for_status(response.status_code, body)

                if response.status_code >= 500 and attempt < self.config.max_retries:
                    await asyncio.sleep(self.config.retry_delay * (2**attempt))
                    continue

                raise_for_status(response.status_code, body)
                return APIResponse.from_response(body)

            except (httpx.ConnectError, httpx.TimeoutException) as e:
                last_error = e
                if attempt < self.config.max_retries:
                    await asyncio.sleep(self.config.retry_delay * (2**attempt))
                    continue
                raise

        if last_error:
            raise last_error
        raise RuntimeError("Unexpected retry loop exit")

    async def close(self) -> None:
        """Close the HTTP client."""
        await self._http.aclose()

    async def __aenter__(self) -> AsyncCerebe:
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()
