"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""

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

if TYPE_CHECKING:
    from outpost_sdk.attempts import Attempts
    from outpost_sdk.destinations import Destinations
    from outpost_sdk.events import Events
    from outpost_sdk.health import Health
    from outpost_sdk.metrics import Metrics
    from outpost_sdk.publish import Publish
    from outpost_sdk.schemas import Schemas
    from outpost_sdk.tenants import Tenants
    from outpost_sdk.topics_sdk import TopicsSDK


class Outpost(BaseSDK):
    r"""Outpost API: The Outpost API is a REST-based JSON API for managing tenants, destinations, and publishing events."""

    health: "Health"
    r"""API Health Check"""
    tenants: "Tenants"
    r"""The API segments resources per `tenant`. A tenant represents a user/team/organization in your product. The provided value determines the tenant's ID, which can be any string representation.

    If your system is not multi-tenant, create a single tenant with a hard-code tenant ID upon initialization. If your system has a single tenant but multiple environments, create a tenant per environment, like `live` and `test`.

    """
    events: "Events"
    r"""Operations related to event history."""
    attempts: "Attempts"
    r"""Attempts represent individual delivery attempts of events to destinations. The attempts API provides an attempt-centric view of event processing.

    Each attempt contains:
    - `id`: Unique attempt identifier
    - `status`: success or failed
    - `time`: Timestamp of the attempt
    - `code`: HTTP status code or error code
    - `attempt`: Attempt number (1 for first attempt, 2+ for retries)
    - `event`: Associated event (ID or included object)
    - `destination`: Destination ID

    Use the `include` query parameter to include related data:
    - `include=event`: Include event summary (id, topic, time, eligible_for_retry, metadata)
    - `include=event.data`: Include full event with payload data
    - `include=response_data`: Include response body and headers from the attempt
    - `include=destination`: Include the full destination object with target information

    """
    destinations: "Destinations"
    r"""Destinations are the endpoints where events are sent. Each destination is associated with a tenant and can be configured to receive specific event topics.

    ```json
    {
    \"id\": \"des_12345\", // Control plane generated ID or user provided ID
    \"type\": \"webhooks\", // Type of the destination
    \"topics\": [\"user.created\", \"user.updated\"], // Topics of events this destination is eligible for
    \"config\": {
    // Destination type specific configuration. Schema of depends on type
    \"url\": \"https://example.com/webhooks/user\" 
    },
    \"credentials\": {
    // Destination type specific credentials. AES encrypted. Schema depends on type
    \"secret\": \"some***********\" 
    },
    \"disabled_at\": null, // null or ISO date if disabled
    \"created_at\": \"2024-01-01T00:00:00Z\" // Date the destination was created
    }
    ```

    The `topics` array can contain either a list of topics or a wildcard `*` implying that all topics are supported. If you do not wish to implement topics for your application, you set all destination topics to `*`.

    By default all destination `credentials` are obfuscated and the values cannot be read. This does not apply to the `webhook` type destination secret and each destination can expose their own obfuscation logic.

    """
    publish: "Publish"
    r"""Operations for publishing events."""
    schemas: "Schemas"
    r"""Operations for retrieving destination type schemas."""
    topics: "TopicsSDK"
    r"""Operations for retrieving available event topics."""
    metrics: "Metrics"
    r"""Aggregated metrics for events and delivery attempts. Supports time bucketing, dimensional grouping, and filtering.

    """
    _sub_sdk_map = {
        "health": ("outpost_sdk.health", "Health"),
        "tenants": ("outpost_sdk.tenants", "Tenants"),
        "events": ("outpost_sdk.events", "Events"),
        "attempts": ("outpost_sdk.attempts", "Attempts"),
        "destinations": ("outpost_sdk.destinations", "Destinations"),
        "publish": ("outpost_sdk.publish", "Publish"),
        "schemas": ("outpost_sdk.schemas", "Schemas"),
        "topics": ("outpost_sdk.topics_sdk", "TopicsSDK"),
        "metrics": ("outpost_sdk.metrics", "Metrics"),
    }

    def __init__(
        self,
        api_key: Optional[Union[Optional[str], Callable[[], Optional[str]]]] = None,
        server_idx: Optional[int] = None,
        url_params: Optional[Dict[str, str]] = None,
        server_url: Optional[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.

        :param api_key: The api_key required for authentication
        :param server_idx: The index of the server to use for all methods
        :param server_url: The server URL to use for all methods
        :param url_params: Parameters to optionally template the server URL with
        :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(api_key):
            # pylint: disable=unnecessary-lambda-assignment
            security = lambda: models.Security(api_key=api_key())
        else:
            security = models.Security(api_key=api_key)

        if server_url is not None:
            if url_params is not None:
                server_url = utils.template_url(server_url, url_params)

        BaseSDK.__init__(
            self,
            SDKConfiguration(
                client=client,
                client_supplied=client_supplied,
                async_client=async_client,
                async_client_supplied=async_client_supplied,
                security=security,
                server_url=server_url,
                server_idx=server_idx,
                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

        current_server_url, *_ = self.sdk_configuration.get_server_details()
        server_url, self.sdk_configuration.client = hooks.sdk_init(
            current_server_url, client
        )
        if current_server_url != server_url:
            self.sdk_configuration.server_url = server_url

        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 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
