"""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 paygentic_sdk import models, utils
from paygentic_sdk._hooks import SDKHooks
from paygentic_sdk.types import OptionalNullable, UNSET
import sys
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Union, cast
import weakref

if TYPE_CHECKING:
    from paygentic_sdk.billablemetrics import BillableMetrics
    from paygentic_sdk.customers import Customers
    from paygentic_sdk.disputes import Disputes
    from paygentic_sdk.entitlements import Entitlements
    from paygentic_sdk.entitlementsv0 import EntitlementsV0
    from paygentic_sdk.events import Events
    from paygentic_sdk.features import Features
    from paygentic_sdk.fees import Fees
    from paygentic_sdk.invoices_v2 import InvoicesV2
    from paygentic_sdk.payments import Payments
    from paygentic_sdk.plans import Plans
    from paygentic_sdk.prices import Prices
    from paygentic_sdk.products import Products
    from paygentic_sdk.revenue import Revenue
    from paygentic_sdk.sources import Sources
    from paygentic_sdk.subscriptions import Subscriptions
    from paygentic_sdk.test_clocks import TestClocks
    from paygentic_sdk.usageevents import UsageEvents
    from paygentic_sdk.users import Users


class Paygentic(BaseSDK):
    r"""Paygentic API: The Paygentic API provides billing infrastructure for usage-based and subscription monetization — customers, subscriptions, usage metering, invoicing, entitlements, and payments.

    See the [Quickstart](https://docs.paygentic.io/getting-started/quickstart) to go from zero to billing in four steps.
    """

    billable_metrics: "BillableMetrics"
    customers: "Customers"
    r"""A `Customer` is an entity connected to a `Merchant` via a `Subscription`. This represents the merchant-facing perspective of `Consumers` who purchase their `Products`."""
    disputes: "Disputes"
    r"""A `Dispute` enables customers to contest usage events that they consider to be inaccurately recorded or billed."""
    entitlements_v0: "EntitlementsV0"
    entitlements: "Entitlements"
    r"""An `Entitlement` grants a customer the right to access and use a specific product feature."""
    features: "Features"
    r"""A `Feature` represents a specific capability or functionality provided by a `Product`. Features can be metered (usage-based), static (fixed allocation), or boolean (enabled/disabled)."""
    fees: "Fees"
    r"""A `Fee` defines a recurring or one-time charge tied to a `Product`. Fees are linked to prices, and cadence is defined on the Price."""
    plans: "Plans"
    r"""A `Plan` links a collection of `Prices` to a `Product`. It functions as a pricing structure document for a particular feature set or service offering."""
    prices: "Prices"
    r"""A `Price` determines the monetary value for a single unit of a `Billable Metric`. Prices are exclusively grouped within a `Plan`."""
    products: "Products"
    r"""A `Product` is an offering sold by a `Merchant`. It includes product metadata like title, summary, and pricing details. `Plans`, `Prices`, and `Subscriptions` are all associated with products."""
    sources: "Sources"
    r"""A `Source` is an external data provider capable of automatically creating usage events. Configuration occurs at the plan level, enabling data retrieval from third-party platforms such as Stripe to produce billable events."""
    subscriptions: "Subscriptions"
    r"""A `Subscription` is a customer's commitment to purchase a `Product` following the terms of a `Plan` and its linked `Prices`."""
    usage_events: "UsageEvents"
    users: "Users"
    r"""A `User` is an entity granted access to an Organization's resources. All operations are performed by users."""
    invoices_v2: "InvoicesV2"
    r"""Invoice V2 operations supporting billing cycles organized by time periods. Warning: v0 invoice endpoints are no longer supported."""
    payments: "Payments"
    r"""Create and manage one-off payments. A payment represents a single charge that a merchant wants to collect from a customer."""
    events: "Events"
    r"""Ingest raw metering events that are processed by the meters service."""
    revenue: "Revenue"
    r"""Revenue data from invoices and payments"""
    test_clocks: "TestClocks"
    r"""Test clocks provide programmable time control to simulate subscription and billing scenarios during testing."""
    _sub_sdk_map = {
        "billable_metrics": ("paygentic_sdk.billablemetrics", "BillableMetrics"),
        "customers": ("paygentic_sdk.customers", "Customers"),
        "disputes": ("paygentic_sdk.disputes", "Disputes"),
        "entitlements_v0": ("paygentic_sdk.entitlementsv0", "EntitlementsV0"),
        "entitlements": ("paygentic_sdk.entitlements", "Entitlements"),
        "features": ("paygentic_sdk.features", "Features"),
        "fees": ("paygentic_sdk.fees", "Fees"),
        "plans": ("paygentic_sdk.plans", "Plans"),
        "prices": ("paygentic_sdk.prices", "Prices"),
        "products": ("paygentic_sdk.products", "Products"),
        "sources": ("paygentic_sdk.sources", "Sources"),
        "subscriptions": ("paygentic_sdk.subscriptions", "Subscriptions"),
        "usage_events": ("paygentic_sdk.usageevents", "UsageEvents"),
        "users": ("paygentic_sdk.users", "Users"),
        "invoices_v2": ("paygentic_sdk.invoices_v2", "InvoicesV2"),
        "payments": ("paygentic_sdk.payments", "Payments"),
        "events": ("paygentic_sdk.events", "Events"),
        "revenue": ("paygentic_sdk.revenue", "Revenue"),
        "test_clocks": ("paygentic_sdk.test_clocks", "TestClocks"),
    }

    def __init__(
        self,
        bearer_auth: 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 bearer_auth: The bearer_auth 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(bearer_auth):
            # pylint: disable=unnecessary-lambda-assignment
            security = lambda: models.Security(bearer_auth=bearer_auth())
        else:
            security = models.Security(bearer_auth=bearer_auth)

        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

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