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 BusinessRedFlushServiceTestCase(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username='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 _assert_balance_red_flushed(self, *, source_type, source_id, red_flush_id):
        records = business_models.BalanceChangeRecord.objects.filter(
            source_type=source_type,
            source_id=source_id,
        ).order_by('id')
        self.assertEqual(records.count(), 2)
        original = records[0]
        reverse = records[1]
        self.assertTrue(original.cancelled)
        self.assertEqual(original.offset_id, reverse.id)
        self.assertEqual(original.red_flush_id, red_flush_id)
        self.assertEqual(reverse.offset_to, original.id)
        self.assertEqual(reverse.red_flush_id, red_flush_id)
        self.assertEqual(reverse.delta, -original.delta)
        return original, reverse

    def _assert_stock_red_flushed(self, *, source_type, source_id, red_flush_id):
        original = stock_models.StockChangeRecord.objects.get(
            source_type=source_type,
            source_id=source_id,
        )
        reverse = stock_models.StockChangeRecord.objects.get(
            source_type=stock_models.StockChangeSourceEnum.OFFSET,
            source_id=original.id,
        )
        self.assertEqual(original.red_flush_id, red_flush_id)
        self.assertEqual(reverse.red_flush_id, red_flush_id)
        self.assertTrue(original.is_reversed)
        self.assertTrue(reverse.is_offset)
        return original, reverse

    def test_red_flush_purchase_order_reverses_balance_and_stock(self):
        merchant, supplier, _, warehouse, product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)
        order = services.create_purchase_order(
            merchant=merchant,
            supplier=supplier,
            order_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 40, 'num_of_rolls': 2, 'price': '10'}],
        )
        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'])

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

        self.assertEqual(flushed.status, business_models.PurchaseOrderStatusEnum.APPROVED)
        self.assertTrue(flushed.is_red_flushed)
        self.assertIsNotNone(flushed.red_flush_id)
        balance = business_models.SupplierBalance.objects.get(merchant=merchant, supplier=supplier)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.PURCHASE_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self._assert_stock_red_flushed(
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )

    def test_red_flush_sales_order_reverses_balance_and_stock(self):
        merchant, customer, _, warehouse, _, product, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        order = services.create_sales_order(
            merchant=merchant,
            customer=customer,
            order_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 20, 'num_of_rolls': 1, 'price': '15'}],
        )
        services.review_sales_order(
            sales_order=order,
            target_status=business_models.SalesOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )
        payload = services.create_sales_order_stock_entries_sync(
            sales_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'])

        flushed = services.red_flush_sales_order(merchant=merchant, sales_order=order, red_flushed_by=self.user)

        self.assertTrue(flushed.is_red_flushed)
        balance = business_models.CustomerBalance.objects.get(merchant=merchant, customer=customer)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.SALES_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self._assert_stock_red_flushed(
            source_type=stock_models.StockChangeSourceEnum.SALES,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )

    def test_red_flush_sales_order_without_stock_reverses_balance_only(self):
        merchant, customer, _, warehouse, _, product, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        order = services.create_sales_order(
            merchant=merchant,
            customer=customer,
            order_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 20, 'num_of_rolls': 1, 'price': '15'}],
        )
        services.review_sales_order(
            sales_order=order,
            target_status=business_models.SalesOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        flushed = services.red_flush_sales_order(merchant=merchant, sales_order=order, red_flushed_by=self.user)

        self.assertTrue(flushed.is_red_flushed)
        balance = business_models.CustomerBalance.objects.get(merchant=merchant, customer=customer)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.SALES_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self.assertFalse(
            stock_models.StockChangeRecord.objects.filter(red_flush_id=flushed.red_flush_id).exists()
        )

    def test_red_flush_purchase_return_order_reverses_balance_and_stock(self):
        merchant, supplier, _, warehouse, product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)
        order = services.create_purchase_return_order(
            merchant=merchant,
            supplier=supplier,
            return_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 12, 'num_of_rolls': 1, 'price': '8'}],
        )
        services.review_purchase_return_order(
            purchase_return_order=order,
            target_status=business_models.PurchaseReturnStatusEnum.APPROVED,
            reviewed_by=self.user,
        )
        payload = services.create_purchase_return_order_stock_entries_sync(
            purchase_return_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'])

        flushed = services.red_flush_purchase_return_order(
            merchant=merchant,
            purchase_return_order=order,
            red_flushed_by=self.user,
        )

        self.assertTrue(flushed.is_red_flushed)
        balance = business_models.SupplierBalance.objects.get(merchant=merchant, supplier=supplier)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.PURCHASE_RETURN_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self._assert_stock_red_flushed(
            source_type=stock_models.StockChangeSourceEnum.PURCHASE_RETURN,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )

    def test_red_flush_sales_return_order_reverses_balance_and_stock(self):
        merchant, customer, _, warehouse, _, product, operator = create_sales_fixtures()
        self._disable_auto_stock_tasks(merchant)
        order = services.create_sales_return_order(
            merchant=merchant,
            customer=customer,
            return_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 12, 'num_of_rolls': 1, 'price': '8'}],
        )
        services.review_sales_return_order(
            sales_return_order=order,
            target_status=business_models.SalesReturnStatusEnum.APPROVED,
            reviewed_by=self.user,
        )
        payload = services.create_sales_return_order_stock_entries_sync(
            sales_return_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'])

        flushed = services.red_flush_sales_return_order(
            merchant=merchant,
            sales_return_order=order,
            red_flushed_by=self.user,
        )

        self.assertTrue(flushed.is_red_flushed)
        balance = business_models.CustomerBalance.objects.get(merchant=merchant, customer=customer)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.SALES_RETURN_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self._assert_stock_red_flushed(
            source_type=stock_models.StockChangeSourceEnum.SALES_RETURN,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )

    def test_red_flush_payment_order_reverses_balance_only(self):
        merchant, supplier, _, _, _, operator = create_basic_fixtures()
        order = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=timezone.now().date(),
            amount='30',
            operator=operator,
        )
        services.review_payment_order(
            payment_order=order,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        flushed = services.red_flush_payment_order(merchant=merchant, payment_order=order, red_flushed_by=self.user)

        self.assertTrue(flushed.is_red_flushed)
        balance = business_models.SupplierBalance.objects.get(merchant=merchant, supplier=supplier)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.PAYMENT_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self.assertFalse(stock_models.StockChangeRecord.objects.filter(red_flush_id=flushed.red_flush_id).exists())

    def test_red_flush_receipt_order_reverses_balance_only(self):
        merchant, _, _, _, _, operator = create_basic_fixtures()
        customer = basic_models.Customer.objects.create(merchant=merchant, name='红冲客户')
        order = services.create_receipt_order(
            merchant=merchant,
            customer=customer,
            receipt_date=timezone.now().date(),
            amount='45',
            operator=operator,
        )
        services.review_receipt_order(
            receipt_order=order,
            target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        flushed = services.red_flush_receipt_order(merchant=merchant, receipt_order=order, red_flushed_by=self.user)

        self.assertTrue(flushed.is_red_flushed)
        balance = business_models.CustomerBalance.objects.get(merchant=merchant, customer=customer)
        self.assertEqual(balance.balance, Decimal('0.00'))
        self._assert_balance_red_flushed(
            source_type=business_models.BalanceChangeSourceEnum.RECEIPT_ORDER,
            source_id=order.id,
            red_flush_id=flushed.red_flush_id,
        )
        self.assertFalse(stock_models.StockChangeRecord.objects.filter(red_flush_id=flushed.red_flush_id).exists())

    def test_red_flush_rejects_external_payment_order(self):
        merchant, supplier, _, _, _, operator = create_basic_fixtures()
        order = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=timezone.now().date(),
            amount='30',
            operator=operator,
        )
        order.is_external_source = True
        order.external_source_id = 'XT-EXTERNAL-001'
        order.save(update_fields=['is_external_source', 'external_source_id', 'updated_at'])
        services.review_payment_order(
            payment_order=order,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        with self.assertRaisesMessage(ValueError, '外部来源付款单不允许红冲'):
            services.red_flush_payment_order(merchant=merchant, payment_order=order, red_flushed_by=self.user)

        order.refresh_from_db()
        self.assertFalse(order.is_red_flushed)

    def test_red_flush_rejects_external_receipt_order(self):
        merchant, _, _, _, _, operator = create_basic_fixtures()
        customer = basic_models.Customer.objects.create(merchant=merchant, name='外部红冲客户')
        order = services.create_receipt_order(
            merchant=merchant,
            customer=customer,
            receipt_date=timezone.now().date(),
            amount='45',
            operator=operator,
        )
        order.external_source_id = 'SK-EXTERNAL-001'
        order.save(update_fields=['external_source_id', 'updated_at'])
        services.review_receipt_order(
            receipt_order=order,
            target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
            reviewed_by=self.user,
        )

        with self.assertRaisesMessage(ValueError, '外部来源收款单不允许红冲'):
            services.red_flush_receipt_order(merchant=merchant, receipt_order=order, red_flushed_by=self.user)

        order.refresh_from_db()
        self.assertFalse(order.is_red_flushed)

    def test_red_flush_rejects_non_approved_order(self):
        merchant, supplier, _, warehouse, product, operator = create_basic_fixtures()
        order = services.create_purchase_order(
            merchant=merchant,
            supplier=supplier,
            order_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 10, 'num_of_rolls': 1, 'price': '5'}],
        )

        with self.assertRaises(ValueError):
            services.red_flush_purchase_order(merchant=merchant, purchase_order=order, red_flushed_by=self.user)

    def test_red_flush_rejects_repeated_order(self):
        merchant, supplier, _, _, _, operator = create_basic_fixtures()
        order = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=timezone.now().date(),
            amount='10',
            operator=operator,
        )
        services.review_payment_order(
            payment_order=order,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
        )
        services.red_flush_payment_order(merchant=merchant, payment_order=order, red_flushed_by=self.user)

        with self.assertRaises(ValueError):
            services.red_flush_payment_order(merchant=merchant, payment_order=order, red_flushed_by=self.user)

    def test_red_flush_rejects_missing_balance_record(self):
        merchant, supplier, _, _, _, operator = create_basic_fixtures()
        order = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=timezone.now().date(),
            amount='10',
            operator=operator,
        )
        order.status = business_models.PaymentOrderStatusEnum.APPROVED
        order.save(update_fields=['status'])

        with self.assertRaises(ValueError):
            services.red_flush_payment_order(merchant=merchant, payment_order=order, red_flushed_by=self.user)

    def test_red_flush_rolls_back_balance_when_stock_missing(self):
        merchant, supplier, _, warehouse, product, operator = create_basic_fixtures()
        self._disable_auto_stock_tasks(merchant)
        order = services.create_purchase_order(
            merchant=merchant,
            supplier=supplier,
            order_date=timezone.now().date(),
            warehouse=warehouse,
            operator=operator,
            items=[{'product_id': product.id, 'quantity': 10, 'num_of_rolls': 1, 'price': '5'}],
        )
        services.review_purchase_order(
            purchase_order=order,
            target_status=business_models.PurchaseOrderStatusEnum.APPROVED,
        )

        with self.assertRaises(ValueError):
            services.red_flush_purchase_order(merchant=merchant, purchase_order=order, red_flushed_by=self.user)

        order.refresh_from_db()
        self.assertFalse(order.is_red_flushed)
        balance_records = business_models.BalanceChangeRecord.objects.filter(
            source_type=business_models.BalanceChangeSourceEnum.PURCHASE_ORDER,
            source_id=order.id,
        )
        self.assertEqual(balance_records.count(), 1)
        self.assertFalse(balance_records.first().cancelled)

    def test_red_flush_rejects_cross_merchant_order(self):
        merchant, supplier, _, _, _, operator = create_basic_fixtures()
        other_merchant = basic_models.Merchant.objects.create(
            name='其他商户',
            type=basic_models.MerchantTypeEnum.FACTORY,
        )
        order = services.create_payment_order(
            merchant=merchant,
            supplier=supplier,
            payment_date=timezone.now().date(),
            amount='10',
            operator=operator,
        )
        services.review_payment_order(
            payment_order=order,
            target_status=business_models.PaymentOrderStatusEnum.APPROVED,
        )

        with self.assertRaises(ValueError):
            services.red_flush_payment_order(
                merchant=other_merchant,
                payment_order_id=order.id,
                red_flushed_by=self.user,
            )
