"""Originally generated by Speakeasy; now maintained manually."""

from .basesdk import BaseSDK
from .httpclient import AsyncHttpClient, ClientOwner, HttpClient, close_clients
from .sdkconfiguration import (
    DEFAULT_BASE_URL,
    DEFAULT_PROJECT_NAME,
    SDKConfiguration,
)
from .utils.logger import Logger, get_default_logger
from .utils.retries import RetryConfig
import httpx
import importlib
import sys
import warnings
import weakref
from lambdadb import models, utils
from lambdadb._hooks import SDKHooks
from lambdadb.types import OptionalNullable, UNSET
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union, cast

if TYPE_CHECKING:
    from lambdadb.collections import Collections
    from lambdadb.collection import Collection


class LambdaDB(BaseSDK):
    r"""LambdaDB API: LambdaDB Open API Spec"""

    collections: "Collections"
    _sub_sdk_map = {
        "collections": ("lambdadb.collections", "Collections"),
    }

    def __init__(
        self,
        project_api_key: Optional[
            Union[Optional[str], Callable[[], Optional[str]]]
        ] = None,
        base_url: Optional[str] = None,
        project_name: Optional[str] = None,
        project_host: Optional[str] = None,
        server_idx: Optional[int] = None,
        server_url: Optional[str] = None,
        url_params: Optional[Dict[str, str]] = None,
        client: Optional[HttpClient] = None,
        async_client: Optional[AsyncHttpClient] = None,
        retry_config: OptionalNullable[RetryConfig] = UNSET,
        timeout_ms: Optional[int] = None,
        debug_logger: Optional[Logger] = None,
    ) -> None:
        r"""Instantiates the SDK configuring it with the provided parameters.

        Recommended: use base_url and project_name (defaults: https://api.lambdadb.ai, playground).
        server_url and project_host are deprecated and will be removed in the next major version.

        :param project_api_key: The project API key required for authentication.
        :param base_url: API base URL (e.g. "https://api.lambdadb.ai"). Defaults to https://api.lambdadb.ai.
        :param project_name: Project name (e.g. "playground"). Defaults to "playground".
        :param project_host: (Deprecated) Legacy projectHost for URL substitution. Use base_url + project_name.
        :param server_idx: The index of the server to use for all methods (legacy).
        :param server_url: (Deprecated) Full server URL. Use base_url + project_name instead.
        :param url_params: Parameters to optionally template the server URL with (legacy).
        :param client: The HTTP client to use for all synchronous methods.
        :param async_client: The Async HTTP client to use for all asynchronous methods.
        :param retry_config: The retry configuration to use for all supported methods.
        :param timeout_ms: Optional request timeout applied to each operation in milliseconds.
        """
        client_supplied = True
        if client is None:
            client = httpx.Client(follow_redirects=True)
            client_supplied = False

        assert issubclass(
            type(client), HttpClient
        ), "The provided client must implement the HttpClient protocol."

        async_client_supplied = True
        if async_client is None:
            async_client = httpx.AsyncClient(follow_redirects=True)
            async_client_supplied = False

        if debug_logger is None:
            debug_logger = get_default_logger()

        assert issubclass(
            type(async_client), AsyncHttpClient
        ), "The provided async_client must implement the AsyncHttpClient protocol."

        security: Any = None
        if callable(project_api_key):
            # pylint: disable=unnecessary-lambda-assignment
            security = lambda: models.Security(project_api_key=project_api_key())
        else:
            security = models.Security(project_api_key=project_api_key)

        # Resolve base URL: prefer base_url+project_name; fall back to server_url / project_host (deprecated)
        resolved_server_url: Optional[str] = None
        resolved_base_url: Optional[str] = base_url if base_url is not None else DEFAULT_BASE_URL
        resolved_project_name: Optional[str] = project_name if project_name is not None else DEFAULT_PROJECT_NAME
        server_defaults: List[Dict[str, str]] = [
            {"projectHost": project_host or "api.lambdadb.ai/projects/playground"},
        ]

        if server_url is not None:
            warnings.warn(
                "server_url is deprecated and will be removed in the next major version. "
                "Use base_url and project_name instead (e.g. base_url='https://api.lambdadb.ai', project_name='playground').",
                DeprecationWarning,
                stacklevel=2,
            )
            resolved_server_url = utils.template_url(server_url, url_params or {})
            resolved_base_url = None
            resolved_project_name = None
        elif project_host is not None:
            warnings.warn(
                "project_host is deprecated and will be removed in the next major version. "
                "Use base_url and project_name instead (e.g. base_url='https://api.lambdadb.ai', project_name='playground').",
                DeprecationWarning,
                stacklevel=2,
            )
            resolved_server_url = None
            resolved_base_url = None
            resolved_project_name = None
            server_defaults = [{"projectHost": project_host}]

        BaseSDK.__init__(
            self,
            SDKConfiguration(
                client=client,
                client_supplied=client_supplied,
                async_client=async_client,
                async_client_supplied=async_client_supplied,
                security=security,
                server_url=resolved_server_url,
                server_idx=server_idx,
                server_defaults=server_defaults,
                base_url=resolved_base_url,
                project_name=resolved_project_name,
                retry_config=retry_config,
                timeout_ms=timeout_ms,
                debug_logger=debug_logger,
            ),
            parent_ref=self,
        )

        hooks = SDKHooks()

        # pylint: disable=protected-access
        self.sdk_configuration.__dict__["_hooks"] = hooks

        self.sdk_configuration = hooks.sdk_init(self.sdk_configuration)

        weakref.finalize(
            self,
            close_clients,
            cast(ClientOwner, self.sdk_configuration),
            self.sdk_configuration.client,
            self.sdk_configuration.client_supplied,
            self.sdk_configuration.async_client,
            self.sdk_configuration.async_client_supplied,
        )

    def collection(self, name: str) -> "Collection":
        """Return a Collection handle for the given collection name.
        Use this for a better DX: client.collection('my_coll').docs.list(), .query(), etc.
        """
        from lambdadb.collection import Collection  # lazy to avoid circular import
        return Collection(self.sdk_configuration, name, parent_ref=self)

    def dynamic_import(self, modname, retries=3):
        for attempt in range(retries):
            try:
                return importlib.import_module(modname)
            except KeyError:
                # Clear any half-initialized module and retry
                sys.modules.pop(modname, None)
                if attempt == retries - 1:
                    break
        raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")

    def __getattr__(self, name: str):
        if name in self._sub_sdk_map:
            module_path, class_name = self._sub_sdk_map[name]
            try:
                module = self.dynamic_import(module_path)
                klass = getattr(module, class_name)
                instance = klass(self.sdk_configuration, parent_ref=self)
                setattr(self, name, instance)
                return instance
            except ImportError as e:
                raise AttributeError(
                    f"Failed to import module {module_path} for attribute {name}: {e}"
                ) from e
            except AttributeError as e:
                raise AttributeError(
                    f"Failed to find class {class_name} in module {module_path} for attribute {name}: {e}"
                ) from e

        raise AttributeError(
            f"'{type(self).__name__}' object has no attribute '{name}'"
        )

    def __dir__(self):
        default_attrs = list(super().__dir__())
        lazy_attrs = list(self._sub_sdk_map.keys())
        return sorted(list(set(default_attrs + lazy_attrs)))

    def __enter__(self):
        return self

    async def __aenter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if (
            self.sdk_configuration.client is not None
            and not self.sdk_configuration.client_supplied
        ):
            self.sdk_configuration.client.close()
        self.sdk_configuration.client = None

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if (
            self.sdk_configuration.async_client is not None
            and not self.sdk_configuration.async_client_supplied
        ):
            await self.sdk_configuration.async_client.aclose()
        self.sdk_configuration.async_client = None
