from __future__ import annotations

import base64
import logging
from dataclasses import dataclass
from datetime import date, datetime
from decimal import Decimal, InvalidOperation
from typing import Any, Callable, Iterable

import requests
from django.conf import settings
from django.db import transaction

from basic_info import models as basic_models
from . import models as business_models
from . import services as business_services
from .amounts import calculate_settlement_amount, normalize_money_amount


logger = logging.getLogger(__name__)


class ExternalFinanceSyncError(RuntimeError):
    pass


class ExternalFinanceSyncConflictError(ExternalFinanceSyncError):
    pass


@dataclass(slots=True)
class ExternalCustomerRef:
    customer_id: str
    customer_name: str


class HaoBuYeFinanceClient:
    def __init__(
        self,
        *,
        base_url: str | None = None,
        authorization: str | None = None,
        timeout_seconds: float | None = None,
        session: requests.Session | None = None,
    ):
        self.base_url = str(base_url or getattr(settings, 'HAOBUYE_API_BASE_URL', '') or '').rstrip('/')
        self.authorization = str(
            authorization or getattr(settings, 'HAOBUYE_API_AUTHORIZATION', '') or ''
        ).strip()
        self.timeout_seconds = float(
            timeout_seconds
            if timeout_seconds is not None
            else getattr(settings, 'HAOBUYE_API_TIMEOUT_SECONDS', 30.0)
        )
        self.session = session or requests.Session()
        if not self.base_url:
            raise ExternalFinanceSyncError('未配置 HAOBUYE_API_BASE_URL')
        if not self.authorization:
            raise ExternalFinanceSyncError('未配置 HAOBUYE_API_AUTHORIZATION')

    def list_customers(self, *, cursor_id: str | None = None) -> dict[str, Any]:
        params: dict[str, str] = {}
        if cursor_id:
            params['cursor_id'] = str(cursor_id).strip()
        payload = self._request_json('/api/v1/customers', params=params)
        if not isinstance(payload.get('customers', []), list):
            raise ExternalFinanceSyncError('外部 customers 接口返回格式非法：customers 不是数组')
        return payload

    def fetch_customer_finance(
        self,
        *,
        customer_name: str,
        include_adjustments: bool = True,
        include_cash_movement: bool = True,
    ) -> dict[str, Any]:
        normalized_name = str(customer_name or '').strip()
        if not normalized_name:
            raise ExternalFinanceSyncError('customer_name 不能为空')
        customer_name_b64 = base64.urlsafe_b64encode(normalized_name.encode('utf-8')).decode('ascii').rstrip('=')
        payload = self._request_json(
            '/api/v1/finance/by-customer',
            params={
                'customer_name_b64': customer_name_b64,
                'record_types': 'receipt,refund,sale_discount',
                'include_cash_movement': str(include_cash_movement).lower(),
                'include_adjustments': str(include_adjustments).lower(),
            },
        )
        return payload

    def fetch_i_sale_by_customer(
        self,
        *,
        customer_id: str,
        category: str,
    ) -> dict[str, Any]:
        normalized_customer_id = str(customer_id or '').strip()
        normalized_category = str(category or '').strip()
        if not normalized_customer_id:
            raise ExternalFinanceSyncError('customer_id 不能为空')
        if normalized_category not in {
            business_models.ExternalCustomerStatementCategoryEnum.SALE,
            business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN,
        }:
            raise ExternalFinanceSyncError(f'不支持的 i_sale category: {normalized_category}')
        return self._request_json(
            '/api/v1/i-sale/by-customer',
            params={
                'customer_id': normalized_customer_id,
                'category': normalized_category,
            },
        )

    def _request_json(self, path: str, *, params: dict[str, str] | None = None) -> dict[str, Any]:
        url = f'{self.base_url}{path}'
        try:
            response = self.session.get(
                url,
                headers={'Authorization': self.authorization},
                params=params,
                timeout=self.timeout_seconds,
            )
        except requests.RequestException as exc:
            raise ExternalFinanceSyncError(f'请求外部接口失败: {exc}') from exc
        if response.status_code >= 400:
            try:
                payload = response.json()
            except ValueError:
                payload = {}
            # 404 且返回了 status=not_found 的 JSON 视为正常业务响应（如客户无退货单），
            # 由调用方根据 status 字段决定后续逻辑，而不是在此处抛异常中断整个同步。
            if response.status_code == 404 and isinstance(payload, dict) and payload.get('status') == 'not_found':
                return payload
            message = payload.get('message') or payload.get('error') or f'外部接口异常状态码: {response.status_code}'
            raise ExternalFinanceSyncError(str(message))
        try:
            payload = response.json()
        except ValueError as exc:
            raise ExternalFinanceSyncError('外部接口返回格式非法：非 JSON object') from exc
        if not isinstance(payload, dict):
            raise ExternalFinanceSyncError('外部接口返回格式非法：非 JSON object')
        return payload


def sync_customer_finance(
    *,
    customer_name: str,
    operator: basic_models.Employee,
    client: HaoBuYeFinanceClient | None = None,
    allow_create_customer: bool = True,
    dry_run: bool = False,
    force_update: bool = False,
    include_adjustments: bool = True,
    include_cash_movement: bool = True,
    progress_callback: Callable[[str], None] | None = None,
    progress_every: int = 200,
) -> dict[str, Any]:
    merchant = _ensure_active_operator(operator)
    _emit_progress(progress_callback, f'开始同步客户: {customer_name}')
    finance_client = client or HaoBuYeFinanceClient()
    payload = finance_client.fetch_customer_finance(
        customer_name=customer_name,
        include_adjustments=include_adjustments,
        include_cash_movement=include_cash_movement,
    )

    status_value = str(payload.get('status') or '').strip()
    normalized_name = str(payload.get('customer_name') or customer_name or '').strip()
    external_customer_id = _extract_external_customer_id(payload)

    summary: dict[str, Any] = {
        'customer_name': normalized_name,
        'external_customer_id': external_customer_id,
        'status': status_value or 'active',
        'receipts_seen': 0,
        'refunds_seen': 0,
        'sales_seen': 0,
        'sale_returns_seen': 0,
        'created_count': 0,
        'skipped_existing_count': 0,
        'skipped_zero_settlement_count': 0,
        'external_business_created_count': 0,
        'external_business_skipped_existing_count': 0,
        'dry_run_count': 0,
        'created_receipt_ids': [],
        'created_external_business_ids': [],
        'skipped_external_ids': [],
    }

    if status_value == 'not_found':
        return summary

    customer = _find_or_create_customer(
        merchant=merchant,
        customer_name=normalized_name,
        external_customer_id=external_customer_id,
        operator=operator,
        allow_create_customer=allow_create_customer,
        dry_run=dry_run,
    )

    receipts = payload.get('receipts') or []
    refunds = payload.get('refunds') or []
    if not isinstance(receipts, list) or not isinstance(refunds, list):
        raise ExternalFinanceSyncError('外部 finance 接口返回格式非法：receipts/refunds 不是数组')

    summary['receipts_seen'] = len(receipts)
    summary['refunds_seen'] = len(refunds)
    _emit_progress(
        progress_callback,
        f'财务记录已获取: receipts={summary["receipts_seen"]}, refunds={summary["refunds_seen"]}',
    )

    for index, record in enumerate(receipts, start=1):
        result = _sync_external_receipt_record(
            merchant=merchant,
            customer=customer,
            operator=operator,
            record=record,
            record_kind='receipt',
            dry_run=dry_run,
        )
        _merge_sync_result(summary, result)
        if index == 1 or index % progress_every == 0 or index == len(receipts):
            _emit_progress(
                progress_callback,
                f'收款同步进度: {index}/{len(receipts)}',
            )

    # NOTE: refunds (XT% 退款记录) 不再创建 ReceiptOrder。
    # 退货的减欠效果完全由 ExternalCustomerStatementOrder(category=sale_return) 承担。
    # 之前的实现会把 refund 同时写成 ReceiptOrder，导致退货金额被双重计入，
    # 与 ERP 对账结果不一致（详见 docs/DEBT_ANALYSIS_SUMMARY_2026-05-18.md 第四节）。
    if refunds:
        _emit_progress(
            progress_callback,
            f'跳过 refunds 桶（{len(refunds)} 条），退货效果由 sale_return 业务依据承担',
        )

    if external_customer_id:
        _emit_progress(progress_callback, f'开始同步外部业务依据: customer_id={external_customer_id}, category=sale')
        sales_result = sync_customer_external_statement_orders(
            customer=customer,
            external_customer_id=external_customer_id,
            operator=operator,
            client=finance_client,
            category=business_models.ExternalCustomerStatementCategoryEnum.SALE,
            dry_run=dry_run,
            force_update=force_update,
            progress_callback=progress_callback,
            progress_every=progress_every,
        )
        _merge_external_business_sync_result(summary, sales_result)

        _emit_progress(progress_callback, f'开始同步外部业务依据: customer_id={external_customer_id}, category=sale_return')
        sale_return_result = sync_customer_external_statement_orders(
            customer=customer,
            external_customer_id=external_customer_id,
            operator=operator,
            client=finance_client,
            category=business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN,
            dry_run=dry_run,
            force_update=force_update,
            progress_callback=progress_callback,
            progress_every=progress_every,
        )
        _merge_external_business_sync_result(summary, sale_return_result)

    # 处理 sale_discount 数据：从 F_Skd 的 XS% 行获取真实的销售折扣 ZkJinE，
    # 更新到对应的 ExternalCustomerStatementOrder.zk_amount 中。
    # I_Sale 表的 ZkJinE 始终为 0，真实折扣只存在于 F_Skd 表。
    sale_discounts = payload.get('sale_discounts') or []
    if sale_discounts and isinstance(sale_discounts, list) and customer:
        _apply_sale_discount_zk(
            merchant=merchant,
            customer=customer,
            sale_discounts=sale_discounts,
            progress_callback=progress_callback,
        )

    _emit_progress(progress_callback, f'客户同步完成: {customer_name}')
    return summary


def sync_customer_external_statement_orders(
    *,
    customer: basic_models.Customer,
    external_customer_id: str,
    operator: basic_models.Employee,
    client: HaoBuYeFinanceClient | None = None,
    category: str,
    dry_run: bool = False,
    force_update: bool = False,
    progress_callback: Callable[[str], None] | None = None,
    progress_every: int = 200,
) -> dict[str, Any]:
    merchant = _ensure_active_operator(operator)
    if customer.merchant_id != merchant.id:
        raise ExternalFinanceSyncError('customer 与 operator 不属于同一 merchant')
    finance_client = client or HaoBuYeFinanceClient()
    payload = finance_client.fetch_i_sale_by_customer(customer_id=external_customer_id, category=category)
    status_value = str(payload.get('status') or '').strip()
    records = payload.get('records') or []
    if not isinstance(records, list):
        raise ExternalFinanceSyncError('外部 i_sale by customer 接口返回格式非法：records 不是数组')

    summary = {
        'category': category,
        'seen_count': len(records),
        'created_count': 0,
        'skipped_existing_count': 0,
        'created_ids': [],
    }
    if status_value == 'not_found':
        _emit_progress(progress_callback, f'外部业务依据未命中: category={category}, customer_id={external_customer_id}')
        return summary

    grouped_records = _group_external_i_sale_records(records)
    summary['seen_count'] = len(grouped_records)
    _emit_progress(
        progress_callback,
        f'外部业务依据已获取: category={category}, raw_records={len(records)}, grouped_orders={summary["seen_count"]}',
    )
    for index, (external_source_id, grouped) in enumerate(grouped_records.items(), start=1):
        result = _sync_external_statement_order_group(
            merchant=merchant,
            customer=customer,
            external_customer_id=external_customer_id,
            external_source_id=external_source_id,
            category=category,
            grouped_records=grouped,
            dry_run=dry_run,
            force_update=force_update,
        )
        summary['created_count'] += result['created_count']
        summary['skipped_existing_count'] += result['skipped_existing_count']
        summary['created_ids'].extend(result['created_ids'])
        if index == 1 or index % progress_every == 0 or index == len(grouped_records):
            _emit_progress(
                progress_callback,
                (
                    f'外部业务依据同步进度: category={category}, {index}/{len(grouped_records)}, '
                    f'created={summary["created_count"]}, skipped={summary["skipped_existing_count"]}'
                ),
            )
    return summary


def sync_customer_finance_batch(
    *,
    operator: basic_models.Employee,
    cursor_id: str | None = None,
    client: HaoBuYeFinanceClient | None = None,
    allow_create_customer: bool = True,
    dry_run: bool = False,
    include_adjustments: bool = True,
    include_cash_movement: bool = True,
) -> dict[str, Any]:
    _ensure_active_operator(operator)
    finance_client = client or HaoBuYeFinanceClient()
    customers_payload = finance_client.list_customers(cursor_id=cursor_id)
    raw_customers = customers_payload.get('customers') or []
    customer_refs = [
        ExternalCustomerRef(
            customer_id=str(item.get('customer_id') or '').strip(),
            customer_name=str(item.get('customer_name') or '').strip(),
        )
        for item in raw_customers
        if str(item.get('customer_name') or '').strip()
    ]

    results = []
    for customer_ref in customer_refs:
        results.append(
            sync_customer_finance(
                customer_name=customer_ref.customer_name,
                operator=operator,
                client=finance_client,
                allow_create_customer=allow_create_customer,
                dry_run=dry_run,
                include_adjustments=include_adjustments,
                include_cash_movement=include_cash_movement,
            )
        )

    return {
        'mode': 'batch_customer_finance',
        'cursor_id': customers_payload.get('cursor_id'),
        'next_cursor_id': customers_payload.get('next_cursor_id'),
        'count': len(customer_refs),
        'results': results,
    }


def _ensure_active_operator(operator: basic_models.Employee) -> basic_models.Merchant:
    if not operator:
        raise ExternalFinanceSyncError('operator 不能为空')
    if operator.status != basic_models.EmployeeStatusEnum.ACTIVE:
        raise ExternalFinanceSyncError('operator 必须为在职员工')
    if not operator.merchant_id:
        raise ExternalFinanceSyncError('operator 未绑定 merchant')
    return operator.merchant


def _extract_external_customer_id(payload: dict[str, Any]) -> str:
    customer_ids = payload.get('customer_ids') or []
    if isinstance(customer_ids, list) and customer_ids:
        return str(customer_ids[0] or '').strip()
    return ''


def _find_or_create_customer(
    *,
    merchant: basic_models.Merchant,
    customer_name: str,
    external_customer_id: str,
    operator: basic_models.Employee,
    allow_create_customer: bool,
    dry_run: bool,
):
    customer = basic_models.Customer.objects.filter(
        merchant=merchant,
        name=customer_name,
    ).order_by('id').first()
    if customer:
        return customer
    if not allow_create_customer:
        raise ExternalFinanceSyncError(f'客户不存在且未允许自动创建: {customer_name}')
    if dry_run:
        return basic_models.Customer(
            merchant=merchant,
            name=customer_name,
            created_by=operator,
            description=f'外部客户ID: {external_customer_id}' if external_customer_id else '',
        )
    return basic_models.Customer.objects.create(
        merchant=merchant,
        name=customer_name,
        created_by=operator,
        description=f'外部客户ID: {external_customer_id}' if external_customer_id else '',
    )


def _sync_external_receipt_record(
    *,
    merchant: basic_models.Merchant,
    customer,
    operator: basic_models.Employee,
    record: dict[str, Any],
    record_kind: str,
    dry_run: bool,
) -> dict[str, Any]:
    if not isinstance(record, dict):
        raise ExternalFinanceSyncError('外部 finance record 不是对象')

    normalized = _normalize_external_receipt_record(record=record, record_kind=record_kind)
    existing = business_models.ReceiptOrder.objects.filter(
        merchant=merchant,
        external_source_id=normalized['external_source_id'],
        is_external_source=True,
    ).select_related('customer').first()

    if existing:
        if not _is_equivalent_receipt_order(existing=existing, customer=customer, normalized=normalized):
            raise ExternalFinanceSyncConflictError(
                f'外部记录 {normalized["external_source_id"]} 已存在，但本地字段与外部不一致'
            )
        return {
            'created_count': 0,
            'skipped_existing_count': 1,
            'skipped_zero_settlement_count': 0,
            'dry_run_count': 0,
            'created_receipt_ids': [],
            'skipped_external_ids': [normalized['external_source_id']],
        }

    if dry_run:
        return {
            'created_count': 0,
            'skipped_existing_count': 0,
            'skipped_zero_settlement_count': 0,
            'dry_run_count': 1,
            'created_receipt_ids': [],
            'skipped_external_ids': [],
        }

    with transaction.atomic():
        order = business_models.ReceiptOrder.objects.create(
            merchant=merchant,
            customer=customer,
            receipt_date=normalized['receipt_date'],
            amount=normalized['amount'],
            discount_amount=normalized['discount_amount'],
            bank_account=None,
            markup=normalized['markup'],
            operator=operator,
            status=business_models.ReceiptOrderStatusEnum.PENDING,
            is_external_source=True,
            external_source_id=normalized['external_source_id'],
            remarks=normalized['remarks'],
        )
        approved = business_services.review_receipt_order(
            receipt_order=order,
            target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
            reviewed_by=operator,
        )

    return {
        'created_count': 1,
        'skipped_existing_count': 0,
        'skipped_zero_settlement_count': 0,
        'dry_run_count': 0,
        'created_receipt_ids': [approved.id],
        'skipped_external_ids': [],
    }


def _normalize_external_receipt_record(*, record: dict[str, Any], record_kind: str) -> dict[str, Any]:
    external_source_id = str(record.get('BianHaoID') or '').strip()
    if not external_source_id:
        raise ExternalFinanceSyncError('外部 finance record 缺少 BianHaoID')

    customer_name = str(record.get('KhName') or '').strip()
    markup = str(record.get('JieSunFS') or '').strip() or None
    receipt_date = _parse_external_date(record.get('RiQi') or record.get('KdRiQi'))
    if record_kind == 'receipt':
        amount = normalize_money_amount(
            _to_decimal(record.get('FkJinE'), field_name='FkJinE'),
            field_name='FkJinE',
        )
        discount_amount = normalize_money_amount(
            _to_decimal(record.get('ZkJinE'), field_name='ZkJinE', default=Decimal('0')),
            field_name='ZkJinE',
        )
    elif record_kind == 'refund':
        amount = normalize_money_amount(
            _to_decimal(record.get('YfJinE'), field_name='YfJinE'),
            field_name='YfJinE',
        )
        discount_amount = Decimal('0')
    else:
        raise ExternalFinanceSyncError(f'不支持的 record_kind: {record_kind}')

    return {
        'external_source_id': external_source_id,
        'receipt_date': receipt_date,
        'amount': amount,
        'discount_amount': discount_amount,
        'settlement_amount': calculate_settlement_amount(
            amount=amount,
            discount_amount=discount_amount,
        ),
        'customer_name': customer_name,
        'markup': markup,
        'remarks': _build_external_remarks(record=record, record_kind=record_kind),
    }


def _build_external_remarks(*, record: dict[str, Any], record_kind: str) -> str:
    lines = [f'外部财务同步: {record_kind}']
    external_customer_id = str(record.get('KhID') or '').strip()
    if external_customer_id:
        lines.append(f'外部客户ID: {external_customer_id}')
    summary = str(record.get('ZhaiYao') or '').strip()
    if summary:
        lines.append(f'摘要: {summary}')
    note = str(record.get('BeiZhu') or '').strip()
    if note:
        lines.append(f'备注: {note}')
    return '\n'.join(lines)


def _parse_external_date(value: Any) -> date:
    text = str(value or '').strip()
    if not text:
        raise ExternalFinanceSyncError('外部 finance record 缺少日期字段')
    try:
        parsed = datetime.fromisoformat(text.replace('Z', '+00:00'))
    except ValueError as exc:
        raise ExternalFinanceSyncError(f'外部日期格式非法: {text}') from exc
    return parsed.date()


def _to_decimal(value: Any, *, field_name: str, default: Decimal | None = None) -> Decimal:
    if value in (None, ''):
        if default is not None:
            return default
        raise ExternalFinanceSyncError(f'外部字段 {field_name} 不能为空')
    try:
        return Decimal(str(value))
    except (InvalidOperation, TypeError, ValueError) as exc:
        raise ExternalFinanceSyncError(f'外部字段 {field_name} 不是合法数字: {value!r}') from exc


def _is_equivalent_receipt_order(*, existing: business_models.ReceiptOrder, customer, normalized: dict[str, Any]) -> bool:
    customer_id = getattr(customer, 'id', None)
    return all(
        [
            existing.customer_id == customer_id,
            existing.receipt_date == normalized['receipt_date'],
            existing.amount == normalized['amount'],
            existing.discount_amount == normalized['discount_amount'],
            existing.status == business_models.ReceiptOrderStatusEnum.APPROVED,
        ]
    )


def _merge_sync_result(summary: dict[str, Any], result: dict[str, Any]) -> None:
    summary['created_count'] += int(result.get('created_count') or 0)
    summary['skipped_existing_count'] += int(result.get('skipped_existing_count') or 0)
    summary['skipped_zero_settlement_count'] += int(result.get('skipped_zero_settlement_count') or 0)
    summary['dry_run_count'] += int(result.get('dry_run_count') or 0)
    summary['created_receipt_ids'].extend(result.get('created_receipt_ids') or [])
    summary['skipped_external_ids'].extend(result.get('skipped_external_ids') or [])


def _merge_external_business_sync_result(summary: dict[str, Any], result: dict[str, Any]) -> None:
    category = result.get('category')
    seen_count = int(result.get('seen_count') or 0)
    if category == business_models.ExternalCustomerStatementCategoryEnum.SALE:
        summary['sales_seen'] += seen_count
    elif category == business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN:
        summary['sale_returns_seen'] += seen_count
    summary['external_business_created_count'] += int(result.get('created_count') or 0)
    summary['external_business_skipped_existing_count'] += int(result.get('skipped_existing_count') or 0)
    summary['created_external_business_ids'].extend(result.get('created_ids') or [])


def _group_external_i_sale_records(records: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
    grouped: dict[str, list[dict[str, Any]]] = {}
    for record in records:
        if not isinstance(record, dict):
            raise ExternalFinanceSyncError('外部 i_sale record 不是对象')
        external_source_id = str(record.get('BianHaoID') or '').strip()
        if not external_source_id:
            raise ExternalFinanceSyncError('外部 i_sale record 缺少 BianHaoID')
        grouped.setdefault(external_source_id, []).append(record)
    return grouped


def _sync_external_statement_order_group(
    *,
    merchant: basic_models.Merchant,
    customer: basic_models.Customer,
    external_customer_id: str,
    external_source_id: str,
    category: str,
    grouped_records: list[dict[str, Any]],
    dry_run: bool,
    force_update: bool = False,
) -> dict[str, Any]:
    normalized = _normalize_external_statement_order_group(
        customer=customer,
        external_customer_id=external_customer_id,
        category=category,
        external_source_id=external_source_id,
        grouped_records=grouped_records,
    )
    existing = business_models.ExternalCustomerStatementOrder.objects.filter(
        merchant=merchant,
        category=category,
        external_source_id=external_source_id,
    ).first()
    if existing:
        if _is_equivalent_external_statement_order(existing=existing, normalized=normalized):
            return {'created_count': 0, 'skipped_existing_count': 1, 'updated_count': 0, 'created_ids': []}
        if not force_update:
            raise ExternalFinanceSyncConflictError(
                f'外部业务记录 {external_source_id} 已存在，但本地字段与外部不一致'
            )
        # force_update: 用新数据覆盖已有记录
        update_fields = [
            'total_amount', 'sf_amount', 'zk_amount', 'occurred_at', 'recorded_at',
            'settlement_method', 'remarks', 'items_payload', 'extra_payload',
        ]
        for field in update_fields:
            if field in normalized:
                setattr(existing, field, normalized[field])
        existing.save(update_fields=[*update_fields, 'updated_at'])
        return {'created_count': 0, 'skipped_existing_count': 0, 'updated_count': 1, 'created_ids': []}

    if dry_run:
        return {'created_count': 0, 'skipped_existing_count': 0, 'updated_count': 0, 'created_ids': []}

    created = business_models.ExternalCustomerStatementOrder.objects.create(**normalized)
    return {'created_count': 1, 'skipped_existing_count': 0, 'updated_count': 0, 'created_ids': [created.id]}


def _normalize_external_statement_order_group(
    *,
    customer: basic_models.Customer,
    external_customer_id: str,
    category: str,
    external_source_id: str,
    grouped_records: list[dict[str, Any]],
) -> dict[str, Any]:
    first_record = grouped_records[0]
    occurred_at = _parse_external_date(first_record.get('RiQi') or first_record.get('KdRiQi'))
    recorded_at = _parse_external_datetime(first_record.get('KdRiQi'))
    remarks_lines = []
    items_payload = []
    total_amount = Decimal('0')
    sf_amount = Decimal('0')
    zk_amount = Decimal('0')

    # SfJinE 是单据头级别字段（每行重复），只取首条记录的值（按 BianHaoID 去重）
    sf_amount = abs(_to_decimal(first_record.get('SfJinE'), field_name='SfJinE', default=Decimal('0')))
    # ZkJinE 不去重，每行独立求和

    for record in grouped_records:
        # JinE 不取绝对值：同一订单内可能有负数明细行（如冲减行），应保留原始正负
        # 最终 total_amount 取绝对值（退货单整体为负，销售单整体为正）
        amount_value = _to_decimal(record.get('JinE'), field_name='JinE', default=Decimal('0'))
        price_value = abs(_to_decimal(record.get('DanJia'), field_name='DanJia', default=Decimal('0')))
        quantity_value = abs(_to_decimal(record.get('ShuLiang'), field_name='ShuLiang', default=Decimal('0')))
        rolls_value = abs(_to_decimal(record.get('JianShu'), field_name='JianShu', default=Decimal('0')))
        zk_value = abs(_to_decimal(record.get('ZkJinE'), field_name='ZkJinE', default=Decimal('0')))
        total_amount += amount_value
        zk_amount += zk_value
        external_product_id = str(record.get('HpID') or '').strip()
        product_name = str(record.get('HpName') or '').strip() or external_product_id
        color = str(record.get('YanSe') or '').strip()
        spec = str(record.get('SeHao') or '').strip()
        unit = str(record.get('JiJiaDW') or '').strip()
        item_payload = {
            'product_id': None,
            'product_name': product_name,
            'quantity': _decimal_to_string(quantity_value),
            'price': _decimal_to_string(price_value),
            'unit': unit,
            'color': color,
            'spec': spec,
            'quantity_of_rolls': [],
            'num_of_rolls': int(rolls_value) if rolls_value == rolls_value.to_integral_value() else 0,
            'external_sub_id': record.get('SubID'),
            'external_product_id': external_product_id,
        }
        items_payload.append(item_payload)
        for note_key in ('BeiZhu', 'BeiZhuC', 'BeiZhuD', 'MeoD'):
            note = str(record.get(note_key) or '').strip()
            if note:
                remarks_lines.append(f'{note_key}: {note}')

    return {
        'merchant': customer.merchant,
        'customer': customer,
        'category': category,
        'external_customer_id': external_customer_id,
        'external_source_id': external_source_id,
        'occurred_at': occurred_at,
        'recorded_at': recorded_at,
        'settlement_method': str(first_record.get('JieSunFS') or '').strip(),
        'total_amount': abs(total_amount),
        'sf_amount': sf_amount,
        'zk_amount': zk_amount,
        'remarks': '\n'.join(dict.fromkeys(remarks_lines)),
        'items_payload': items_payload,
        'extra_payload': {
            'raw_count': len(grouped_records),
            'dan_type': str(first_record.get('DanType') or '').strip(),
        },
    }


def _is_equivalent_external_statement_order(
    *,
    existing: business_models.ExternalCustomerStatementOrder,
    normalized: dict[str, Any],
) -> bool:
    return all(
        [
            existing.customer_id == normalized['customer'].id,
            existing.external_customer_id == normalized['external_customer_id'],
            existing.occurred_at == normalized['occurred_at'],
            existing.recorded_at == normalized['recorded_at'],
            existing.total_amount == normalized['total_amount'],
            existing.sf_amount == normalized.get('sf_amount', Decimal('0')),
            existing.zk_amount == normalized.get('zk_amount', Decimal('0')),
            existing.items_payload == normalized['items_payload'],
            (existing.remarks or '') == (normalized['remarks'] or ''),
        ]
    )


def _apply_sale_discount_zk(
    *,
    merchant: basic_models.Merchant,
    customer: basic_models.Customer,
    sale_discounts: list[dict[str, Any]],
    progress_callback: Callable[[str], None] | None = None,
) -> int:
    """
    从 sale_discount 数据（F_Skd 的 XS% 行）中提取 ZkJinE 和 YfJinE，
    更新到对应的 ExternalCustomerStatementOrder.zk_amount 和 total_amount。

    关键修正：I_Sale.SfJinE 始终为 0（数据源缺陷），导致从 I_Sale.JinE 计算的
    total_amount 未扣除实付部分。F_Skd XS% 的 YfJinE 已经扣除了实付，是正确的
    欠款基数，因此用它覆盖 total_amount。

    返回更新的记录数。
    """
    # 按 BianHaoID 聚合折扣和应付金额
    zk_by_source: dict[str, Decimal] = {}
    yf_by_source: dict[str, Decimal] = {}
    for record in sale_discounts:
        if not isinstance(record, dict):
            continue
        source_id = str(record.get('BianHaoID') or '').strip()
        if not source_id:
            continue
        zk_value = abs(_to_decimal(record.get('ZkJinE'), field_name='ZkJinE', default=Decimal('0')))
        # YfJinE 不取 abs()：负值代表退款/冲减，应保留原始正负
        yf_value = _to_decimal(record.get('YfJinE'), field_name='YfJinE', default=Decimal('0'))
        if zk_value:
            zk_by_source[source_id] = zk_by_source.get(source_id, Decimal('0')) + zk_value
        # YfJinE 是每个销售单的正确欠款金额（已扣除实付 SfJinE）
        # 一个 BianHaoID 通常只有一条 F_Skd XS% 记录，但为安全起见做聚合
        # 注意：不过滤零值，因为 YfJinE=0 也是有效数据（表示全额实付）
        yf_by_source[source_id] = yf_by_source.get(source_id, Decimal('0')) + yf_value

    if not zk_by_source and not yf_by_source:
        return 0

    updated_count = 0

    # 更新 total_amount：用 F_Skd XS%.YfJinE 覆盖从 I_Sale.JinE 计算的值
    for source_id, yf_total in yf_by_source.items():
        updated = business_models.ExternalCustomerStatementOrder.objects.filter(
            merchant=merchant,
            customer=customer,
            category=business_models.ExternalCustomerStatementCategoryEnum.SALE,
            external_source_id=source_id,
        ).exclude(total_amount=yf_total).update(total_amount=yf_total)
        updated_count += updated

    # 更新 zk_amount
    for source_id, zk_total in zk_by_source.items():
        updated = business_models.ExternalCustomerStatementOrder.objects.filter(
            merchant=merchant,
            customer=customer,
            category=business_models.ExternalCustomerStatementCategoryEnum.SALE,
            external_source_id=source_id,
        ).exclude(zk_amount=zk_total).update(zk_amount=zk_total)
        updated_count += updated

    if updated_count:
        _emit_progress(
            progress_callback,
            f'销售折扣/应付更新: {updated_count} 条记录已从 sale_discount 数据更新 (total_amount via YfJinE, zk_amount via ZkJinE)',
        )
    return updated_count


def _find_local_product_by_external_product_id(
    *,
    merchant: basic_models.Merchant,
    external_product_id: str,
):
    if not external_product_id:
        return None
    return basic_models.Product.objects.filter(merchant=merchant, human_id=external_product_id).first()


def _parse_external_datetime(value: Any) -> datetime | None:
    text = str(value or '').strip()
    if not text:
        return None
    try:
        return datetime.fromisoformat(text.replace('Z', '+00:00'))
    except ValueError as exc:
        raise ExternalFinanceSyncError(f'外部日期时间格式非法: {text}') from exc


def _decimal_to_string(value: Decimal) -> str:
    return f'{value:.2f}'


def _emit_progress(progress_callback: Callable[[str], None] | None, message: str) -> None:
    if callable(progress_callback):
        progress_callback(message)
