from decimal import Decimal

from prices import Money, TaxedMoney

from ...discount import DiscountValueType, VoucherType
from ...discount.utils import get_product_discount_on_sale
from ..base_calculations import (
    base_checkout_total,
    base_tax_rate,
    calculate_base_line_total_price,
    calculate_base_line_unit_price,
)
from ..fetch import fetch_checkout_lines


def test_calculate_base_line_unit_price(checkout_with_single_item):
    # given
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert not checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price


def test_calculate_base_line_unit_price_with_custom_price(checkout_with_single_item):
    # given
    line = checkout_with_single_item.lines.first()
    price_override = Decimal("12.22")
    line.price_override = price_override
    line.save(update_fields=["price_override"])

    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert not checkout_line_info.voucher

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    currency = checkout_line_info.channel_listing.currency
    expected_price = Money(price_override, currency)
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price


def test_calculate_base_line_unit_price_with_variant_on_sale(
    checkout_with_single_item, discount_info, category
):
    # given
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert not checkout_line_info.voucher
    variant = checkout_line_info.variant
    # set category on sale
    variant.product.category = category
    variant.product.save()
    checkout_line_info.product = variant.product

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[discount_info]
    )

    # then
    expected_undiscounted_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    product_collections = set(pc.id for pc in checkout_line_info.collections)
    _, sale_discount = get_product_discount_on_sale(
        product=checkout_line_info.product,
        product_collections=product_collections,
        discount=discount_info,
        channel=checkout_with_single_item.channel,
        variant_id=variant.id,
    )
    expected_price = sale_discount(expected_undiscounted_price)

    assert prices_data.undiscounted_price == expected_undiscounted_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price


def test_calculate_base_line_unit_price_with_variant_on_sale_custom_price(
    checkout_with_single_item, discount_info, category
):
    # given
    line = checkout_with_single_item.lines.first()
    price_override = Decimal("20.00")
    line.price_override = price_override
    line.save(update_fields=["price_override"])

    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert not checkout_line_info.voucher
    variant = checkout_line_info.variant
    # set category on sale
    variant.product.category = category
    variant.product.save()
    checkout_line_info.product = variant.product

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[discount_info]
    )

    # then
    currency = checkout_line_info.channel_listing.currency
    expected_undiscounted_price = Money(price_override, currency)
    product_collections = set(pc.id for pc in checkout_line_info.collections)
    _, sale_discount = get_product_discount_on_sale(
        product=checkout_line_info.product,
        product_collections=product_collections,
        discount=discount_info,
        channel=checkout_with_single_item.channel,
        variant_id=variant.id,
    )
    expected_price = sale_discount(expected_undiscounted_price)

    assert prices_data.undiscounted_price == expected_undiscounted_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price


def test_calculate_base_line_unit_price_with_fixed_voucher(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.save()

    voucher_amount = Money(Decimal(3), checkout_with_single_item.currency)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount = voucher_amount
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price - voucher_amount


def test_calculate_base_line_unit_price_with_fixed_voucher_custom_prices(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()
    price_override = Decimal("20.00")
    checkout_line.price_override = price_override
    checkout_line.save(update_fields=["price_override"])

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.save()

    voucher_amount = Money(Decimal(3), checkout_with_single_item.currency)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount = voucher_amount
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    currency = checkout_line_info.channel_listing.currency
    expected_price = Money(price_override, currency)
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price - voucher_amount


def test_calculate_base_line_unit_price_with_percentage_voucher(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.discount_value_type = DiscountValueType.PERCENTAGE
    voucher.save()

    voucher_percent_value = Decimal(10)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount_value = voucher_percent_value
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_voucher_amount = Money(Decimal("1"), checkout_with_single_item.currency)
    expected_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price - expected_voucher_amount


def test_calculate_base_line_unit_price_with_percentage_voucher_custom_prices(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()
    price_override = Decimal("20.00")
    checkout_line.price_override = price_override
    checkout_line.save(update_fields=["price_override"])

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.discount_value_type = DiscountValueType.PERCENTAGE
    voucher.save()

    voucher_percent_value = Decimal(10)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount_value = voucher_percent_value
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    currency = checkout_line_info.channel_listing.currency
    expected_price = Money(price_override, currency)
    expected_voucher_amount = Money(
        price_override * voucher_percent_value / 100, checkout_with_single_item.currency
    )
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price - expected_voucher_amount


def test_calculate_base_line_unit_price_with_discounts_apply_once_per_order(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.apply_once_per_order = True
    voucher.discount_value_type = DiscountValueType.PERCENTAGE
    voucher.save()

    voucher_percent_value = Decimal(10)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount_value = voucher_percent_value
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    # apply once per order is applied when calculating line total.
    assert prices_data.price_with_discounts == expected_price


def test_calculate_base_line_unit_price_with_discounts_once_per_order_custom_prices(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()
    price_override = Decimal("20.00")
    checkout_line.price_override = price_override
    checkout_line.save(update_fields=["price_override"])

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.apply_once_per_order = True
    voucher.discount_value_type = DiscountValueType.PERCENTAGE
    voucher.save()

    voucher_percent_value = Decimal(10)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount_value = voucher_percent_value
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    currency = checkout_line_info.channel_listing.currency
    expected_price = Money(price_override, currency)
    assert prices_data.undiscounted_price == expected_price
    assert prices_data.price_with_sale == expected_price
    # apply once per order is applied when calculating line total.
    assert prices_data.price_with_discounts == expected_price


def test_calculate_base_line_unit_price_with_variant_on_sale_and_voucher(
    checkout_with_single_item, discount_info, category, voucher, channel_USD
):
    # given
    checkout_line = checkout_with_single_item.lines.first()
    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.save()
    voucher_amount = Money(Decimal(3), checkout_with_single_item.currency)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount = voucher_amount
    voucher_channel_listing.save()
    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # set category on sale
    variant.product.category = category
    variant.product.save()
    checkout_line_info.product = variant.product

    # when
    prices_data = calculate_base_line_unit_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[discount_info]
    )

    # then
    expected_undiscounted_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    product_collections = set(pc.id for pc in checkout_line_info.collections)
    _, sale_discount = get_product_discount_on_sale(
        product=checkout_line_info.product,
        product_collections=product_collections,
        discount=discount_info,
        channel=checkout_with_single_item.channel,
        variant_id=variant.id,
    )
    sale_discount_amount = sale_discount(expected_undiscounted_price)
    expected_price = expected_undiscounted_price - sale_discount_amount

    assert prices_data.undiscounted_price == expected_undiscounted_price
    assert prices_data.price_with_sale == expected_price
    assert prices_data.price_with_discounts == expected_price - voucher_amount


def test_calculate_base_line_total_price(checkout_with_single_item):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert not checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_price * quantity
    assert prices_data.price_with_sale == expected_price * quantity
    assert prices_data.price_with_discounts == expected_price * quantity


def test_calculate_base_line_total_price_with_variant_on_sale(
    checkout_with_single_item, discount_info, category
):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert not checkout_line_info.voucher
    variant = checkout_line_info.variant
    # set category on sale
    variant.product.category = category
    variant.product.save()
    checkout_line_info.product = variant.product

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[discount_info]
    )

    # then
    expected_undiscounted_unit_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    product_collections = set(pc.id for pc in checkout_line_info.collections)
    _, sale_discount = get_product_discount_on_sale(
        product=checkout_line_info.product,
        product_collections=product_collections,
        discount=discount_info,
        channel=checkout_with_single_item.channel,
        variant_id=variant.id,
    )
    sale_discount_amount = sale_discount(expected_undiscounted_unit_price)
    expected_price = expected_undiscounted_unit_price - sale_discount_amount

    assert prices_data.undiscounted_price == expected_undiscounted_unit_price * quantity
    assert prices_data.price_with_sale == expected_price * quantity
    assert prices_data.price_with_discounts == expected_price * quantity


def test_calculate_base_line_total_price_with_fixed_voucher(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.save()

    voucher_amount = Money(Decimal(3), checkout_with_single_item.currency)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount = voucher_amount
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_unit_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_unit_price * quantity
    assert prices_data.price_with_sale == expected_unit_price * quantity
    assert (
        prices_data.price_with_discounts
        == (expected_unit_price - voucher_amount) * quantity
    )


def test_calculate_base_line_total_price_with_percentage_voucher(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.discount_value_type = DiscountValueType.PERCENTAGE
    voucher.save()

    voucher_percent_value = Decimal(10)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount_value = voucher_percent_value
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_voucher_amount = Money(Decimal("1"), checkout_with_single_item.currency)
    expected_unit_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_unit_price * quantity
    assert prices_data.price_with_sale == expected_unit_price * quantity
    assert (
        prices_data.price_with_discounts
        == (expected_unit_price - expected_voucher_amount) * quantity
    )


def test_calculate_base_line_total_price_with_discounts_apply_once_per_order(
    checkout_with_single_item, voucher, channel_USD
):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.apply_once_per_order = True
    voucher.discount_value_type = DiscountValueType.PERCENTAGE
    voucher.save()

    voucher_percent_value = Decimal(10)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount_value = voucher_percent_value
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[]
    )

    # then
    expected_voucher_amount = Money(Decimal("1"), checkout_with_single_item.currency)
    expected_unit_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    assert prices_data.undiscounted_price == expected_unit_price * quantity
    assert prices_data.price_with_sale == expected_unit_price * quantity
    # apply once per order is applied when calculating line total.
    assert (
        prices_data.price_with_discounts
        == (expected_unit_price * quantity) - expected_voucher_amount
    )


def test_calculate_base_line_total_price_with_variant_on_sale_and_voucher(
    checkout_with_single_item, discount_info, category, voucher, channel_USD
):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.save()

    voucher_amount = Money(Decimal(3), checkout_with_single_item.currency)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount = voucher_amount
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # set category on sale
    variant.product.category = category
    variant.product.save()
    checkout_line_info.product = variant.product

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[discount_info]
    )

    # then
    expected_undiscounted_unit_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    product_collections = set(pc.id for pc in checkout_line_info.collections)
    _, sale_discount = get_product_discount_on_sale(
        product=checkout_line_info.product,
        product_collections=product_collections,
        discount=discount_info,
        channel=checkout_with_single_item.channel,
        variant_id=variant.id,
    )
    sale_discount_amount = sale_discount(expected_undiscounted_unit_price)
    expected_unit_price = expected_undiscounted_unit_price - sale_discount_amount

    assert prices_data.undiscounted_price == expected_undiscounted_unit_price * quantity
    assert prices_data.price_with_sale == expected_unit_price * quantity
    assert (
        prices_data.price_with_discounts
        == (expected_unit_price - voucher_amount) * quantity
    )


def test_calculate_base_line_total_price_with_variant_on_sale_and_voucher_applied_once(
    checkout_with_single_item, discount_info, category, voucher, channel_USD
):
    # given
    quantity = 3
    checkout_line = checkout_with_single_item.lines.first()
    checkout_line.quantity = quantity
    checkout_line.save()

    checkout_line = checkout_with_single_item.lines.first()

    voucher.products.add(checkout_line.variant.product)
    voucher.type = VoucherType.SPECIFIC_PRODUCT
    voucher.apply_once_per_order = True
    voucher.save()

    voucher_amount = Money(Decimal(3), checkout_with_single_item.currency)
    voucher_channel_listing = voucher.channel_listings.get(channel=channel_USD)
    voucher_channel_listing.discount = voucher_amount
    voucher_channel_listing.save()

    checkout_with_single_item.voucher_code = voucher.code
    checkout_lines_info, _ = fetch_checkout_lines(checkout_with_single_item)
    checkout_line_info = checkout_lines_info[0]
    assert checkout_line_info.voucher
    variant = checkout_line_info.variant

    # set category on sale
    variant.product.category = category
    variant.product.save()
    checkout_line_info.product = variant.product

    # when
    prices_data = calculate_base_line_total_price(
        checkout_line_info, checkout_with_single_item.channel, discounts=[discount_info]
    )

    # then
    expected_undiscounted_unit_price = variant.get_price(
        product=checkout_line_info.product,
        collections=checkout_line_info.collections,
        channel=checkout_with_single_item.channel,
        channel_listing=checkout_line_info.channel_listing,
        discounts=[],
    )
    product_collections = set(pc.id for pc in checkout_line_info.collections)
    _, sale_discount = get_product_discount_on_sale(
        product=checkout_line_info.product,
        product_collections=product_collections,
        discount=discount_info,
        channel=checkout_with_single_item.channel,
        variant_id=variant.id,
    )
    sale_discount_amount = sale_discount(expected_undiscounted_unit_price)
    expected_unit_price = expected_undiscounted_unit_price - sale_discount_amount

    assert prices_data.undiscounted_price == expected_undiscounted_unit_price * quantity
    assert prices_data.price_with_sale == expected_unit_price * quantity
    assert (
        prices_data.price_with_discounts
        == (expected_unit_price * quantity) - voucher_amount
    )


def test_base_tax_rate_net_price_zero():
    price = TaxedMoney(net=Money(0, "USD"), gross=Money(3, "USD"))
    assert base_tax_rate(price) == Decimal("0.0")


def test_base_tax_rate_gross_price_zero():
    price = TaxedMoney(net=Money(3, "USD"), gross=Money(0, "USD"))
    assert base_tax_rate(price) == Decimal("0.0")


def test_base_checkout_total():
    # given
    currency = "USD"
    taxed_money = TaxedMoney(net=Money(10, currency), gross=Money(10, currency))
    subtotal = taxed_money
    shipping_price = taxed_money
    discount = Money(5, currency)

    # when
    total = base_checkout_total(subtotal, shipping_price, discount, currency)
    expected = subtotal + shipping_price - discount

    # then
    assert total == expected


def test_base_checkout_total_high_discount():
    # given
    currency = "USD"
    zero_taxed_money = TaxedMoney(net=Money(0, currency), gross=Money(0, currency))
    subtotal = TaxedMoney(net=Money(10, currency), gross=Money(12, currency))
    shipping_price = zero_taxed_money
    discount = Money(20, currency)

    # when
    total = base_checkout_total(subtotal, shipping_price, discount, currency)

    # then
    assert total == zero_taxed_money

[((44, 21, 44, 37), 'decimal.Decimal', 'Decimal', ({(44, 29, 44, 36): '"""12.22"""'}, {}), "('12.22')", False, 'from decimal import Decimal\n'), ((59, 21, 59, 52), 'prices.Money', 'Money', ({(59, 27, 59, 41): 'price_override', (59, 43, 59, 51): 'currency'}, {}), '(price_override, currency)', False, 'from prices import Money, TaxedMoney\n'), ((111, 21, 111, 37), 'decimal.Decimal', 'Decimal', ({(111, 29, 111, 36): '"""20.00"""'}, {}), "('20.00')", False, 'from decimal import Decimal\n'), ((131, 34, 131, 65), 'prices.Money', 'Money', ({(131, 40, 131, 54): 'price_override', (131, 56, 131, 64): 'currency'}, {}), '(price_override, currency)', False, 'from prices import Money, TaxedMoney\n'), ((191, 21, 191, 37), 'decimal.Decimal', 'Decimal', ({(191, 29, 191, 36): '"""20.00"""'}, {}), "('20.00')", False, 'from decimal import Decimal\n'), ((216, 21, 216, 52), 'prices.Money', 'Money', ({(216, 27, 216, 41): 'price_override', (216, 43, 216, 51): 'currency'}, {}), '(price_override, currency)', False, 'from prices import Money, TaxedMoney\n'), ((233, 28, 233, 39), 'decimal.Decimal', 'Decimal', ({(233, 36, 233, 38): '10'}, {}), '(10)', False, 'from decimal import Decimal\n'), ((268, 21, 268, 37), 'decimal.Decimal', 'Decimal', ({(268, 29, 268, 36): '"""20.00"""'}, {}), "('20.00')", False, 'from decimal import Decimal\n'), ((277, 28, 277, 39), 'decimal.Decimal', 'Decimal', ({(277, 36, 277, 38): '10'}, {}), '(10)', False, 'from decimal import Decimal\n'), ((294, 21, 294, 52), 'prices.Money', 'Money', ({(294, 27, 294, 41): 'price_override', (294, 43, 294, 51): 'currency'}, {}), '(price_override, currency)', False, 'from prices import Money, TaxedMoney\n'), ((295, 30, 297, 5), 'prices.Money', 'Money', ({(296, 8, 296, 52): 'price_override * voucher_percent_value / 100', (296, 54, 296, 88): 'checkout_with_single_item.currency'}, {}), '(price_override * voucher_percent_value / 100,\n    checkout_with_single_item.currency)', False, 'from prices import Money, TaxedMoney\n'), ((315, 28, 315, 39), 'decimal.Decimal', 'Decimal', ({(315, 36, 315, 38): '10'}, {}), '(10)', False, 'from decimal import Decimal\n'), ((350, 21, 350, 37), 'decimal.Decimal', 'Decimal', ({(350, 29, 350, 36): '"""20.00"""'}, {}), "('20.00')", False, 'from decimal import Decimal\n'), ((360, 28, 360, 39), 'decimal.Decimal', 'Decimal', ({(360, 36, 360, 38): '10'}, {}), '(10)', False, 'from decimal import Decimal\n'), ((377, 21, 377, 52), 'prices.Money', 'Money', ({(377, 27, 377, 41): 'price_override', (377, 43, 377, 51): 'currency'}, {}), '(price_override, currency)', False, 'from prices import Money, TaxedMoney\n'), ((576, 28, 576, 39), 'decimal.Decimal', 'Decimal', ({(576, 36, 576, 38): '10'}, {}), '(10)', False, 'from decimal import Decimal\n'), ((626, 28, 626, 39), 'decimal.Decimal', 'Decimal', ({(626, 36, 626, 38): '10'}, {}), '(10)', False, 'from decimal import Decimal\n'), ((803, 15, 803, 33), 'prices.Money', 'Money', ({(803, 21, 803, 22): '5', (803, 24, 803, 32): 'currency'}, {}), '(5, currency)', False, 'from prices import Money, TaxedMoney\n'), ((819, 15, 819, 34), 'prices.Money', 'Money', ({(819, 21, 819, 23): '20', (819, 25, 819, 33): 'currency'}, {}), '(20, currency)', False, 'from prices import Money, TaxedMoney\n'), ((157, 27, 157, 37), 'decimal.Decimal', 'Decimal', ({(157, 35, 157, 36): '3'}, {}), '(3)', False, 'from decimal import Decimal\n'), ((199, 27, 199, 37), 'decimal.Decimal', 'Decimal', ({(199, 35, 199, 36): '3'}, {}), '(3)', False, 'from decimal import Decimal\n'), ((250, 36, 250, 48), 'decimal.Decimal', 'Decimal', ({(250, 44, 250, 47): '"""1"""'}, {}), "('1')", False, 'from decimal import Decimal\n'), ((392, 27, 392, 37), 'decimal.Decimal', 'Decimal', ({(392, 35, 392, 36): '3'}, {}), '(3)', False, 'from decimal import Decimal\n'), ((528, 27, 528, 37), 'decimal.Decimal', 'Decimal', ({(528, 35, 528, 36): '3'}, {}), '(3)', False, 'from decimal import Decimal\n'), ((593, 36, 593, 48), 'decimal.Decimal', 'Decimal', ({(593, 44, 593, 47): '"""1"""'}, {}), "('1')", False, 'from decimal import Decimal\n'), ((643, 36, 643, 48), 'decimal.Decimal', 'Decimal', ({(643, 44, 643, 47): '"""1"""'}, {}), "('1')", False, 'from decimal import Decimal\n'), ((675, 27, 675, 37), 'decimal.Decimal', 'Decimal', ({(675, 35, 675, 36): '3'}, {}), '(3)', False, 'from decimal import Decimal\n'), ((739, 27, 739, 37), 'decimal.Decimal', 'Decimal', ({(739, 35, 739, 36): '3'}, {}), '(3)', False, 'from decimal import Decimal\n'), ((789, 35, 789, 49), 'decimal.Decimal', 'Decimal', ({(789, 43, 789, 48): '"""0.0"""'}, {}), "('0.0')", False, 'from decimal import Decimal\n'), ((794, 35, 794, 49), 'decimal.Decimal', 'Decimal', ({(794, 43, 794, 48): '"""0.0"""'}, {}), "('0.0')", False, 'from decimal import Decimal\n'), ((788, 27, 788, 42), 'prices.Money', 'Money', ({(788, 33, 788, 34): '0', (788, 36, 788, 41): '"""USD"""'}, {}), "(0, 'USD')", False, 'from prices import Money, TaxedMoney\n'), ((788, 50, 788, 65), 'prices.Money', 'Money', ({(788, 56, 788, 57): '3', (788, 59, 788, 64): '"""USD"""'}, {}), "(3, 'USD')", False, 'from prices import Money, TaxedMoney\n'), ((793, 27, 793, 42), 'prices.Money', 'Money', ({(793, 33, 793, 34): '3', (793, 36, 793, 41): '"""USD"""'}, {}), "(3, 'USD')", False, 'from prices import Money, TaxedMoney\n'), ((793, 50, 793, 65), 'prices.Money', 'Money', ({(793, 56, 793, 57): '0', (793, 59, 793, 64): '"""USD"""'}, {}), "(0, 'USD')", False, 'from prices import Money, TaxedMoney\n'), ((800, 33, 800, 52), 'prices.Money', 'Money', ({(800, 39, 800, 41): '10', (800, 43, 800, 51): 'currency'}, {}), '(10, currency)', False, 'from prices import Money, TaxedMoney\n'), ((800, 60, 800, 79), 'prices.Money', 'Money', ({(800, 66, 800, 68): '10', (800, 70, 800, 78): 'currency'}, {}), '(10, currency)', False, 'from prices import Money, TaxedMoney\n'), ((816, 38, 816, 56), 'prices.Money', 'Money', ({(816, 44, 816, 45): '0', (816, 47, 816, 55): 'currency'}, {}), '(0, currency)', False, 'from prices import Money, TaxedMoney\n'), ((816, 64, 816, 82), 'prices.Money', 'Money', ({(816, 70, 816, 71): '0', (816, 73, 816, 81): 'currency'}, {}), '(0, currency)', False, 'from prices import Money, TaxedMoney\n'), ((817, 30, 817, 49), 'prices.Money', 'Money', ({(817, 36, 817, 38): '10', (817, 40, 817, 48): 'currency'}, {}), '(10, currency)', False, 'from prices import Money, TaxedMoney\n'), ((817, 57, 817, 76), 'prices.Money', 'Money', ({(817, 63, 817, 65): '12', (817, 67, 817, 75): 'currency'}, {}), '(12, currency)', False, 'from prices import Money, TaxedMoney\n')]