import contextlib
import uuid
from collections.abc import AsyncGenerator, Sequence
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from typing import Any, Literal, cast, overload
from urllib.parse import urlencode

import structlog
from sqlalchemy import func, select
from sqlalchemy.orm import contains_eager, selectinload

from polar.auth.models import AuthSubject
from polar.billing_entry.repository import BillingEntryRepository
from polar.billing_entry.service import MeteredLineItem
from polar.billing_entry.service import billing_entry as billing_entry_service
from polar.checkout.eventstream import CheckoutEvent, publish_checkout_event
from polar.checkout.guard import has_product_checkout
from polar.config import settings
from polar.customer.repository import CustomerRepository
from polar.customer_meter.service import customer_meter as customer_meter_service
from polar.customer_seat.service import seat_service
from polar.customer_session.service import customer_session as customer_session_service
from polar.discount.repository import DiscountRedemptionRepository
from polar.discount.service import discount as discount_service
from polar.email.react import render_email_template
from polar.email.schemas import EmailAdapter
from polar.email.sender import enqueue_email
from polar.enums import SubscriptionProrationBehavior, SubscriptionRecurringInterval
from polar.event.service import event as event_service
from polar.event.system import (
    SubscriptionCanceledMetadata,
    SubscriptionCreatedMetadata,
    SubscriptionCycledMetadata,
    SubscriptionRevokedMetadata,
    SubscriptionUncanceledMetadata,
    SystemEvent,
    build_system_event,
)
from polar.exceptions import (
    BadRequest,
    PolarError,
    PolarRequestValidationError,
    ResourceUnavailable,
    ValidationError,
)
from polar.kit.db.postgres import AsyncReadSession, AsyncSession
from polar.kit.metadata import MetadataQuery, apply_metadata_clause
from polar.kit.pagination import PaginationParams
from polar.kit.sorting import Sorting
from polar.kit.tax import calculate_tax
from polar.kit.utils import utc_now
from polar.locker import Locker
from polar.logging import Logger
from polar.models import (
    Benefit,
    BenefitGrant,
    BillingEntry,
    Checkout,
    Customer,
    Discount,
    Event,
    Organization,
    PaymentMethod,
    Product,
    ProductBenefit,
    Subscription,
    SubscriptionMeter,
    SubscriptionProductPrice,
    User,
)
from polar.models.billing_entry import BillingEntryDirection, BillingEntryType
from polar.models.order import OrderBillingReasonInternal
from polar.models.product_price import ProductPriceSeatUnit
from polar.models.subscription import CustomerCancellationReason, SubscriptionStatus
from polar.models.webhook_endpoint import WebhookEventType
from polar.notifications.notification import (
    MaintainerNewPaidSubscriptionNotificationPayload,
    NotificationType,
)
from polar.notifications.service import PartialNotification
from polar.notifications.service import notifications as notifications_service
from polar.organization.repository import OrganizationRepository
from polar.product.guard import (
    is_custom_price,
    is_fixed_price,
    is_free_price,
    is_static_price,
)
from polar.product.repository import ProductRepository
from polar.product.service import product as product_service
from polar.webhook.service import webhook as webhook_service
from polar.worker import enqueue_job, make_bulk_job_delay_calculator

from .repository import SubscriptionRepository
from .schemas import (
    SubscriptionCancel,
    SubscriptionChargePreview,
    SubscriptionCreate,
    SubscriptionCreateCustomer,
    SubscriptionRevoke,
    SubscriptionUpdate,
    SubscriptionUpdateBillingPeriod,
    SubscriptionUpdateDiscount,
    SubscriptionUpdateProduct,
    SubscriptionUpdateSeats,
    SubscriptionUpdateTrial,
)
from .sorting import SubscriptionSortProperty

log: Logger = structlog.get_logger()


class SubscriptionError(PolarError): ...


class NotARecurringProduct(SubscriptionError):
    def __init__(self, checkout: Checkout, product: Product) -> None:
        self.checkout = checkout
        self.product = product
        message = (
            f"Checkout {checkout.id} is for product {product.id}, "
            "which is not a recurring product."
        )
        super().__init__(message)


class MissingCheckoutCustomer(SubscriptionError):
    def __init__(self, checkout: Checkout) -> None:
        self.checkout = checkout
        message = f"Checkout {checkout.id} is missing a customer."
        super().__init__(message)


class InactiveSubscription(SubscriptionError):
    def __init__(self, subscription: Subscription) -> None:
        self.subscription = subscription
        message = f"Subscription {subscription.id} is not active."
        super().__init__(message, 403)


class AlreadyCanceledSubscription(SubscriptionError):
    def __init__(self, subscription: Subscription) -> None:
        self.subscription = subscription
        message = (
            "This subscription is already canceled or will be at the end of the period."
        )
        super().__init__(message, 403)


class TrialingSubscription(SubscriptionError):
    def __init__(self, subscription: Subscription) -> None:
        self.subscription = subscription
        message = (
            "This subscription is currently in a trial period and cannot be updated."
        )
        super().__init__(message, 403)


class SubscriptionLocked(SubscriptionError):
    def __init__(self, subscription: Subscription) -> None:
        self.subscription = subscription
        message = "This subscription is pending an update."
        super().__init__(message, 409)


class NotASeatBasedSubscription(SubscriptionError):
    def __init__(self, subscription: Subscription) -> None:
        self.subscription = subscription
        message = "This subscription does not support seat-based pricing."
        super().__init__(message, 400)


class SeatsAlreadyAssigned(SubscriptionError):
    def __init__(
        self, subscription: Subscription, assigned_count: int, requested_seats: int
    ) -> None:
        self.subscription = subscription
        self.assigned_count = assigned_count
        self.requested_seats = requested_seats
        message = (
            f"Cannot decrease seats to {requested_seats}. "
            f"Currently {assigned_count} seats are assigned. "
            f"Revoke seats first."
        )
        super().__init__(message, 400)


class BelowMinimumSeats(SubscriptionError):
    def __init__(
        self, subscription: Subscription, minimum_seats: int, requested_seats: int
    ) -> None:
        self.subscription = subscription
        self.minimum_seats = minimum_seats
        self.requested_seats = requested_seats
        message = f"Minimum seat count is {minimum_seats} based on pricing tiers."
        super().__init__(message, 400)


@overload
def _from_timestamp(t: int) -> datetime: ...


@overload
def _from_timestamp(t: None) -> None: ...


def _from_timestamp(t: int | None) -> datetime | None:
    if t is None:
        return None
    return datetime.fromtimestamp(t, UTC)


class SubscriptionService:
    def _get_seat_based_price(
        self, subscription: Subscription
    ) -> ProductPriceSeatUnit | None:
        """Get the seat-based price from subscription, if any."""
        for spp in subscription.subscription_product_prices:
            if isinstance(spp.product_price, ProductPriceSeatUnit):
                return spp.product_price
        return None

    def _get_minimum_seats_from_tiers(self, seat_price: ProductPriceSeatUnit) -> int:
        """Get the absolute minimum seats from the first tier."""
        if seat_price.seat_tiers is None:
            return 1
        tiers = seat_price.seat_tiers["tiers"]
        if not tiers:
            return 1
        sorted_tiers = sorted(tiers, key=lambda t: t["min_seats"])
        return sorted_tiers[0]["min_seats"]

    @staticmethod
    def _calculate_time_proration(
        period_start: datetime, period_end: datetime, now: datetime
    ) -> Decimal | None:
        """
        Calculate proration factor for a time period.

        Returns:
            Decimal between 0 and 1 representing percentage of time remaining,
            or None if no time is remaining.
        """
        period_total = (period_end - period_start).total_seconds()
        time_remaining = (period_end - now).total_seconds()

        if time_remaining <= 0:
            return None

        return Decimal(time_remaining) / Decimal(period_total)

    def _calculate_proration_factor(
        self, subscription: Subscription, *, now: datetime | None = None
    ) -> Decimal | None:
        """
        Calculate proration factor for subscription's current billing period.

        Returns:
            Decimal between 0 and 1 representing percentage of time remaining,
            or None if period has ended or no period_end exists.
        """
        if now is None:
            now = datetime.now(UTC)

        period_end = subscription.current_period_end
        if period_end is None:
            return None

        period_start = subscription.current_period_start
        return self._calculate_time_proration(period_start, period_end, now)

    async def list(
        self,
        session: AsyncReadSession,
        auth_subject: AuthSubject[User | Organization],
        *,
        organization_id: Sequence[uuid.UUID] | None = None,
        product_id: Sequence[uuid.UUID] | None = None,
        customer_id: Sequence[uuid.UUID] | None = None,
        external_customer_id: Sequence[str] | None = None,
        discount_id: Sequence[uuid.UUID] | None = None,
        active: bool | None = None,
        cancel_at_period_end: bool | None = None,
        metadata: MetadataQuery | None = None,
        pagination: PaginationParams,
        sorting: list[Sorting[SubscriptionSortProperty]] = [
            (SubscriptionSortProperty.started_at, True)
        ],
    ) -> tuple[Sequence[Subscription], int]:
        repository = SubscriptionRepository.from_session(session)
        statement = (
            repository.get_readable_statement(auth_subject)
            .where(Subscription.started_at.is_not(None))
            .join(Subscription.customer)
            .join(Subscription.discount, isouter=True)
        )

        if organization_id is not None:
            statement = statement.where(Product.organization_id.in_(organization_id))

        if product_id is not None:
            statement = statement.where(Product.id.in_(product_id))

        if customer_id is not None:
            statement = statement.where(Subscription.customer_id.in_(customer_id))

        if external_customer_id is not None:
            statement = statement.where(Customer.external_id.in_(external_customer_id))

        if discount_id is not None:
            statement = statement.where(Subscription.discount_id.in_(discount_id))

        if active is not None:
            if active:
                statement = statement.where(Subscription.active.is_(True))
            else:
                statement = statement.where(Subscription.revoked.is_(True))

        if cancel_at_period_end is not None:
            statement = statement.where(
                Subscription.cancel_at_period_end.is_(cancel_at_period_end)
            )

        if metadata is not None:
            statement = apply_metadata_clause(Subscription, statement, metadata)

        statement = repository.apply_sorting(statement, sorting)

        statement = statement.options(
            contains_eager(Subscription.product).options(
                selectinload(Product.product_medias),
                selectinload(Product.attached_custom_fields),
            ),
            contains_eager(Subscription.discount),
            contains_eager(Subscription.customer),
            selectinload(Subscription.meters).joinedload(SubscriptionMeter.meter),
        )

        return await repository.paginate(
            statement, limit=pagination.limit, page=pagination.page
        )

    async def get(
        self,
        session: AsyncReadSession,
        auth_subject: AuthSubject[User | Organization],
        id: uuid.UUID,
    ) -> Subscription | None:
        repository = SubscriptionRepository.from_session(session)
        statement = (
            repository.get_readable_statement(auth_subject)
            .where(
                Subscription.id == id,
                Subscription.started_at.is_not(None),
            )
            .options(
                *repository.get_eager_options(
                    product_load=contains_eager(Subscription.product)
                )
            )
        )
        return await repository.get_one_or_none(statement)

    async def create(
        self,
        session: AsyncSession,
        subscription_create: SubscriptionCreate,
        auth_subject: AuthSubject[User | Organization],
    ) -> Subscription:
        errors: list[ValidationError] = []

        product = await product_service.get(
            session, auth_subject, subscription_create.product_id
        )
        if product is None:
            errors.append(
                {
                    "type": "value_error",
                    "loc": ("body", "product_id"),
                    "msg": "Product does not exist.",
                    "input": subscription_create.product_id,
                }
            )
        elif not product.is_recurring:
            errors.append(
                {
                    "type": "value_error",
                    "loc": ("body", "product_id"),
                    "msg": "Product is not a recurring product.",
                    "input": subscription_create.product_id,
                }
            )
        elif product.is_legacy_recurring_price:
            errors.append(
                {
                    "type": "value_error",
                    "loc": ("body", "product_id"),
                    "msg": "Legacy recurring products are not supported.",
                    "input": subscription_create.product_id,
                }
            )
        elif (static_price := product.get_static_price()) and not is_free_price(
            static_price
        ):
            errors.append(
                {
                    "type": "value_error",
                    "loc": ("body", "product_id"),
                    "msg": (
                        "Product is not free. "
                        "The customer should go through a checkout to create a paid subscription."
                    ),
                    "input": subscription_create.product_id,
                }
            )

        customer: Customer | None = None
        customer_repository = CustomerRepository.from_session(session)
        error_loc: str
        input_value: uuid.UUID | str
        if isinstance(subscription_create, SubscriptionCreateCustomer):
            error_loc = "customer_id"
            input_value = subscription_create.customer_id
            customer = await customer_repository.get_readable_by_id(
                auth_subject, input_value
            )
        else:
            error_loc = "external_customer_id"
            input_value = subscription_create.external_customer_id
            customer = await customer_repository.get_readable_by_external_id(
                auth_subject, input_value
            )

        if customer is None:
            errors.append(
                {
                    "type": "value_error",
                    "loc": ("body", error_loc),
                    "msg": "Customer does not exist.",
                    "input": input_value,
                }
            )

        if len(errors) > 0:
            raise PolarRequestValidationError(errors)

        assert product is not None
        assert customer is not None

        prices = product.prices
        assert product.recurring_interval is not None
        assert product.recurring_interval_count is not None
        recurring_interval = product.recurring_interval
        recurring_interval_count = product.recurring_interval_count

        subscription_product_prices: list[SubscriptionProductPrice] = []
        for price in prices:
            subscription_product_prices.append(
                SubscriptionProductPrice.from_price(price)
            )

        status = SubscriptionStatus.active
        current_period_start = utc_now()
        current_period_end = recurring_interval.get_next_period(
            current_period_start, recurring_interval_count
        )

        subscription = Subscription(
            status=SubscriptionStatus.active,
            started_at=current_period_start,
            current_period_start=current_period_start,
            current_period_end=current_period_end,
            cancel_at_period_end=False,
            recurring_interval=recurring_interval,
            recurring_interval_count=recurring_interval_count,
            product=product,
            customer=customer,
            subscription_product_prices=subscription_product_prices,
            user_metadata=subscription_create.metadata,
        )

        repository = SubscriptionRepository.from_session(session)
        subscription = await repository.create(subscription, flush=True)

        await self._after_subscription_created(session, subscription)
        # ⚠️ Some users are relying on `subscription.updated` for everything
        # It was working before with Stripe since it always triggered an update
        # after creation.
        # But that's not the case with our new engine.
        # So we manually trigger it here to keep the same behavior.
        await self._on_subscription_updated(session, subscription)

        # Reset the subscription meters to start fresh
        await self.reset_meters(session, subscription)

        # Enqueue the benefits grants for the subscription
        await self.enqueue_benefits_grants(session, subscription)

        return subscription

    async def create_or_update_from_checkout(
        self,
        session: AsyncSession,
        checkout: Checkout,
        payment_method: PaymentMethod | None = None,
    ) -> tuple[Subscription, bool]:
        assert has_product_checkout(checkout)

        product = checkout.product
        if not product.is_recurring:
            raise NotARecurringProduct(checkout, product)

        customer = checkout.customer
        if customer is None:
            raise MissingCheckoutCustomer(checkout)

        prices = checkout.prices[product.id]
        recurring_interval: SubscriptionRecurringInterval
        recurring_interval_count: int
        if product.is_legacy_recurring_price:
            prices = [checkout.product_price]
            recurring_interval = prices[0].recurring_interval
            recurring_interval_count = 1
        else:
            assert product.recurring_interval is not None
            assert product.recurring_interval_count is not None
            recurring_interval = product.recurring_interval
            recurring_interval_count = product.recurring_interval_count

        subscription_product_prices: list[SubscriptionProductPrice] = []
        for price in prices:
            subscription_product_prices.append(
                SubscriptionProductPrice.from_price(
                    price, checkout.amount, checkout.seats
                )
            )

        subscription = checkout.subscription
        created = False
        previous_is_canceled = subscription.canceled if subscription else False
        previous_status = subscription.status if subscription else None

        status = SubscriptionStatus.active
        current_period_start = utc_now()
        trial_start: datetime | None = None
        trial_end = checkout.trial_end
        if trial_end is not None:
            status = SubscriptionStatus.trialing
            trial_start = current_period_start
            current_period_end = trial_end
        else:
            current_period_end = recurring_interval.get_next_period(
                current_period_start, recurring_interval_count
            )

        # New subscription
        if subscription is None:
            subscription = Subscription(
                started_at=current_period_start,
                cancel_at_period_end=False,
                customer=customer,
            )
            created = True

        # Even when updating from a free subscription, we change the current period:
        # we start a billing cycle from the checkout date.
        subscription.current_period_start = current_period_start
        subscription.current_period_end = current_period_end
        subscription.trial_start = trial_start
        subscription.trial_end = trial_end

        subscription.recurring_interval = recurring_interval
        subscription.recurring_interval_count = recurring_interval_count
        subscription.status = status
        subscription.payment_method = payment_method
        subscription.product = product
        subscription.subscription_product_prices = subscription_product_prices
        subscription.discount = checkout.discount
        subscription.checkout = checkout
        subscription.user_metadata = checkout.user_metadata
        subscription.custom_field_data = checkout.custom_field_data
        subscription.seats = checkout.seats

        repository = SubscriptionRepository.from_session(session)
        if created:
            subscription = await repository.create(subscription, flush=True)
            await self._after_subscription_created(session, subscription)
            # ⚠️ Some users are relying on `subscription.updated` for everything
            # It was working before with Stripe since it always triggered an update
            # after creation.
            # But that's not the case with our new engine.
            # So we manually trigger it here to keep the same behavior.
            await self._on_subscription_updated(session, subscription)

        else:
            subscription = await repository.update(subscription, flush=True)
            assert previous_status is not None
            await self._after_subscription_updated(
                session,
                subscription,
                previous_status=previous_status,
                previous_is_canceled=previous_is_canceled,
            )

        # Link potential discount redemption to the subscription
        if subscription.discount is not None:
            discount_redemption_repository = DiscountRedemptionRepository.from_session(
                session
            )
            await discount_redemption_repository.set_subscription_by_checkout(
                checkout.id, subscription.id
            )

        # Reset the subscription meters to start fresh
        await self.reset_meters(session, subscription)

        # Enqueue the benefits grants for the subscription
        await self.enqueue_benefits_grants(session, subscription)

        # Notify checkout channel that a subscription has been created from it
        await publish_checkout_event(
            checkout.client_secret, CheckoutEvent.subscription_created
        )

        return subscription, created

    async def cycle(
        self,
        session: AsyncSession,
        subscription: Subscription,
        update_cycle_dates: bool = True,
    ) -> Subscription:
        if not subscription.active:
            raise InactiveSubscription(subscription)

        revoke = subscription.cancel_at_period_end
        previous_status = subscription.status
        previous_canceled = subscription.canceled

        # Subscription is due to cancel, revoke it
        if revoke:
            subscription.ended_at = subscription.ends_at
            subscription.status = SubscriptionStatus.canceled
            await self.enqueue_benefits_grants(session, subscription)
        # Normal cycle
        else:
            if update_cycle_dates:
                current_period_end = subscription.current_period_end
                assert current_period_end is not None
                subscription.current_period_start = current_period_end
                subscription.current_period_end = (
                    subscription.recurring_interval.get_next_period(
                        current_period_end, subscription.recurring_interval_count
                    )
                )

            # Check if discount is still applicable
            if subscription.discount is not None:
                assert subscription.started_at is not None
                if subscription.discount.is_repetition_expired(
                    subscription.started_at,
                    subscription.current_period_start,
                    previous_status == SubscriptionStatus.trialing,
                ):
                    subscription.discount = None

            event = event = await event_service.create_event(
                session,
                build_system_event(
                    SystemEvent.subscription_cycled,
                    customer=subscription.customer,
                    organization=subscription.organization,
                    metadata=SubscriptionCycledMetadata(
                        subscription_id=str(subscription.id),
                        product_id=str(subscription.product_id),
                        amount=subscription.amount,
                        currency=subscription.currency,
                        recurring_interval=subscription.recurring_interval.value,
                        recurring_interval_count=subscription.recurring_interval_count,
                    ),
                ),
            )
            # Add a billing entry for a new period
            billing_entry_repository = BillingEntryRepository.from_session(session)
            for subscription_product_price in subscription.subscription_product_prices:
                product_price = subscription_product_price.product_price
                if is_static_price(product_price):
                    discount_amount = 0
                    if subscription.discount:
                        discount_amount = subscription.discount.get_discount_amount(
                            subscription_product_price.amount
                        )

                    await billing_entry_repository.create(
                        BillingEntry(
                            start_timestamp=subscription.current_period_start,
                            end_timestamp=subscription.current_period_end,
                            type=BillingEntryType.cycle,
                            direction=BillingEntryDirection.debit,
                            amount=subscription_product_price.amount,
                            currency=subscription.currency,
                            customer=subscription.customer,
                            product_price=product_price,
                            discount=subscription.discount,
                            discount_amount=discount_amount,
                            subscription=subscription,
                            event=event,
                        ),
                    )

        if previous_status == SubscriptionStatus.trialing:
            subscription.status = SubscriptionStatus.active

        repository = SubscriptionRepository.from_session(session)
        subscription = await repository.update(
            subscription, update_dict={"scheduler_locked_at": None}
        )

        billing_reason = (
            OrderBillingReasonInternal.subscription_cycle_after_trial
            if previous_status == SubscriptionStatus.trialing
            else OrderBillingReasonInternal.subscription_cycle
        )
        enqueue_job(
            "order.create_subscription_order",
            subscription.id,
            billing_reason,
        )

        await self._after_subscription_updated(
            session,
            subscription,
            previous_status=previous_status,
            previous_is_canceled=previous_canceled,
        )

        return subscription

    async def reset_meters(
        self, session: AsyncSession, subscription: Subscription
    ) -> None:
        """
        Resets all the subscription meters to start fresh, optionally reporting
        rollover units if applicable.

        This should be called when creating a new subscription or cycling an
        existing one.
        """
        customer = subscription.customer
        for subscription_meter in subscription.meters:
            rollover_units = await customer_meter_service.get_rollover_units(
                session, customer, subscription_meter.meter
            )
            await event_service.create_event(
                session,
                build_system_event(
                    SystemEvent.meter_reset,
                    customer=customer,
                    organization=subscription.organization,
                    metadata={"meter_id": str(subscription_meter.meter_id)},
                ),
            )
            if rollover_units > 0:
                await event_service.create_event(
                    session,
                    build_system_event(
                        SystemEvent.meter_credited,
                        customer=customer,
                        organization=subscription.organization,
                        metadata={
                            "meter_id": str(subscription_meter.meter_id),
                            "units": rollover_units,
                            "rollover": True,
                        },
                    ),
                )

    async def _after_subscription_created(
        self, session: AsyncSession, subscription: Subscription
    ) -> None:
        await self._send_webhook(
            session, subscription, WebhookEventType.subscription_created
        )

        assert subscription.started_at is not None
        await event_service.create_event(
            session,
            build_system_event(
                SystemEvent.subscription_created,
                customer=subscription.customer,
                organization=subscription.organization,
                metadata=SubscriptionCreatedMetadata(
                    subscription_id=str(subscription.id),
                    product_id=str(subscription.product_id),
                    amount=subscription.amount,
                    currency=subscription.currency,
                    recurring_interval=subscription.recurring_interval.value,
                    recurring_interval_count=subscription.recurring_interval_count,
                    started_at=subscription.started_at.isoformat(),
                ),
            ),
        )

        # ⚠️ In some cases, the subscription is immediately active
        # Make sure then to perform all the operations required!
        if subscription.active:
            await self._on_subscription_activated(session, subscription, False)

        enqueue_job(
            "customer.webhook",
            WebhookEventType.customer_state_changed,
            subscription.customer_id,
        )

    @contextlib.asynccontextmanager
    async def lock(
        self, locker: Locker, subscription: Subscription
    ) -> AsyncGenerator[Subscription]:
        lock_name = f"subscription:{subscription.id}"
        if await locker.is_locked(lock_name):
            raise SubscriptionLocked(subscription)
        async with locker.lock(
            lock_name,
            timeout=10.0,  # Quite long, but we've experienced slow responses from Stripe in test mode
            blocking_timeout=1,
        ):
            yield subscription

    async def update(
        self,
        session: AsyncSession,
        locker: Locker,
        subscription: Subscription,
        *,
        update: SubscriptionUpdate,
    ) -> Subscription:
        if isinstance(update, SubscriptionUpdateProduct):
            return await self.update_product(
                session,
                subscription,
                product_id=update.product_id,
                proration_behavior=update.proration_behavior,
            )

        if isinstance(update, SubscriptionUpdateDiscount):
            return await self.update_discount(
                session,
                locker,
                subscription,
                discount_id=update.discount_id,
            )

        if isinstance(update, SubscriptionUpdateTrial):
            return await self.update_trial(
                session, subscription, trial_end=update.trial_end
            )

        if isinstance(update, SubscriptionUpdateSeats):
            return await self.update_seats(
