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

from __future__ import annotations
from .invoicelineitem import InvoiceLineItem, InvoiceLineItemTypedDict
from datetime import datetime
from paygentic_sdk.types import (
    BaseModel,
    Nullable,
    OptionalNullable,
    UNSET,
    UNSET_SENTINEL,
    UnrecognizedStr,
)
import pydantic
from pydantic import model_serializer
from typing import Any, Dict, List, Literal, Optional, Union
from typing_extensions import Annotated, NotRequired, TypedDict


InvoiceObject = Literal["invoice",]
r"""The object type"""


class LineItemsTypedDict(TypedDict):
    r"""Line items (only present if expand=lineItems query parameter is provided)"""

    invoice_id: str
    r"""The invoice ID"""
    line_items: List[InvoiceLineItemTypedDict]
    r"""Array of line items for this page"""
    total_count: int
    r"""Total number of line items across all pages"""
    next_page_token: NotRequired[Nullable[str]]
    r"""Token for fetching the next page, null if no more pages"""


class LineItems(BaseModel):
    r"""Line items (only present if expand=lineItems query parameter is provided)"""

    invoice_id: Annotated[str, pydantic.Field(alias="invoiceId")]
    r"""The invoice ID"""

    line_items: Annotated[List[InvoiceLineItem], pydantic.Field(alias="lineItems")]
    r"""Array of line items for this page"""

    total_count: Annotated[int, pydantic.Field(alias="totalCount")]
    r"""Total number of line items across all pages"""

    next_page_token: Annotated[
        OptionalNullable[str], pydantic.Field(alias="nextPageToken")
    ] = UNSET
    r"""Token for fetching the next page, null if no more pages"""

    @model_serializer(mode="wrap")
    def serialize_model(self, handler):
        optional_fields = set(["nextPageToken"])
        nullable_fields = set(["nextPageToken"])
        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


InvoiceStatus = Union[
    Literal[
        "ACTIVE",
        "CLOSING",
        "CLOSED",
        "CALCULATING",
        "DRAFT",
        "ISSUED",
        "PAYMENT_FAILED",
        "PAID",
        "CANCELLED",
        "WRITTEN_OFF",
        "FAILED",
    ],
    UnrecognizedStr,
]
r"""The current status of the invoice"""


class TaxTypedDict(TypedDict):
    r"""Tax reconciliation metadata (only present when plan has taxEnabled)"""

    actual_tax: NotRequired[str]
    r"""Actual tax calculated (in atomic units)"""
    adjustment_applied: NotRequired[bool]
    r"""Whether wallet adjustment was applied during tax reconciliation"""
    adjustment_needed: NotRequired[bool]
    r"""Whether wallet adjustment is needed (difference >= $0.01)"""
    difference: NotRequired[str]
    r"""Difference between actual and estimated (in atomic units)"""
    estimated_tax: NotRequired[str]
    r"""Tax estimated from subscription rate (in atomic units)"""
    reconciled: NotRequired[bool]
    r"""Whether reconciliation was performed"""
    reconciled_at: NotRequired[datetime]
    r"""When reconciliation occurred"""


class Tax(BaseModel):
    r"""Tax reconciliation metadata (only present when plan has taxEnabled)"""

    actual_tax: Annotated[Optional[str], pydantic.Field(alias="actualTax")] = None
    r"""Actual tax calculated (in atomic units)"""

    adjustment_applied: Annotated[
        Optional[bool], pydantic.Field(alias="adjustmentApplied")
    ] = None
    r"""Whether wallet adjustment was applied during tax reconciliation"""

    adjustment_needed: Annotated[
        Optional[bool], pydantic.Field(alias="adjustmentNeeded")
    ] = None
    r"""Whether wallet adjustment is needed (difference >= $0.01)"""

    difference: Optional[str] = None
    r"""Difference between actual and estimated (in atomic units)"""

    estimated_tax: Annotated[Optional[str], pydantic.Field(alias="estimatedTax")] = None
    r"""Tax estimated from subscription rate (in atomic units)"""

    reconciled: Optional[bool] = None
    r"""Whether reconciliation was performed"""

    reconciled_at: Annotated[
        Optional[datetime], pydantic.Field(alias="reconciledAt")
    ] = None
    r"""When reconciliation occurred"""

    @model_serializer(mode="wrap")
    def serialize_model(self, handler):
        optional_fields = set(
            [
                "actualTax",
                "adjustmentApplied",
                "adjustmentNeeded",
                "difference",
                "estimatedTax",
                "reconciled",
                "reconciledAt",
            ]
        )
        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


class InvoiceTypedDict(TypedDict):
    id: str
    r"""The invoice ID"""
    object: InvoiceObject
    r"""The object type"""
    auto_approve: bool
    r"""Whether this invoice auto-approves after calculation"""
    billing_anchor: datetime
    r"""The billing anchor date used for period calculations"""
    billing_cadence: str
    r"""ISO 8601 duration string for billing frequency"""
    created_at: datetime
    r"""When the invoice was created"""
    currency: str
    r"""ISO 4217 currency code (e.g., USD, EUR)"""
    grace_period_end: datetime
    r"""The end of the grace period for accepting usage events"""
    grand_total: str
    r"""Grand total (subtotal + tax) in decimal dollars (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""
    item_count: int
    r"""Number of billing items (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""
    merchant_id: str
    r"""The merchant organization ID"""
    paid_amount: str
    r"""Amount already paid via wallet (account_type='main') in decimal dollars"""
    period_end: datetime
    r"""The end of the billing period"""
    period_start: datetime
    r"""The start of the billing period"""
    sequence_number: int
    r"""The sequence number of this invoice period"""
    status: InvoiceStatus
    r"""The current status of the invoice"""
    subscription_id: str
    r"""The subscription ID this invoice belongs to"""
    subtotal: str
    r"""Subtotal in decimal dollars (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""
    total_tax: str
    r"""Total tax in decimal dollars (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""
    unpaid_amount: str
    r"""Amount accrued as liability (account_type='usage') in decimal dollars"""
    updated_at: datetime
    r"""When the invoice was last updated"""
    invoice_number: NotRequired[Nullable[str]]
    r"""The invoice number"""
    line_items: NotRequired[Nullable[LineItemsTypedDict]]
    r"""Line items (only present if expand=lineItems query parameter is provided)"""
    metadata: NotRequired[Dict[str, Any]]
    r"""Additional metadata including transition history"""
    next_action_at: NotRequired[Nullable[datetime]]
    r"""When the next scheduled action should occur"""
    paid_at: NotRequired[Nullable[datetime]]
    r"""When the invoice was paid (null if not yet paid)"""
    payment_url: NotRequired[Nullable[str]]
    r"""Payment URL for completing payment (only present when status is ISSUED and unpaidAmount > 0)"""
    pdf_url: NotRequired[Nullable[str]]
    r"""Direct PDF download link for tax invoice"""
    permalink: NotRequired[Nullable[str]]
    r"""Public URL to view tax invoice"""
    tax: NotRequired[Nullable[TaxTypedDict]]
    r"""Tax reconciliation metadata (only present when plan has taxEnabled)"""


class Invoice(BaseModel):
    id: str
    r"""The invoice ID"""

    object: InvoiceObject
    r"""The object type"""

    auto_approve: Annotated[bool, pydantic.Field(alias="autoApprove")]
    r"""Whether this invoice auto-approves after calculation"""

    billing_anchor: Annotated[datetime, pydantic.Field(alias="billingAnchor")]
    r"""The billing anchor date used for period calculations"""

    billing_cadence: Annotated[str, pydantic.Field(alias="billingCadence")]
    r"""ISO 8601 duration string for billing frequency"""

    created_at: Annotated[datetime, pydantic.Field(alias="createdAt")]
    r"""When the invoice was created"""

    currency: str
    r"""ISO 4217 currency code (e.g., USD, EUR)"""

    grace_period_end: Annotated[datetime, pydantic.Field(alias="gracePeriodEnd")]
    r"""The end of the grace period for accepting usage events"""

    grand_total: Annotated[str, pydantic.Field(alias="grandTotal")]
    r"""Grand total (subtotal + tax) in decimal dollars (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""

    item_count: Annotated[int, pydantic.Field(alias="itemCount")]
    r"""Number of billing items (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""

    merchant_id: Annotated[str, pydantic.Field(alias="merchantId")]
    r"""The merchant organization ID"""

    paid_amount: Annotated[str, pydantic.Field(alias="paidAmount")]
    r"""Amount already paid via wallet (account_type='main') in decimal dollars"""

    period_end: Annotated[datetime, pydantic.Field(alias="periodEnd")]
    r"""The end of the billing period"""

    period_start: Annotated[datetime, pydantic.Field(alias="periodStart")]
    r"""The start of the billing period"""

    sequence_number: Annotated[int, pydantic.Field(alias="sequenceNumber")]
    r"""The sequence number of this invoice period"""

    status: InvoiceStatus
    r"""The current status of the invoice"""

    subscription_id: Annotated[str, pydantic.Field(alias="subscriptionId")]
    r"""The subscription ID this invoice belongs to"""

    subtotal: str
    r"""Subtotal in decimal dollars (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""

    total_tax: Annotated[str, pydantic.Field(alias="totalTax")]
    r"""Total tax in decimal dollars (real-time for ACTIVE/CLOSING/CLOSED, cached otherwise)"""

    unpaid_amount: Annotated[str, pydantic.Field(alias="unpaidAmount")]
    r"""Amount accrued as liability (account_type='usage') in decimal dollars"""

    updated_at: Annotated[datetime, pydantic.Field(alias="updatedAt")]
    r"""When the invoice was last updated"""

    invoice_number: Annotated[
        OptionalNullable[str], pydantic.Field(alias="invoiceNumber")
    ] = UNSET
    r"""The invoice number"""

    line_items: Annotated[
        OptionalNullable[LineItems], pydantic.Field(alias="lineItems")
    ] = UNSET
    r"""Line items (only present if expand=lineItems query parameter is provided)"""

    metadata: Optional[Dict[str, Any]] = None
    r"""Additional metadata including transition history"""

    next_action_at: Annotated[
        OptionalNullable[datetime], pydantic.Field(alias="nextActionAt")
    ] = UNSET
    r"""When the next scheduled action should occur"""

    paid_at: Annotated[OptionalNullable[datetime], pydantic.Field(alias="paidAt")] = (
        UNSET
    )
    r"""When the invoice was paid (null if not yet paid)"""

    payment_url: Annotated[
        OptionalNullable[str], pydantic.Field(alias="paymentUrl")
    ] = UNSET
    r"""Payment URL for completing payment (only present when status is ISSUED and unpaidAmount > 0)"""

    pdf_url: Annotated[OptionalNullable[str], pydantic.Field(alias="pdfUrl")] = UNSET
    r"""Direct PDF download link for tax invoice"""

    permalink: OptionalNullable[str] = UNSET
    r"""Public URL to view tax invoice"""

    tax: OptionalNullable[Tax] = UNSET
    r"""Tax reconciliation metadata (only present when plan has taxEnabled)"""

    @model_serializer(mode="wrap")
    def serialize_model(self, handler):
        optional_fields = set(
            [
                "invoiceNumber",
                "lineItems",
                "metadata",
                "nextActionAt",
                "paidAt",
                "paymentUrl",
                "pdfUrl",
                "permalink",
                "tax",
            ]
        )
        nullable_fields = set(
            [
                "invoiceNumber",
                "lineItems",
                "nextActionAt",
                "paidAt",
                "paymentUrl",
                "pdfUrl",
                "permalink",
                "tax",
            ]
        )
        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:
    LineItems.model_rebuild()
except NameError:
    pass
try:
    Tax.model_rebuild()
except NameError:
    pass
try:
    Invoice.model_rebuild()
except NameError:
    pass
