import uuid
from datetime import UTC, datetime, timedelta
from types import SimpleNamespace
from typing import Any
from unittest.mock import ANY, AsyncMock, MagicMock

import pytest
import pytest_asyncio
import stripe as stripe_lib
from pydantic import HttpUrl, ValidationError
from pytest_mock import MockerFixture
from sqlalchemy.orm import joinedload

from polar.auth.models import Anonymous, AuthSubject
from polar.checkout.guard import has_product_checkout
from polar.checkout.schemas import (
    CheckoutConfirm,
    CheckoutConfirmStripe,
    CheckoutCreatePublic,
    CheckoutPriceCreate,
    CheckoutProductCreate,
    CheckoutProductsCreate,
    CheckoutUpdate,
    CheckoutUpdatePublic,
)
from polar.checkout.service import (
    AlreadyActiveSubscriptionError,
    NotConfirmedCheckout,
    NotOpenCheckout,
    TrialAlreadyRedeemed,
)
from polar.checkout.service import checkout as checkout_service
from polar.config import Environment, settings
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.enums import AccountType, PaymentProcessor, SubscriptionRecurringInterval
from polar.event.repository import EventRepository
from polar.event.system import SystemEvent
from polar.exceptions import PaymentNotReady, PolarRequestValidationError
from polar.integrations.stripe.schemas import ProductType
from polar.integrations.stripe.service import StripeService
from polar.kit.address import AddressInput
from polar.kit.tax import (
    IncompleteTaxLocation,
    TaxabilityReason,
    TaxIDFormat,
    calculate_tax,
)
from polar.kit.trial import TrialInterval
from polar.kit.utils import utc_now
from polar.locker import Locker
from polar.models import (
    Account,
    Checkout,
    CheckoutProduct,
    Customer,
    Discount,
    DiscountRedemption,
    Organization,
    Payment,
    Product,
    User,
    UserOrganization,
)
from polar.models.checkout import CheckoutStatus
from polar.models.custom_field import CustomFieldType
from polar.models.discount import DiscountDuration, DiscountType
from polar.models.order import OrderBillingReasonInternal
from polar.models.organization import OrganizationStatus
from polar.models.product_price import (
    ProductPriceAmountType,
    ProductPriceCustom,
    ProductPriceFixed,
    ProductPriceFree,
    ProductPriceSeatUnit,
)
from polar.models.subscription import SubscriptionStatus
from polar.models.user import IdentityVerificationStatus
from polar.order.service import OrderService
from polar.postgres import AsyncSession
from polar.product.guard import is_fixed_price, is_metered_price, is_seat_price
from polar.product.schemas import ProductPriceFixedCreate
from polar.subscription.service import SubscriptionService
from polar.trial_redemption.repository import TrialRedemptionRepository
from tests.fixtures.auth import AuthSubjectFixture
from tests.fixtures.database import SaveFixture
from tests.fixtures.random_objects import (
    create_active_subscription,
    create_checkout,
    create_checkout_link,
    create_custom_field,
    create_customer,
    create_discount,
    create_product,
    create_product_price_fixed,
    create_product_price_seat_unit,
    create_subscription,
    create_trial_redemption,
)

MINIMUM_AMOUNT = 2500
PRESET_AMOUNT = 5000


@pytest.fixture(autouse=True)
def stripe_service_mock(mocker: MockerFixture) -> MagicMock:
    mock = MagicMock(spec=StripeService)
    mocker.patch("polar.checkout.service.stripe_service", new=mock)
    return mock


@pytest.fixture(autouse=True)
def subscription_service_mock(mocker: MockerFixture) -> MagicMock:
    mock = MagicMock(spec=SubscriptionService)
    mocker.patch("polar.checkout.service.subscription_service", new=mock)
    return mock


@pytest.fixture(autouse=True)
def order_service_mock(mocker: MockerFixture) -> MagicMock:
    mock = MagicMock(spec=OrderService)
    mocker.patch("polar.checkout.service.order_service", new=mock)
    return mock


@pytest.fixture(autouse=True)
def calculate_tax_mock(mocker: MockerFixture) -> AsyncMock:
    mock = AsyncMock(spec=calculate_tax)
    mocker.patch("polar.checkout.service.calculate_tax", new=mock)
    mock.return_value = {"processor_id": "TAX_PROCESSOR_ID", "amount": 0}
    return mock


@pytest.fixture
def product_parametrization_helper(request: pytest.FixtureRequest) -> Product:
    return request.getfixturevalue(request.param)


@pytest_asyncio.fixture
async def checkout_one_time_fixed(
    save_fixture: SaveFixture, product_one_time: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_one_time])


@pytest_asyncio.fixture
async def checkout_one_time_custom(
    save_fixture: SaveFixture, product_one_time_custom_price: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_one_time_custom_price])


@pytest_asyncio.fixture
async def checkout_one_time_free(
    save_fixture: SaveFixture, product_one_time_free_price: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_one_time_free_price])


@pytest_asyncio.fixture
async def checkout_recurring_fixed(
    save_fixture: SaveFixture, product: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product])


@pytest_asyncio.fixture
async def checkout_recurring_free(
    save_fixture: SaveFixture, product_recurring_free_price: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_recurring_free_price])


@pytest_asyncio.fixture
async def checkout_confirmed_one_time(
    save_fixture: SaveFixture, product_one_time: Product
) -> Checkout:
    return await create_checkout(
        save_fixture, products=[product_one_time], status=CheckoutStatus.confirmed
    )


@pytest_asyncio.fixture
async def checkout_confirmed_recurring(
    save_fixture: SaveFixture, product: Product
) -> Checkout:
    return await create_checkout(
        save_fixture, products=[product], status=CheckoutStatus.confirmed
    )


@pytest_asyncio.fixture
async def checkout_confirmed_recurring_upgrade(
    save_fixture: SaveFixture,
    product: Product,
    product_recurring_free_price: Product,
    customer: Customer,
) -> Checkout:
    subscription = await create_subscription(
        save_fixture, product=product_recurring_free_price, customer=customer
    )
    return await create_checkout(
        save_fixture,
        products=[product],
        status=CheckoutStatus.confirmed,
        subscription=subscription,
    )


@pytest_asyncio.fixture
async def checkout_discount_percentage_100(
    save_fixture: SaveFixture, product: Product, discount_percentage_100: Discount
) -> Checkout:
    return await create_checkout(
        save_fixture,
        products=[product],
        status=CheckoutStatus.open,
        discount=discount_percentage_100,
    )


@pytest_asyncio.fixture
async def product_custom_fields(
    save_fixture: SaveFixture, organization: Organization
) -> Product:
    text_field = await create_custom_field(
        save_fixture, type=CustomFieldType.text, slug="text", organization=organization
    )
    select_field = await create_custom_field(
        save_fixture,
        type=CustomFieldType.select,
        slug="select",
        organization=organization,
        properties={
            "options": [{"value": "a", "label": "A"}, {"value": "b", "label": "B"}],
        },
    )
    return await create_product(
        save_fixture,
        organization=organization,
        recurring_interval=SubscriptionRecurringInterval.month,
        attached_custom_fields=[(text_field, False), (select_field, True)],
    )


@pytest_asyncio.fixture
async def checkout_custom_fields(
    save_fixture: SaveFixture, product_custom_fields: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_custom_fields])


@pytest_asyncio.fixture
async def product_tax_not_applicable(
    save_fixture: SaveFixture, organization: Organization
) -> Product:
    return await create_product(
        save_fixture,
        organization=organization,
        recurring_interval=SubscriptionRecurringInterval.month,
        is_tax_applicable=False,
    )


@pytest_asyncio.fixture
async def checkout_tax_not_applicable(
    save_fixture: SaveFixture, product_tax_not_applicable: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_tax_not_applicable])


@pytest_asyncio.fixture
async def product_custom_price_minimum(
    save_fixture: SaveFixture, organization: Organization
) -> Product:
    return await create_product(
        save_fixture,
        organization=organization,
        recurring_interval=None,
        prices=[(MINIMUM_AMOUNT, None, None)],
    )


@pytest_asyncio.fixture
async def product_custom_price_preset(
    save_fixture: SaveFixture, organization: Organization
) -> Product:
    return await create_product(
        save_fixture,
        organization=organization,
        recurring_interval=None,
        prices=[(MINIMUM_AMOUNT, None, PRESET_AMOUNT)],
    )


@pytest_asyncio.fixture
async def product_custom_price_no_amounts(
    save_fixture: SaveFixture, organization: Organization
) -> Product:
    return await create_product(
        save_fixture,
        organization=organization,
        recurring_interval=None,
        prices=[(None, None, None)],
    )


@pytest_asyncio.fixture
async def product_seat_based(
    save_fixture: SaveFixture, organization: Organization
) -> Product:
    product = await create_product(
        save_fixture,
        organization=organization,
        recurring_interval=SubscriptionRecurringInterval.month,
        prices=[],
    )
    price = await create_product_price_seat_unit(
        save_fixture, product=product, price_per_seat=1000
    )
    product.prices = [price]
    return product


@pytest_asyncio.fixture
async def checkout_seat_based(
    save_fixture: SaveFixture, product_seat_based: Product
) -> Checkout:
    return await create_checkout(save_fixture, products=[product_seat_based], seats=5)


@pytest.mark.asyncio
class TestCreate:
    @pytest.mark.auth
    async def test_not_existing_price(
        self, session: AsyncSession, auth_subject: AuthSubject[User]
    ) -> None:
        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=uuid.uuid4(),
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user_second"),
        AuthSubjectFixture(subject="organization_second"),
    )
    async def test_not_writable_price(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        product_one_time: Product,
    ) -> None:
        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=product_one_time.prices[0].id,
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_archived_price(
        self,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        price = await create_product_price_fixed(
            save_fixture,
            product=product_one_time,
            is_archived=True,
        )
        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(product_price_id=price.id),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_archived_product(
        self,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        product_one_time.is_archived = True
        await save_fixture(product_one_time)
        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=product_one_time.prices[0].id,
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    @pytest.mark.parametrize("amount", [500, 10000])
    async def test_amount_invalid_limits(
        self,
        amount: int,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time_custom_price: Product,
    ) -> None:
        price = product_one_time_custom_price.prices[0]
        assert isinstance(price, ProductPriceCustom)
        price.minimum_amount = 1000
        price.maximum_amount = 5000
        await save_fixture(price)

        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=product_one_time_custom_price.prices[0].id,
                    amount=amount,
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    @pytest.mark.parametrize(
        "payload",
        [
            {"customer_tax_id": "123"},
            {"customer_billing_address": {"country": "FR"}, "customer_tax_id": "123"},
        ],
    )
    async def test_invalid_tax_id(
        self,
        payload: dict[str, Any],
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)

        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate.model_validate(
                    {
                        "payment_processor": PaymentProcessor.stripe,
                        "product_price_id": price.id,
                        **payload,
                    }
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_invalid_not_existing_subscription(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product: Product,
    ) -> None:
        price = product.prices[0]
        assert isinstance(price, ProductPriceFixed)

        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=price.id,
                    subscription_id=uuid.uuid4(),
                    metadata={"key": "value"},
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_invalid_not_existing_discount(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product: Product,
    ) -> None:
        price = product.prices[0]
        assert isinstance(price, ProductPriceFixed)

        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=price.id,
                    discount_id=uuid.uuid4(),
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_invalid_not_applicable_discount(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time_free_price: Product,
        discount_fixed_once: Discount,
    ) -> None:
        price = product_one_time_free_price.prices[0]
        assert isinstance(price, ProductPriceFree)

        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=price.id,
                    discount_id=discount_fixed_once.id,
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_invalid_upgrade_paid_subscription(
        self,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product: Product,
        product_second: Product,
        customer: Customer,
    ) -> None:
        subscription = await create_subscription(
            save_fixture, product=product, customer=customer
        )

        with pytest.raises(PolarRequestValidationError):
            await checkout_service.create(
                session,
                CheckoutProductsCreate(
                    products=[product_second.id],
                    subscription_id=subscription.id,
                    metadata={"key": "value"},
                ),
                auth_subject,
            )

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    @pytest.mark.parametrize("amount", [None, 4242])
    async def test_valid_fixed_price(
        self,
        amount: int | None,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)
        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                amount=amount,
                metadata={"key": "value"},
            ),
            auth_subject,
        )

        assert checkout.product_price == price
        assert checkout.product == product_one_time
        assert checkout.products == [product_one_time]
        assert checkout.amount == price.price_amount
        assert checkout.currency == price.price_currency
        assert checkout.user_metadata == {"key": "value"}

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    @pytest.mark.parametrize("amount", [None, 4242])
    async def test_valid_free_price(
        self,
        amount: int | None,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time_free_price: Product,
    ) -> None:
        price = product_one_time_free_price.prices[0]
        assert isinstance(price, ProductPriceFree)
        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                amount=amount,
                metadata={"key": "value"},
            ),
            auth_subject,
        )

        assert checkout.product_price == price
        assert checkout.product == product_one_time_free_price
        assert checkout.products == [product_one_time_free_price]
        assert checkout.amount == 0
        assert checkout.currency == "usd"
        assert checkout.user_metadata == {"key": "value"}

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    @pytest.mark.parametrize("amount", [None, 1000])
    async def test_valid_custom_price(
        self,
        amount: int | None,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time_custom_price: Product,
    ) -> None:
        price = product_one_time_custom_price.prices[0]
        assert isinstance(price, ProductPriceCustom)
        price.preset_amount = 4242

        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                amount=amount,
                metadata={"key": "value"},
            ),
            auth_subject,
        )

        assert checkout.product_price == price
        assert checkout.product == product_one_time_custom_price
        assert checkout.products == [product_one_time_custom_price]
        if amount is None:
            assert checkout.amount == price.preset_amount
        else:
            assert checkout.amount == amount
        assert checkout.currency == price.price_currency
        assert checkout.user_metadata == {"key": "value"}

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_valid_metered_price(
        self,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_recurring_metered: Product,
    ) -> None:
        price = product_recurring_metered.prices[0]
        assert is_metered_price(price)

        checkout = await checkout_service.create(
            session,
            CheckoutProductsCreate(products=[product_recurring_metered.id]),
            auth_subject,
        )

        assert checkout.product_price == price
        assert checkout.product == product_recurring_metered
        assert checkout.products == [product_recurring_metered]
        assert checkout.currency == price.price_currency

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_valid_fixed_and_metered_price(
        self,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_recurring_fixed_and_metered: Product,
    ) -> None:
        static_price = next(
            p for p in product_recurring_fixed_and_metered.prices if is_fixed_price(p)
        )

        checkout = await checkout_service.create(
            session,
            CheckoutProductsCreate(products=[product_recurring_fixed_and_metered.id]),
            auth_subject,
        )

        assert checkout.product_price == static_price
        assert checkout.product == product_recurring_fixed_and_metered
        assert checkout.products == [product_recurring_fixed_and_metered]
        assert checkout.amount == static_price.price_amount
        assert checkout.currency == static_price.price_currency

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_valid_tax_id(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)
        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                customer_billing_address=AddressInput.model_validate({"country": "FR"}),
                customer_tax_id="FR61954506077",
            ),
            auth_subject,
        )

        assert checkout.customer_tax_id == ("FR61954506077", TaxIDFormat.eu_vat)
        assert checkout.customer_tax_id_number == "FR61954506077"

    @pytest.mark.auth
    async def test_valid_success_url_with_interpolation(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)
        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                success_url=HttpUrl(
                    "https://example.com/success?checkout_id={CHECKOUT_ID}"
                ),
            ),
            auth_subject,
        )

        assert (
            checkout.success_url
            == f"https://example.com/success?checkout_id={checkout.id}"
        )

    @pytest.mark.auth
    async def test_valid_success_url_with_invalid_interpolation_variable(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User],
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)
        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                success_url=HttpUrl(
                    "https://example.com/success?checkout_id={CHECKOUT_SESSION_ID}"
                ),
            ),
            auth_subject,
        )

        assert (
            checkout.success_url
            == "https://example.com/success?checkout_id={CHECKOUT_SESSION_ID}"
        )

    async def test_silent_calculate_tax_error(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        calculate_tax_mock: AsyncMock,
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        calculate_tax_mock.side_effect = IncompleteTaxLocation(
            stripe_lib.InvalidRequestError("ERROR", "ERROR")
        )

        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)

        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                customer_billing_address=AddressInput.model_validate({"country": "US"}),
            ),
            auth_subject,
        )

        assert checkout.tax_amount is None
        assert checkout.customer_billing_address is not None
        assert checkout.customer_billing_address.country == "US"

    async def test_valid_calculate_tax(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        calculate_tax_mock: AsyncMock,
        user_organization: UserOrganization,
        product_one_time: Product,
    ) -> None:
        calculate_tax_mock.return_value = {
            "processor_id": "TAX_PROCESSOR_ID",
            "amount": 100,
            "taxability_reason": TaxabilityReason.standard_rated,
            "tax_rate": {},
        }

        price = product_one_time.prices[0]
        assert isinstance(price, ProductPriceFixed)

        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                customer_billing_address=AddressInput.model_validate({"country": "FR"}),
            ),
            auth_subject,
        )

        assert checkout.tax_amount == 100
        assert checkout.tax_processor_id == "TAX_PROCESSOR_ID"
        assert checkout.customer_billing_address is not None
        assert checkout.customer_billing_address.country == "FR"

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    async def test_valid_subscription_upgrade(
        self,
        stripe_service_mock: MagicMock,
        save_fixture: SaveFixture,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product: Product,
        product_recurring_free_price: Product,
        customer: Customer,
    ) -> None:
        stripe_service_mock.create_customer_session.return_value = SimpleNamespace(
            client_secret="STRIPE_CUSTOMER_SESSION_SECRET",
        )
        subscription = await create_subscription(
            save_fixture, product=product_recurring_free_price, customer=customer
        )

        price = product.prices[0]
        assert isinstance(price, ProductPriceFixed)

        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                subscription_id=subscription.id,
                metadata={"key": "value"},
            ),
            auth_subject,
        )

        assert checkout.product_price == price
        assert checkout.product == product
        assert checkout.subscription == subscription
        assert (
            checkout.payment_processor_metadata["customer_session_client_secret"]
            == "STRIPE_CUSTOMER_SESSION_SECRET"
        )

    @pytest.mark.parametrize(
        "custom_field_data",
        [pytest.param({"text": "abc", "select": "c"}, id="invalid select")],
    )
    async def test_invalid_custom_field_data(
        self,
        custom_field_data: dict[str, Any],
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_custom_fields: Product,
    ) -> None:
        price = product_custom_fields.prices[0]
        assert isinstance(price, ProductPriceFixed)

        with pytest.raises(PolarRequestValidationError) as e:
            await checkout_service.create(
                session,
                CheckoutPriceCreate(
                    product_price_id=price.id,
                    custom_field_data=custom_field_data,
                ),
                auth_subject,
            )

        for error in e.value.errors():
            assert error["loc"][0:2] == ("body", "custom_field_data")

    async def test_valid_custom_field_data(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_custom_fields: Product,
    ) -> None:
        price = product_custom_fields.prices[0]
        assert isinstance(price, ProductPriceFixed)

        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id,
                custom_field_data={"text": "abc", "select": "a"},
            ),
            auth_subject,
        )

        assert checkout.custom_field_data == {"text": "abc", "select": "a"}

    async def test_valid_missing_required_custom_field(
        self,
        session: AsyncSession,
        auth_subject: AuthSubject[User | Organization],
        user_organization: UserOrganization,
        product_custom_fields: Product,
    ) -> None:
        price = product_custom_fields.prices[0]
        assert isinstance(price, ProductPriceFixed)

        checkout = await checkout_service.create(
            session,
            CheckoutPriceCreate(
                product_price_id=price.id, custom_field_data={"text": "abc"}
            ),
            auth_subject,
        )

        assert checkout.custom_field_data == {"text": "abc", "select": None}

    @pytest.mark.auth(
        AuthSubjectFixture(subject="user"),
        AuthSubjectFixture(subject="organization"),
    )
    @pytest.mark.parametrize("amount", [None, 4242])
    async def test_valid_embed_origin(
        self,
