from decimal import Decimal

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone

from basic_info import models as basic_models
from business import models as business_models, services
from stock import models as stock_models
from stock import services as stock_services

from .fixtures import create_basic_fixtures, create_sales_fixtures


class BusinessStatementServiceTestCase(TestCase):
    def _disable_auto_stock_tasks(self, merchant):
        basic_models.MerchantSetting.objects.filter(
            merchant=merchant,
            key=basic_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS,
        ).update(val_bool=False)

    def test_build_customer_statement_collects_all_customer_records(self):
        merchant, customer, warehouse_strict, warehouse_relaxed, _, product, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        sales_order = services.create_sales_order(
            merchant=merchant,
            customer=customer,
            order_date=today,
            warehouse=warehouse_strict,
            operator=operator,
            items=[{'product_id': product.id, 'numbers': [6, 4], 'price': '10', 'unit': '米'}],
            remarks='销售对账',
        )
        services.review_sales_order(
            sales_order=sales_order,
            target_status=business_models.SalesOrderStatusEnum.APPROVED,
        )

        return_order = services.create_sales_return_order(
            merchant=merchant,
            customer=customer,
            return_date=today,
            warehouse=warehouse_relaxed,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 3, 'num_of_rolls': 1, 'price': '10'}],
            remarks='销退对账',
        )
        services.review_sales_return_order(
            sales_return_order=return_order,
            target_status=business_models.SalesReturnStatusEnum.APPROVED,
        )

        receipt_order = services.create_receipt_order(
            merchant=merchant,
            customer=customer,
            receipt_date=today,
            amount='20',
            discount_amount='2',
            operator=operator,
            remarks='收款对账',
        )
        services.review_receipt_order(
            receipt_order=receipt_order,
            target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
        )

        external_sale_order = business_models.ExternalCustomerStatementOrder.objects.create(
            merchant=merchant,
            customer=customer,
            category=business_models.ExternalCustomerStatementCategoryEnum.SALE,
            external_source_id='EXT-SALE-1',
            occurred_at=today,
            total_amount=Decimal('50'),
            sf_amount=Decimal('5'),
            zk_amount=Decimal('3'),
            items_payload=[{'product_name': '外部销售'}],
        )
        external_return_order = business_models.ExternalCustomerStatementOrder.objects.create(
            merchant=merchant,
            customer=customer,
            category=business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN,
            external_source_id='EXT-RETURN-1',
            occurred_at=today,
            total_amount=Decimal('7'),
            zk_amount=Decimal('1'),
            items_payload=[{'product_name': '外部退货'}],
        )

        payload = services.build_customer_statement(merchant=merchant, customer=customer)

        source_types = {record['source_type'] for record in payload['records']}
        self.assertEqual(
            source_types,
            {
                'sales_order',
                'sales_return_order',
                'receipt_order',
                'external_sales_order',
                'external_sales_return_order',
            },
        )
        sales_record = next(record for record in payload['records'] if record['source_type'] == 'sales_order')
        self.assertEqual(sales_record['human_id'], sales_order.human_id)
        self.assertEqual(sales_record['items'][0]['quantity'], Decimal('10'))
        self.assertEqual(sales_record['items'][0]['quantity_of_rolls'], [6, 4])
        return_record = next(record for record in payload['records'] if record['source_type'] == 'sales_return_order')
        self.assertEqual(return_record['human_id'], return_order.human_id)
        receipt_record = next(record for record in payload['records'] if record['source_type'] == 'receipt_order')
        self.assertEqual(receipt_record['human_id'], receipt_order.human_id)
        self.assertEqual(receipt_record['positive_amount'], Decimal('-2.00'))
        self.assertEqual(receipt_record['negative_amount'], Decimal('20.00'))
        external_sale_record = next(record for record in payload['records'] if record['source_type'] == 'external_sales_order')
        self.assertEqual(external_sale_record['human_id'], str(external_sale_order.id))
        external_return_record = next(record for record in payload['records'] if record['source_type'] == 'external_sales_return_order')
        self.assertEqual(external_return_record['human_id'], str(external_return_order.id))
        summary = services.build_statement_summary(payload)
        self.assertIn('positive_total', summary)
        self.assertIn('negative_total', summary)

    def test_build_supplier_statement_collects_all_supplier_records(self):
        merchant, supplier, warehouse_strict, warehouse_relaxed, product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        purchase_order = services.create_purchase_order(
            merchant=merchant,
            supplier=supplier,
            order_date=today,
            warehouse=warehouse_strict,
            operator=operator,
            items=[{'product_id': product.id, 'numbers': [8, 2], 'price': '9', 'unit': '米'}],
            remarks='采购对账',
        )
        services.review_purchase_order(
            purchase_order=purchase_order,
            target_status=business_models.PurchaseOrderStatusEnum.APPROVED,
        )

        return_order = services.create_purchase_return_order(
            merchant=merchant,
            supplier=supplier,
            return_date=today,
            warehouse=warehouse_relaxed,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 4, 'num_of_rolls': 1, 'price': '9'}],
            remarks='采退对账',
        )
        services.review_purchase_return_order(
            purchase_return_order=return_order,
            target_status=business_models.PurchaseReturnStatusEnum.APPROVED,
        )

        payment_order = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=today,
            amount='15',
            operator=operator,
            remarks='付款对账',
        )
        services.review_payment_order(
            payment_order=payment_order,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
        )

        payload = services.build_supplier_statement(merchant=merchant, supplier=supplier)

        source_types = {record['source_type'] for record in payload['records']}
        self.assertEqual(source_types, {'purchase_order', 'purchase_return_order', 'payment_order'})
        purchase_record = next(record for record in payload['records'] if record['source_type'] == 'purchase_order')
        self.assertEqual(purchase_record['human_id'], purchase_order.human_id)
        self.assertEqual(purchase_record['items'][0]['quantity'], Decimal('10'))
        self.assertEqual(purchase_record['items'][0]['num_of_rolls'], 2)
        return_record = next(record for record in payload['records'] if record['source_type'] == 'purchase_return_order')
        self.assertEqual(return_record['human_id'], return_order.human_id)
        payment_record = next(record for record in payload['records'] if record['source_type'] == 'payment_order')
        self.assertEqual(payment_record['human_id'], payment_order.human_id)
        self.assertEqual(payment_record['negative_amount'], Decimal('15.00'))


class BusinessStatementRedFlushExclusionTestCase(TestCase):
    """对账单 builders 必须排除已红冲的正式单据。

    Bug 复核：红冲服务在保留单据 status=APPROVED 的同时把 is_red_flushed 置为 True。
    若 _build_*_records 仅过滤 status，已红冲单据会以原金额继续出现在对账单 records 中，
    导致 summary 的 positive_total/negative_total 把红冲金额多算一次。

    本测试类对客户侧 3 类与供应商侧 3 类单据各红冲一次后，断言：
      - 对应 source_type 不在 records 列表中
      - summary 的金额合计中没有该单金额
    """

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username='statement-red-flush', password='pass123')

    def _disable_auto_stock_tasks(self, merchant):
        basic_models.MerchantSetting.objects.filter(
            merchant=merchant,
            key=basic_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS,
        ).update(val_bool=False)

    def _complete_stock_record(self, record_id):
        record = stock_models.StockChangeRecord.objects.get(id=record_id)
        stock_services.make_stock_change_completed(record)
        return record

    def _create_and_complete_purchase_order(self, *, merchant, supplier, warehouse, operator, product, quantity, price):
        order = services.create_purchase_order(
            merchant=merchant,
            supplier=supplier,
            order_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': quantity, 'num_of_rolls': 1, 'price': str(price)}],
            remarks='采购对账',
        )
        services.review_purchase_order(
            purchase_order=order,
            target_status=business_models.PurchaseOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )
        payload = services.create_purchase_order_stock_entries_sync(
            purchase_order_id=order.id,
            warehouse_id=warehouse.id,
            items=services._build_stock_flow_items_from_order(order),
            created_by_id=self.user.id,
        )
        self._complete_stock_record(payload['stock_change_record_id'])
        return order

    # ============ 客户侧 ============

    def test_red_flushed_sales_order_excluded_from_customer_statement(self):
        merchant, customer, warehouse_strict, warehouse_relaxed, _, product, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        # 一个保留的销售单 + 一个会被红冲的销售单
        keep = services.create_sales_order(
            merchant=merchant,
            customer=customer,
            order_date=today,
            warehouse=warehouse_strict,
            operator=operator,
            items=[{'product_id': product.id, 'numbers': [6, 4], 'price': '10', 'unit': '米'}],
            remarks='保留销售单',
        )
        services.review_sales_order(
            sales_order=keep,
            target_status=business_models.SalesOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        to_flush = services.create_sales_order(
            merchant=merchant,
            customer=customer,
            order_date=today,
            warehouse=warehouse_strict,
            operator=operator,
            items=[{'product_id': product.id, 'numbers': [5, 5], 'price': '20', 'unit': '米'}],
            remarks='红冲销售单',
        )
        services.review_sales_order(
            sales_order=to_flush,
            target_status=business_models.SalesOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        services.red_flush_sales_order(
            merchant=merchant,
            sales_order=to_flush,
            red_flushed_by=self.user,
            reason='测试红冲销售单',
        )

        payload = services.build_customer_statement(merchant=merchant, customer=customer)
        sales_records = [r for r in payload['records'] if r['source_type'] == 'sales_order']
        self.assertEqual(len(sales_records), 1)
        self.assertEqual(sales_records[0]['source_id'], keep.id)

        summary = services.build_statement_summary(payload)
        # 保留单金额 = 10 * 10 = 100；若红冲单未排除，会再多 200 = 300
        self.assertEqual(summary['positive_total'], '100.00')

    def test_red_flushed_sales_return_order_excluded_from_customer_statement(self):
        merchant, customer, warehouse_strict, warehouse_relaxed, _, product, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        def _make_sales_return(quantity, price, remark):
            ret = services.create_sales_return_order(
                merchant=merchant,
                customer=customer,
                return_date=today,
                warehouse=warehouse_relaxed,
                operator=operator,
                items=[{'product_id': product.id, 'quantity': quantity, 'num_of_rolls': 1, 'price': str(price)}],
                remarks=remark,
            )
            services.review_sales_return_order(
                sales_return_order=ret,
                target_status=business_models.SalesReturnStatusEnum.APPROVED,
                reviewed_by=self.user,
            )
            sync_payload = services.create_sales_return_order_stock_entries_sync(
                sales_return_order_id=ret.id,
                warehouse_id=warehouse_relaxed.id,
                items=services._build_stock_flow_items_from_order(ret),
                created_by_id=self.user.id,
            )
            self._complete_stock_record(sync_payload['stock_change_record_id'])
            return ret

        keep = _make_sales_return(3, Decimal('10'), '保留销退')
        to_flush = _make_sales_return(2, Decimal('20'), '红冲销退')

        services.red_flush_sales_return_order(
            merchant=merchant,
            sales_return_order=to_flush,
            red_flushed_by=self.user,
            reason='测试红冲销退单',
        )

        payload = services.build_customer_statement(merchant=merchant, customer=customer)
        sr_records = [r for r in payload['records'] if r['source_type'] == 'sales_return_order']
        self.assertEqual(len(sr_records), 1)
        self.assertEqual(sr_records[0]['source_id'], keep.id)

        summary = services.build_statement_summary(payload)
        # 保留销退 negative=3*10=30；红冲若未排除会再多 2*20=40，合计 70
        self.assertEqual(summary['negative_total'], '30.00')

    def test_red_flushed_receipt_order_excluded_from_customer_statement(self):
        merchant, customer, _wh1, _wh2, _, _, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        keep = services.create_receipt_order(
            merchant=merchant,
            customer=customer,
            receipt_date=today,
            amount='40',
            operator=operator,
            remarks='保留收款',
        )
        services.review_receipt_order(
            receipt_order=keep,
            target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        to_flush = services.create_receipt_order(
            merchant=merchant,
            customer=customer,
            receipt_date=today,
            amount='25',
            operator=operator,
            remarks='红冲收款',
        )
        services.review_receipt_order(
            receipt_order=to_flush,
            target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        services.red_flush_receipt_order(
            merchant=merchant,
            receipt_order=to_flush,
            red_flushed_by=self.user,
            reason='测试红冲收款单',
        )

        payload = services.build_customer_statement(merchant=merchant, customer=customer)
        receipt_records = [r for r in payload['records'] if r['source_type'] == 'receipt_order']
        self.assertEqual(len(receipt_records), 1)
        self.assertEqual(receipt_records[0]['source_id'], keep.id)

        summary = services.build_statement_summary(payload)
        # 保留收款 negative=40；红冲未排除会再 +25 = 65
        self.assertEqual(summary['negative_total'], '40.00')

    # ============ 供应商侧 ============

    def test_red_flushed_purchase_order_excluded_from_supplier_statement(self):
        merchant, supplier, warehouse_strict, warehouse_relaxed, product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)

        keep = self._create_and_complete_purchase_order(
            merchant=merchant, supplier=supplier, warehouse=warehouse_relaxed,
            operator=operator, product=product, quantity=10, price=Decimal('5'),
        )
        to_flush = self._create_and_complete_purchase_order(
            merchant=merchant, supplier=supplier, warehouse=warehouse_relaxed,
            operator=operator, product=product, quantity=8, price=Decimal('7'),
        )

        services.red_flush_purchase_order(
            merchant=merchant,
            purchase_order=to_flush,
            red_flushed_by=self.user,
            reason='测试红冲采购单',
        )

        payload = services.build_supplier_statement(merchant=merchant, supplier=supplier)
        purchase_records = [r for r in payload['records'] if r['source_type'] == 'purchase_order']
        self.assertEqual(len(purchase_records), 1)
        self.assertEqual(purchase_records[0]['source_id'], keep.id)

        summary = services.build_statement_summary(payload)
        # 保留单 10*5=50；若红冲单未排除会再多 8*7=56，合计 106
        self.assertEqual(summary['positive_total'], '50.00')

    def test_red_flushed_purchase_return_order_excluded_from_supplier_statement(self):
        merchant, supplier, warehouse_strict, warehouse_relaxed, product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        # 先建一个采购单提供库存底盘，然后做两个退货单（一个保留 + 一个红冲）
        purchase = self._create_and_complete_purchase_order(
            merchant=merchant, supplier=supplier, warehouse=warehouse_relaxed,
            operator=operator, product=product, quantity=50, price=Decimal('5'),
        )

        def _make_return(quantity, price, remark):
            ret = services.create_purchase_return_order(
                merchant=merchant,
                supplier=supplier,
                return_date=today,
                warehouse=warehouse_relaxed,
                operator=operator,
                items=[{'product_id': product.id, 'quantity': quantity, 'num_of_rolls': 1, 'price': str(price)}],
                remarks=remark,
                purchase_order=purchase,
            )
            services.review_purchase_return_order(
                purchase_return_order=ret,
                target_status=business_models.PurchaseReturnStatusEnum.APPROVED,
                reviewed_by=self.user,
            )
            sync_payload = services.create_purchase_return_order_stock_entries_sync(
                purchase_return_order_id=ret.id,
                warehouse_id=warehouse_relaxed.id,
                items=services._build_stock_flow_items_from_order(ret),
                created_by_id=self.user.id,
            )
            self._complete_stock_record(sync_payload['stock_change_record_id'])
            return ret

        keep = _make_return(3, Decimal('5'), '保留采退')
        to_flush = _make_return(2, Decimal('7'), '红冲采退')

        services.red_flush_purchase_return_order(
            merchant=merchant,
            purchase_return_order=to_flush,
            red_flushed_by=self.user,
            reason='测试红冲采退单',
        )

        payload = services.build_supplier_statement(merchant=merchant, supplier=supplier)
        pr_records = [r for r in payload['records'] if r['source_type'] == 'purchase_return_order']
        self.assertEqual(len(pr_records), 1)
        self.assertEqual(pr_records[0]['source_id'], keep.id)

        summary = services.build_statement_summary(payload)
        # 保留采退 negative=3*5=15；若红冲未排除会再多 2*7=14，合计 29
        self.assertEqual(summary['negative_total'], '15.00')

    def test_red_flushed_payment_order_excluded_from_supplier_statement(self):
        merchant, supplier, _wh1, _wh2, _product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)
        today = timezone.now().date()

        keep = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=today,
            amount='30',
            operator=operator,
            remarks='保留付款',
        )
        services.review_payment_order(
            payment_order=keep,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        to_flush = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=today,
            amount='18',
            operator=operator,
            remarks='红冲付款',
        )
        services.review_payment_order(
            payment_order=to_flush,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        services.red_flush_payment_order(
            merchant=merchant,
            payment_order=to_flush,
            red_flushed_by=self.user,
            reason='测试红冲付款单',
        )

        payload = services.build_supplier_statement(merchant=merchant, supplier=supplier)
        payment_records = [r for r in payload['records'] if r['source_type'] == 'payment_order']
        self.assertEqual(len(payment_records), 1)
        self.assertEqual(payment_records[0]['source_id'], keep.id)

        summary = services.build_statement_summary(payload)
        # 保留付款 negative=30；若红冲未排除会再多 18 合计 48
        self.assertEqual(summary['negative_total'], '30.00')

