from decimal import Decimal
from io import StringIO
from unittest.mock import patch

from django.core.management import call_command
from django.test import TestCase, override_settings

from basic_info import models as basic_models
from business import models as business_models
from business.external_finance_sync import sync_customer_finance, sync_customer_finance_batch
from business.services import build_customer_statement, build_supplier_statement

from .fixtures import create_basic_fixtures


class FakeHaoBuYeClient:
    def __init__(self, *, finance_payloads=None, customers_payload=None, i_sale_customer_payloads=None):
        self.finance_payloads = finance_payloads or {}
        self.customers_payload = customers_payload or {
            'customers': [],
            'cursor_id': None,
            'next_cursor_id': None,
        }
        self.i_sale_customer_payloads = i_sale_customer_payloads or {}

    def fetch_customer_finance(self, *, customer_name, include_adjustments=True, include_cash_movement=True):
        return self.finance_payloads[customer_name]

    def list_customers(self, *, cursor_id=None):
        return self.customers_payload

    def fetch_i_sale_by_customer(self, *, customer_id, category):
        return self.i_sale_customer_payloads.get(
            (customer_id, category),
            {
                'status': 'not_found',
                'records': [],
            },
        )


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

    def test_sync_customer_finance_creates_receipt_and_refund_orders(self):
        client = FakeHaoBuYeClient(
            finance_payloads={
                '张晓鹏': {
                    'status': 'active',
                    'customer_name': '张晓鹏',
                    'customer_ids': ['KH00308'],
                    'receipts': [
                        {
                            'BianHaoID': 'SK20225267',
                            'KhID': 'KH00308',
                            'RiQi': '2026-05-07T00:00:00Z',
                            'FkJinE': '198708.00',
                            'ZkJinE': '12.00',
                            'JieSunFS': '月结',
                            'ZhaiYao': '正常收款',
                            'BeiZhu': '回款备注',
                        },
                        {
                            'BianHaoID': 'SK20208849',
                            'KhID': 'KH00308',
                            'RiQi': '2022-05-31T00:00:00Z',
                            'FkJinE': '0.00',
                            'ZkJinE': '151.00',
                            'JieSunFS': '月结',
                            'ZhaiYao': '纯折扣收款',
                            'BeiZhu': '',
                        },
                    ],
                    'refunds': [
                        {
                            'BianHaoID': 'XT20203479',
                            'KhID': 'KH00308',
                            'RiQi': '2025-11-25T00:00:00Z',
                            'YfJinE': '-490.00',
                            'FkJinE': '0.00',
                            'JieSunFS': '欠款',
                            'ZhaiYao': '成品销售退货单XT20203479[欠款]',
                            'BeiZhu': '',
                        }
                    ],
                }
            },
            i_sale_customer_payloads={
                (
                    'KH00308',
                    business_models.ExternalCustomerStatementCategoryEnum.SALE,
                ): {
                    'status': 'active',
                    'records': [
                        {
                            'SubID': 1,
                            'BianHaoID': 'XS20367431',
                            'DanType': '成品销售单',
                            'KhID': 'KH00308',
                            'RiQi': '2026-05-14T00:00:00Z',
                            'KdRiQi': '2026-05-14T19:08:23Z',
                            'JieSunFS': '欠款',
                            'HpID': 'HP00001',
                            'HpName': '外部同步品名',
                            'YanSe': '天蓝',
                            'SeHao': '150cm',
                            'JiJiaDW': '米',
                            'JianShu': '1.00',
                            'ShuLiang': '20.00',
                            'DanJia': '32.000000',
                            'JinE': '640.000000',
                            'BeiZhu': '外部销售备注',
                            'BeiZhuC': '',
                            'BeiZhuD': '',
                            'MeoD': '普通单据',
                        }
                    ],
                },
                (
                    'KH00308',
                    business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN,
                ): {
                    'status': 'active',
                    'records': [
                        {
                            'SubID': 2,
                            'BianHaoID': 'XT20203479',
                            'DanType': '客户退货单',
                            'KhID': 'KH00308',
                            'RiQi': '2025-11-25T00:00:00Z',
                            'KdRiQi': '2025-11-25T18:08:23Z',
                            'JieSunFS': '欠款',
                            'HpID': 'HP00001',
                            'HpName': '外部退货品名',
                            'YanSe': '米白',
                            'SeHao': '160cm',
                            'JiJiaDW': '米',
                            'JianShu': '1.00',
                            'ShuLiang': '5.00',
                            'DanJia': '98.000000',
                            'JinE': '490.000000',
                            'BeiZhu': '',
                            'BeiZhuC': '',
                            'BeiZhuD': '',
                            'MeoD': '退货单据',
                        }
                    ],
                },
            },
        )

        basic_models.Product.objects.create(
            merchant=self.merchant,
            category=basic_models.ProductCategory.objects.get(merchant=self.merchant),
            name='外部面料',
            human_id='HP00001',
            unit=basic_models.ProductUnitEnum.METER,
        )

        payload = sync_customer_finance(
            customer_name='张晓鹏',
            operator=self.operator,
            client=client,
            allow_create_customer=True,
        )

        self.assertEqual(payload['created_count'], 2)
        self.assertEqual(payload['skipped_existing_count'], 0)
        self.assertEqual(payload['skipped_zero_settlement_count'], 0)
        self.assertEqual(payload['external_business_created_count'], 2)
        self.assertEqual(payload['sales_seen'], 1)
        self.assertEqual(payload['sale_returns_seen'], 1)

        customer = basic_models.Customer.objects.get(merchant=self.merchant, name='张晓鹏')
        orders = list(
            business_models.ReceiptOrder.objects.filter(merchant=self.merchant, customer=customer).order_by('external_source_id')
        )
        self.assertEqual(len(orders), 2)
        by_external_id = {order.external_source_id: order for order in orders}

        self.assertEqual(by_external_id['SK20225267'].amount, Decimal('198708.00'))
        self.assertEqual(by_external_id['SK20225267'].discount_amount, Decimal('12.00'))
        self.assertEqual(by_external_id['SK20225267'].status, business_models.ReceiptOrderStatusEnum.APPROVED)

        self.assertEqual(by_external_id['SK20208849'].amount, Decimal('0.00'))
        self.assertEqual(by_external_id['SK20208849'].discount_amount, Decimal('151.00'))
        self.assertEqual(by_external_id['SK20208849'].settlement_amount, Decimal('151.00'))

        balance = business_models.CustomerBalance.objects.get(merchant=self.merchant, customer=customer)
        self.assertEqual(balance.balance, Decimal('-198871.00'))

        external_orders = list(
            business_models.ExternalCustomerStatementOrder.objects.filter(
                merchant=self.merchant,
                customer=customer,
            ).order_by('category', 'external_source_id')
        )
        self.assertEqual(len(external_orders), 2)
        self.assertEqual(external_orders[0].total_amount, Decimal('640.00'))
        self.assertEqual(external_orders[1].total_amount, Decimal('490.00'))
        self.assertEqual(external_orders[0].items_payload[0]['product_id'], None)
        self.assertEqual(external_orders[0].items_payload[0]['product_name'], '外部同步品名')
        self.assertEqual(external_orders[0].items_payload[0]['color'], '天蓝')
        self.assertEqual(external_orders[0].items_payload[0]['spec'], '150cm')
        self.assertEqual(external_orders[0].items_payload[0]['external_product_id'], 'HP00001')

        statement_payload = build_customer_statement(merchant=self.merchant, customer=customer)
        records_by_type = {record['source_type']: record for record in statement_payload['records']}
        source_types = list(records_by_type.keys())
        self.assertIn('external_sales_order', source_types)
        self.assertIn('external_sales_return_order', source_types)
        # 对账单 records_by_type 是 dict，同一 source_type 只保留最后一个（按日期倒序排列后字典覆盖）
        # 收款单 remarks 由 _build_external_remarks 构建，包含元信息而非原始摘要
        receipt_remarks = records_by_type['receipt_order']['remarks']
        self.assertIn('外部财务同步: receipt', receipt_remarks)
        self.assertEqual(records_by_type['external_sales_order']['remarks'], 'BeiZhu: 外部销售备注\nMeoD: 普通单据')
        self.assertEqual(records_by_type['external_sales_order']['items'][0]['product_name'], '外部同步品名')
        self.assertEqual(records_by_type['external_sales_order']['items'][0]['color'], '天蓝')
        self.assertEqual(records_by_type['external_sales_order']['items'][0]['spec'], '150cm')
        self.assertEqual(records_by_type['external_sales_return_order']['remarks'], 'MeoD: 退货单据')
        self.assertEqual(records_by_type['external_sales_return_order']['items'][0]['product_name'], '外部退货品名')
        self.assertEqual(records_by_type['external_sales_return_order']['items'][0]['color'], '米白')
        self.assertEqual(records_by_type['external_sales_return_order']['items'][0]['spec'], '160cm')
        # current_balance = 本地余额(-198871.00) + 外部业务净额(销售 +640 - 退货 490 = +150) = -198721.00
        self.assertEqual(statement_payload['records'][0]['current_balance'], '-198721.00')

    def test_build_supplier_statement_records_always_include_remarks(self):
        purchase_order = business_models.PurchaseOrder.objects.create(
            merchant=self.merchant,
            supplier=self.supplier,
            purchase_date='2026-05-01',
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            status=business_models.PurchaseOrderStatusEnum.APPROVED,
            remarks='采购备注',
        )
        purchase_return_order = business_models.PurchaseReturnOrder.objects.create(
            merchant=self.merchant,
            supplier=self.supplier,
            return_date='2026-05-02',
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            purchase_order=purchase_order,
            status=business_models.PurchaseReturnStatusEnum.APPROVED,
            remarks='退货备注',
        )
        business_models.PaymentOrder.objects.create(
            merchant=self.merchant,
            supplier=self.supplier,
            payment_date='2026-05-03',
            amount=Decimal('100.00'),
            discount_amount=Decimal('0.00'),
            operator=self.operator,
            status=business_models.PaymentOrderStatusEnum.APPROVED,
            remarks='付款备注',
        )

        statement_payload = build_supplier_statement(merchant=self.merchant, supplier=self.supplier)

        records_by_type = {record['source_type']: record for record in statement_payload['records']}
        self.assertEqual(records_by_type['purchase_order']['remarks'], '采购备注')
        self.assertEqual(records_by_type['purchase_return_order']['remarks'], '退货备注')
        self.assertEqual(records_by_type['payment_order']['remarks'], '付款备注')
        self.assertTrue(all('remarks' in record for record in statement_payload['records']))

    def test_sync_customer_finance_is_idempotent_for_same_external_source_id(self):
        customer = basic_models.Customer.objects.create(
            merchant=self.merchant,
            name='宏洋',
            created_by=self.operator,
        )
        business_models.ReceiptOrder.objects.create(
            merchant=self.merchant,
            customer=customer,
            receipt_date='2026-05-09',
            amount=Decimal('37839.00'),
            discount_amount=Decimal('0.00'),
            operator=self.operator,
            status=business_models.ReceiptOrderStatusEnum.APPROVED,
            is_external_source=True,
            external_source_id='SK20225323',
            remarks='existing',
        )

        client = FakeHaoBuYeClient(
            finance_payloads={
                '宏洋': {
                    'status': 'active',
                    'customer_name': '宏洋',
                    'customer_ids': ['KH01474'],
                    'receipts': [
                        {
                            'BianHaoID': 'SK20225323',
                            'KhID': 'KH01474',
                            'RiQi': '2026-05-09T00:00:00Z',
                            'FkJinE': '37839.00',
                            'ZkJinE': '0.00',
                            'JieSunFS': '月结',
                            'ZhaiYao': '',
                            'BeiZhu': '',
                        }
                    ],
                    'refunds': [],
                }
            }
        )

        payload = sync_customer_finance(
            customer_name='宏洋',
            operator=self.operator,
            client=client,
            allow_create_customer=False,
        )

        self.assertEqual(payload['created_count'], 0)
        self.assertEqual(payload['skipped_existing_count'], 1)
        self.assertEqual(payload['external_business_created_count'], 0)
        self.assertEqual(
            business_models.ReceiptOrder.objects.filter(merchant=self.merchant, external_source_id='SK20225323').count(),
            1,
        )

    def test_sync_customer_finance_batch_reuses_single_customer_sync_flow(self):
        client = FakeHaoBuYeClient(
            customers_payload={
                'cursor_id': 'KH00001',
                'next_cursor_id': 'KH00011',
                'customers': [
                    {'customer_id': 'KH00002', 'customer_name': '客户甲'},
                    {'customer_id': 'KH00003', 'customer_name': '客户乙'},
                ],
            },
            finance_payloads={
                '客户甲': {
                    'status': 'active',
                    'customer_name': '客户甲',
                    'customer_ids': ['KH00002'],
                    'receipts': [],
                    'refunds': [],
                },
                '客户乙': {
                    'status': 'active',
                    'customer_name': '客户乙',
                    'customer_ids': ['KH00003'],
                    'receipts': [
                        {
                            'BianHaoID': 'SK-TEST-1',
                            'KhID': 'KH00003',
                            'RiQi': '2026-05-01T00:00:00Z',
                            'FkJinE': '100.00',
                            'ZkJinE': '0.00',
                            'JieSunFS': '现金',
                            'ZhaiYao': '',
                            'BeiZhu': '',
                        }
                    ],
                    'refunds': [],
                },
            },
        )

        payload = sync_customer_finance_batch(
            operator=self.operator,
            cursor_id='KH00001',
            client=client,
            allow_create_customer=True,
        )

        self.assertEqual(payload['count'], 2)
        self.assertEqual(payload['next_cursor_id'], 'KH00011')
        self.assertEqual(payload['results'][0]['customer_name'], '客户甲')
        self.assertEqual(payload['results'][1]['created_count'], 1)


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

    @override_settings(HAOBUYE_FINANCE_SYNC_OPERATOR_ID=0)
    def test_command_sync_external_customer_finance(self):
        stdout = StringIO()
        with patch(
            'business.management.commands.sync_external_customer_finance.sync_customer_finance',
            return_value={'customer_name': '宏洋', 'created_count': 2},
        ) as mock_sync:
            call_command(
                'sync_external_customer_finance',
                '宏洋',
                '--operator-id',
                str(self.operator.id),
                '--allow-create-customer',
                stdout=stdout,
            )

        mock_sync.assert_called_once()
        self.assertIn('created_count', stdout.getvalue())
