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

from __future__ import annotations
from datetime import datetime
from functools import partial
from paygentic_sdk.types import (
    BaseModel,
    Nullable,
    OptionalNullable,
    UNSET,
    UNSET_SENTINEL,
    UnrecognizedStr,
)
from paygentic_sdk.utils import validate_const
from paygentic_sdk.utils.unions import parse_open_union
import pydantic
from pydantic import ConfigDict, model_serializer
from pydantic.functional_validators import AfterValidator, BeforeValidator
from typing import Any, Literal, Optional, Union
from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict


SubscriptionObject = Literal["subscription",]


class PaymentPaidTypedDict(TypedDict):
    r"""Zero-amount Invoice 0 that completed synchronously to PAID"""

    invoice_id: str
    r"""The Invoice 0 id"""
    amount: str
    r"""Payment amount ('0' for zero-amount subscriptions)"""
    status: Literal["paid"]
    r"""Payment status"""


class PaymentPaid(BaseModel):
    r"""Zero-amount Invoice 0 that completed synchronously to PAID"""

    invoice_id: Annotated[str, pydantic.Field(alias="invoiceId")]
    r"""The Invoice 0 id"""

    amount: str
    r"""Payment amount ('0' for zero-amount subscriptions)"""

    status: Annotated[
        Annotated[Literal["paid"], AfterValidator(validate_const("paid"))],
        pydantic.Field(alias="status"),
    ] = "paid"
    r"""Payment status"""


class BreakdownTypedDict(TypedDict):
    r"""Breakdown of payment amount"""

    upfront_charges: str
    r"""One-time flat fee charges in decimal dollar format. Sample values: '50.00' equals $50.00 setup fee, '100.00' equals $100.00 activation fee"""
    wallet_charge: str
    r"""Wallet charge amount in decimal dollar format."""


class Breakdown(BaseModel):
    r"""Breakdown of payment amount"""

    upfront_charges: Annotated[str, pydantic.Field(alias="upfrontCharges")]
    r"""One-time flat fee charges in decimal dollar format. Sample values: '50.00' equals $50.00 setup fee, '100.00' equals $100.00 activation fee"""

    wallet_charge: Annotated[str, pydantic.Field(alias="walletCharge")]
    r"""Wallet charge amount in decimal dollar format."""


class PaymentPendingTypedDict(TypedDict):
    amount: str
    r"""Total payment amount in decimal dollar format. Sample values: '250.00' equals $250.00, '99.99' equals $99.99"""
    breakdown: BreakdownTypedDict
    r"""Breakdown of payment amount"""
    checkout_url: str
    r"""Checkout page URL for customer payment completion."""
    payment_session_id: str
    r"""Payment session identifier for upfront payment processing."""
    invoice_id: NotRequired[str]
    r"""ID of the invoice linked to this payment."""
    status: Literal["pending"]
    r"""Payment status"""


class PaymentPending(BaseModel):
    amount: str
    r"""Total payment amount in decimal dollar format. Sample values: '250.00' equals $250.00, '99.99' equals $99.99"""

    breakdown: Breakdown
    r"""Breakdown of payment amount"""

    checkout_url: Annotated[str, pydantic.Field(alias="checkoutUrl")]
    r"""Checkout page URL for customer payment completion."""

    payment_session_id: Annotated[str, pydantic.Field(alias="paymentSessionId")]
    r"""Payment session identifier for upfront payment processing."""

    invoice_id: Annotated[Optional[str], pydantic.Field(alias="invoiceId")] = None
    r"""ID of the invoice linked to this payment."""

    status: Annotated[
        Annotated[Literal["pending"], AfterValidator(validate_const("pending"))],
        pydantic.Field(alias="status"),
    ] = "pending"
    r"""Payment status"""

    @model_serializer(mode="wrap")
    def serialize_model(self, handler):
        optional_fields = set(["invoiceId"])
        serialized = handler(self)
        m = {}

        for n, f in type(self).model_fields.items():
            k = f.alias or n
            val = serialized.get(k, serialized.get(n))

            if val != UNSET_SENTINEL:
                if val is not None or k not in optional_fields:
                    m[k] = val

        return m


PaymentUnionTypedDict = TypeAliasType(
    "PaymentUnionTypedDict", Union[PaymentPaidTypedDict, PaymentPendingTypedDict]
)
r"""Payment session details when upfront payment is required, or confirmation of a zero-amount paid invoice"""


class UnknownPaymentUnion(BaseModel):
    r"""A PaymentUnion variant the SDK doesn't recognize. Preserves the raw payload."""

    status: Literal["UNKNOWN"] = "UNKNOWN"
    raw: Any
    is_unknown: Literal[True] = True

    model_config = ConfigDict(frozen=True)


_PAYMENT_UNION_VARIANTS: dict[str, Any] = {
    "pending": PaymentPending,
    "paid": PaymentPaid,
}


PaymentUnion = Annotated[
    Union[PaymentPending, PaymentPaid, UnknownPaymentUnion],
    BeforeValidator(
        partial(
            parse_open_union,
            disc_key="status",
            variants=_PAYMENT_UNION_VARIANTS,
            unknown_cls=UnknownPaymentUnion,
            union_name="PaymentUnion",
        )
    ),
]
r"""Payment session details when upfront payment is required, or confirmation of a zero-amount paid invoice"""


SubscriptionStatusEnum = Union[
    Literal[
        "pending_payment",
        "active",
        "terminated",
    ],
    UnrecognizedStr,
]


class SubscriptionTypedDict(TypedDict):
    id: str
    object: SubscriptionObject
    created_at: datetime
    customer_id: str
    name: str
    plan_id: str
    started_at: datetime
    status: SubscriptionStatusEnum
    updated_at: datetime
    auto_charge: NotRequired[bool]
    r"""Whether automatic charging is enabled for this subscription. When true, invoices will be automatically paid using stored payment methods."""
    ending_at: NotRequired[datetime]
    estimated_tax_rate: NotRequired[float]
    r"""Projected tax percentage rate. Sample values: 8.875 indicates 8.875% tax rate, 10.0 indicates 10% tax rate, 0 indicates no tax applied"""
    tax_exempt: NotRequired[bool]
    r"""When true, tax rate is forced to 0%."""
    payment: NotRequired[PaymentUnionTypedDict]
    r"""Payment session details when upfront payment is required, or confirmation of a zero-amount paid invoice"""
    prefund_amount: NotRequired[str]
    r"""@deprecated Use minimumAccountBalance instead. Minimum required wallet balance in atomic units. Sample values: '200000000000' equals $200.00 minimum, '1000000000000' equals $1000.00 minimum"""
    minimum_account_balance: NotRequired[str]
    r"""Minimum wallet balance requirement in nanodollars. Can be '0' to disable. The system calculates the difference between this minimum and the customer's current balance, charging only what's needed to reach the minimum. Note: If the calculated charge amount is below payment processor minimums (typically $1.00), the actual charged amount may be automatically adjusted upward to meet the minimum requirement. Sample values: '200000000000' equals $200.00 minimum, '1000000000000' equals $1000.00 minimum"""
    terminated_at: NotRequired[datetime]
    terminated_by: NotRequired[str]
    r"""ID of who terminated the subscription (customer ID or merchant ID)"""
    termination_reason: NotRequired[str]
    r"""Reason for termination"""
    test_clock_id: NotRequired[str]
    r"""Test clock ID if this subscription is attached to a test clock. Only present in non-production environments."""
    wallet_id: NotRequired[str]
    r"""Optional (virtual) wallet ID for the subscription"""
    renewal_reminder_enabled: NotRequired[Nullable[bool]]
    r"""Whether renewal reminder emails are enabled for this subscription. Null means use plan default."""
    renewal_reminder_days: NotRequired[Nullable[int]]
    r"""Number of days before renewal to send the reminder. Null means use plan default."""


class Subscription(BaseModel):
    id: str

    object: SubscriptionObject

    created_at: Annotated[datetime, pydantic.Field(alias="createdAt")]

    customer_id: Annotated[str, pydantic.Field(alias="customerId")]

    name: str

    plan_id: Annotated[str, pydantic.Field(alias="planId")]

    started_at: Annotated[datetime, pydantic.Field(alias="startedAt")]

    status: SubscriptionStatusEnum

    updated_at: Annotated[datetime, pydantic.Field(alias="updatedAt")]

    auto_charge: Annotated[Optional[bool], pydantic.Field(alias="autoCharge")] = False
    r"""Whether automatic charging is enabled for this subscription. When true, invoices will be automatically paid using stored payment methods."""

    ending_at: Annotated[Optional[datetime], pydantic.Field(alias="endingAt")] = None

    estimated_tax_rate: Annotated[
        Optional[float], pydantic.Field(alias="estimatedTaxRate")
    ] = None
    r"""Projected tax percentage rate. Sample values: 8.875 indicates 8.875% tax rate, 10.0 indicates 10% tax rate, 0 indicates no tax applied"""

    tax_exempt: Annotated[Optional[bool], pydantic.Field(alias="taxExempt")] = False
    r"""When true, tax rate is forced to 0%."""

    payment: Optional[PaymentUnion] = None
    r"""Payment session details when upfront payment is required, or confirmation of a zero-amount paid invoice"""

    prefund_amount: Annotated[Optional[str], pydantic.Field(alias="prefundAmount")] = (
        None
    )
    r"""@deprecated Use minimumAccountBalance instead. Minimum required wallet balance in atomic units. Sample values: '200000000000' equals $200.00 minimum, '1000000000000' equals $1000.00 minimum"""

    minimum_account_balance: Annotated[
        Optional[str], pydantic.Field(alias="minimumAccountBalance")
    ] = None
    r"""Minimum wallet balance requirement in nanodollars. Can be '0' to disable. The system calculates the difference between this minimum and the customer's current balance, charging only what's needed to reach the minimum. Note: If the calculated charge amount is below payment processor minimums (typically $1.00), the actual charged amount may be automatically adjusted upward to meet the minimum requirement. Sample values: '200000000000' equals $200.00 minimum, '1000000000000' equals $1000.00 minimum"""

    terminated_at: Annotated[
        Optional[datetime], pydantic.Field(alias="terminatedAt")
    ] = None

    terminated_by: Annotated[Optional[str], pydantic.Field(alias="terminatedBy")] = None
    r"""ID of who terminated the subscription (customer ID or merchant ID)"""

    termination_reason: Annotated[
        Optional[str], pydantic.Field(alias="terminationReason")
    ] = None
    r"""Reason for termination"""

    test_clock_id: Annotated[Optional[str], pydantic.Field(alias="testClockId")] = None
    r"""Test clock ID if this subscription is attached to a test clock. Only present in non-production environments."""

    wallet_id: Annotated[Optional[str], pydantic.Field(alias="walletId")] = None
    r"""Optional (virtual) wallet ID for the subscription"""

    renewal_reminder_enabled: Annotated[
        OptionalNullable[bool], pydantic.Field(alias="renewalReminderEnabled")
    ] = UNSET
    r"""Whether renewal reminder emails are enabled for this subscription. Null means use plan default."""

    renewal_reminder_days: Annotated[
        OptionalNullable[int], pydantic.Field(alias="renewalReminderDays")
    ] = UNSET
    r"""Number of days before renewal to send the reminder. Null means use plan default."""

    @model_serializer(mode="wrap")
    def serialize_model(self, handler):
        optional_fields = set(
            [
                "autoCharge",
                "endingAt",
                "estimatedTaxRate",
                "taxExempt",
                "payment",
                "prefundAmount",
                "minimumAccountBalance",
                "terminatedAt",
                "terminatedBy",
                "terminationReason",
                "testClockId",
                "walletId",
                "renewalReminderEnabled",
                "renewalReminderDays",
            ]
        )
        nullable_fields = set(["renewalReminderEnabled", "renewalReminderDays"])
        serialized = handler(self)
        m = {}

        for n, f in type(self).model_fields.items():
            k = f.alias or n
            val = serialized.get(k, serialized.get(n))
            is_nullable_and_explicitly_set = (
                k in nullable_fields
                and (self.__pydantic_fields_set__.intersection({n}))  # pylint: disable=no-member
            )

            if val != UNSET_SENTINEL:
                if (
                    val is not None
                    or k not in optional_fields
                    or is_nullable_and_explicitly_set
                ):
                    m[k] = val

        return m


try:
    PaymentPaid.model_rebuild()
except NameError:
    pass
try:
    Breakdown.model_rebuild()
except NameError:
    pass
try:
    PaymentPending.model_rebuild()
except NameError:
    pass
try:
    Subscription.model_rebuild()
except NameError:
    pass
