from decimal import Decimal

from django.test import TestCase, override_settings
from django.utils import timezone

from basic_info import models as basic_models
from business import services
from business.amounts import calculate_line_amount, normalize_money_amount

from .fixtures import create_basic_fixtures


class BusinessAmountRulesTestCase(TestCase):
    def setUp(self):
        (
            self.merchant,
            self.supplier,
            self.warehouse_strict,
            self.warehouse_relaxed,
            self.product,
            self.operator,
        ) = create_basic_fixtures()

    def test_line_amount_rounds_half_up_to_integer(self):
        self.assertEqual(calculate_line_amount(quantity='1', price='1.49'), Decimal('1'))
        self.assertEqual(calculate_line_amount(quantity='1', price='1.50'), Decimal('2'))
        self.assertEqual(calculate_line_amount(quantity='1.5', price='1.5'), Decimal('2'))
        self.assertEqual(calculate_line_amount(quantity='1', price='2.5'), Decimal('3'))

    def test_order_total_sums_rounded_line_amounts(self):
        order = services.create_purchase_order(
            merchant=self.merchant,
            supplier=self.supplier,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=[
                {'product_id': self.product.id, 'quantity': '1', 'num_of_rolls': 1, 'price': '1.5', 'unit': '米'},
                {'product_id': self.product.id, 'quantity': '1', 'num_of_rolls': 1, 'price': '1.5', 'unit': '米'},
            ],
        )

        self.assertEqual([item.total_amount() for item in order.items.order_by('id')], [Decimal('2'), Decimal('2')])
        self.assertEqual(order.get_total_amount(), Decimal('4'))

    def test_payment_and_receipt_amounts_are_normalized_on_create(self):
        payment = services.create_payment_order(
            merchant=self.merchant,
            supplier=self.supplier,
            payment_date=timezone.now().date(),
            amount='120.50',
            discount_amount='20.49',
            operator=self.operator,
        )
        self.assertEqual(payment.amount, Decimal('121'))
        self.assertEqual(payment.discount_amount, Decimal('20'))
        self.assertEqual(payment.settlement_amount, Decimal('141'))

        customer = basic_models.Customer.objects.create(
            merchant=self.merchant,
            name='金额测试客户',
            created_by=None,
        )
        receipt = services.create_receipt_order(
            merchant=self.merchant,
            customer=customer,
            receipt_date=timezone.now().date(),
            amount='80.49',
            discount_amount='5.50',
            operator=self.operator,
        )
        self.assertEqual(receipt.amount, Decimal('80'))
        self.assertEqual(receipt.discount_amount, Decimal('6'))
        self.assertEqual(receipt.settlement_amount, Decimal('86'))

    def test_normalize_money_amount_rounds_negative_half_away_from_zero(self):
        self.assertEqual(normalize_money_amount('-25.50'), Decimal('-26'))

    @override_settings(BUSINESS_AMOUNT_MODE='standard_decimal_2')
    def test_standard_decimal_mode_preserves_original_precision(self):
        self.assertEqual(
            calculate_line_amount(quantity='10', standard_quantity='9.50', price='12.345'),
            Decimal('117.28'),
        )

        payment = services.create_payment_order(
            merchant=self.merchant,
            supplier=self.supplier,
            payment_date=timezone.now().date(),
            amount='120.50',
            discount_amount='20.49',
            operator=self.operator,
        )
        self.assertEqual(payment.amount, Decimal('120.50'))
        self.assertEqual(payment.discount_amount, Decimal('20.49'))
        self.assertEqual(payment.settlement_amount, Decimal('140.99'))

    @override_settings(BUSINESS_AMOUNT_MODE='unknown_mode')
    def test_invalid_amount_mode_fails_fast(self):
        with self.assertRaisesMessage(ValueError, 'BUSINESS_AMOUNT_MODE 不合法'):
            calculate_line_amount(quantity='1', price='1')
