from __future__ import annotations

from decimal import Decimal
from typing import List
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
from flower.common import ModelBase
from basic_info import models as basic_info_models
from .amounts import calculate_line_amount, calculate_settlement_amount


def build_business_human_id(prefix: str, created_at, object_id) -> str:
    """生成业务单据的人类可读编号（虚拟编号，不落库）。"""
    if object_id is None or created_at is None:
        return ''
    return f"{prefix}{created_at.year}{created_at.month:02d}{created_at.day:02d}{object_id:06d}"


class PurchaseOrderKindEnum(models.IntegerChoices):
    """采购单类型"""
    WHOLESALE = 1, '大货'
    SAMPLE = 2, '样板'


class PurchaseOrderStatusEnum(models.IntegerChoices):
    """采购单状态"""
    PENDING = 1, '审批中'
    APPROVED = 2, '审批通过'
    CANCELLED = 3, '作废'


class OrderItemsAggregationMixin:
    """
    提供订单金额/数量聚合的通用实现。
    默认读取 `items` 关联管理器，可在子类通过 `order_items_accessor`
    覆盖以适配不同的关联名称。
    """

    order_items_accessor = 'items'

    def _iter_order_items(self):
        return getattr(self, self.order_items_accessor).all()

    def get_total_amount(self) -> Decimal:
        total = Decimal('0')
        for item in self._iter_order_items():
            total += Decimal(item.total_amount())
        return total

    def get_total_quantity(self) -> Decimal:
        total = Decimal('0')
        for item in self._iter_order_items():
            total += Decimal(item.quantity)
        return total

    def get_total_diff_quantity(self) -> Decimal:
        total = Decimal('0')
        for item in self._iter_order_items():
            total += Decimal(item.diff_quantity())
        return total


class OrderDirectionMixin:
    """提供带方向金额的通用实现，子类只需要覆盖方向。"""

    def get_direction(self) -> int:
        """
        返回订单方向：
            入库 / 正向金额: 1
            出库 / 负向金额: -1
        """

    def get_signed_total_amount(self) -> Decimal:
        return self.get_total_amount() * Decimal(self.get_direction())


class OrderCounterpartyMixin:
    """
    抽象出“订单关联业务主体”的能力，子类只需指出字段名称，
    即可通过统一接口获取 Supplier / Customer 等对象。
    """

    counterparty_field_name = ''

    def get_counterparty_field_name(self) -> str:
        """返回用于存放业务主体的字段名，如 supplier / customer。"""

    def get_counterparty(self) -> basic_info_models.Supplier | basic_info_models.Customer:
        field_name = self.get_counterparty_field_name() or self.counterparty_field_name
        if not field_name:
            raise AttributeError('未定义 counterparty 字段名称')
        try:
            counterparty = getattr(self, field_name)
        except AttributeError as exc:
            raise AttributeError(f'找不到字段 {field_name}') from exc
        if counterparty is None:
            raise ValueError(f'{field_name} 未设置，无法获取业务主体')
        return counterparty


class PurchaseOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
    """采购单模型（业务模块）"""

    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='purchase_orders',
        verbose_name='所属商户',
    )
    supplier = models.ForeignKey(
        basic_info_models.Supplier,
        on_delete=models.PROTECT,
        related_name='purchase_orders',
        verbose_name='供应商',
    )
    purchase_date = models.DateField(verbose_name='采购日期')
    kind = models.IntegerField(
        choices=PurchaseOrderKindEnum.choices, 
        default=PurchaseOrderKindEnum.WHOLESALE, 
        verbose_name='采购单类型',
    )
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='purchase_orders',
        verbose_name='经办人',
    )
    warehouse = models.ForeignKey(
        basic_info_models.WareHouse,
        on_delete=models.PROTECT,
        related_name='purchase_orders',
        verbose_name='仓库',
    )
    status = models.IntegerField(
        choices=PurchaseOrderStatusEnum.choices, 
        default=PurchaseOrderStatusEnum.PENDING, 
        verbose_name='状态',
    )
    is_red_flushed = models.BooleanField(default=False, db_index=True, verbose_name='已红冲')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')
    red_flushed_at = models.DateTimeField(null=True, blank=True, verbose_name='红冲时间')
    balance_before_snapshot = models.DecimalField(
        max_digits=15, decimal_places=2, null=True, blank=True,
        verbose_name='创建前供应商余额快照',
    )
    from_pre_purchase_order_id = models.BigIntegerField(
        null=True,
        blank=True,
        verbose_name='关联预采购单ID',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    def __str__(self):
        return f'采购订单 {self.id} - {self.supplier.name}'

    @property
    def human_id(self) -> str:
        """
        人类可读编号（虚拟编号，不落库）。

        规则与 SalesOrder.human_id 保持一致：CG + YYYYMMDD{6位id}
        """
        return build_business_human_id('CG', self.created_at, self.id)

    class Meta:
        verbose_name = '采购单'
        verbose_name_plural = '采购单'

    def get_direction(self) -> int:
        return 1

    def get_counterparty_field_name(self) -> str:
        return 'supplier'


class PurchaseOrderItem(ModelBase):
    """采购单明细模型"""

    id = models.BigAutoField(primary_key=True)
    purchase_order = models.ForeignKey(
        PurchaseOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name='采购单',
    )
    product = models.ForeignKey(
        basic_info_models.Product,
        on_delete=models.PROTECT,
        related_name='purchase_order_items',
        verbose_name='产品',
    )
    price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
    color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量')
    unit = models.CharField(max_length=50, verbose_name='单位')
    spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
    empty_diff_percent = models.DecimalField(
        max_digits=15, 
        decimal_places=2, 
        verbose_name='空差百分比',
    )
    # if purchase_order.warehouse.mode != WarehouseModeEnum.UNRESTRICTED, then quantity_of_rolls is required
    quantity_of_rolls = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name='各条数数量',
    )
    num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
    batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '采购单明细'
        verbose_name_plural = '采购单明细'

    def real_quantity(self):
        return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)

    def diff_quantity(self):
        return round(self.quantity * (self.empty_diff_percent / 100), 2)

    def total_amount(self):
        return calculate_line_amount(
            quantity=self.quantity,
            price=self.price,
            standard_quantity=self.real_quantity(),
        )

    def split_quantity_of_rolls(self) -> List[int]:
        if self.quantity_of_rolls:
            return [int(value) for value in self.quantity_of_rolls.split(',') if value.strip()]
        return []


class SalesOrderKindEnum(models.IntegerChoices):
    """销售单类型"""
    WHOLESALE = 1, '大货'
    SAMPLE = 2, '样板'


class SalesOrderStatusEnum(models.IntegerChoices):
    """销售单状态"""
    PENDING = 1, '审批中'
    APPROVED = 2, '审批通过'
    CANCELLED = 3, '作废'


class AllocationRecordStatusEnum(models.IntegerChoices):
    """配货记录状态"""

    ACTIVE = 1, '有效'
    CANCELLED = 2, '已撤销'


class SalesOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
    """销售单模型"""

    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='sales_orders',
        verbose_name='所属商户',
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='sales_orders',
        verbose_name='客户',
    )
    sales_date = models.DateField(verbose_name='销售日期')
    kind = models.IntegerField(
        choices=SalesOrderKindEnum.choices,
        default=SalesOrderKindEnum.WHOLESALE,
        verbose_name='销售单类型',
    )
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='sales_orders',
        verbose_name='经办人',
    )
    warehouse = models.ForeignKey(
        basic_info_models.WareHouse,
        on_delete=models.PROTECT,
        related_name='sales_orders',
        verbose_name='仓库',
    )
    status = models.IntegerField(
        choices=SalesOrderStatusEnum.choices,
        default=SalesOrderStatusEnum.PENDING,
        verbose_name='状态',
    )
    is_red_flushed = models.BooleanField(default=False, db_index=True, verbose_name='已红冲')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')
    red_flushed_at = models.DateTimeField(null=True, blank=True, verbose_name='红冲时间')
    balance_before_snapshot = models.DecimalField(
        max_digits=15, decimal_places=2, null=True, blank=True,
        verbose_name='创建前客户余额快照',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')
    from_pre_sales_order_id = models.BigIntegerField(
        blank=True,
        null=True,
        verbose_name='来源预销售单ID',
    )

    def __str__(self):
        order_id = self.human_id or str(self.id)
        return f'销售订单 {order_id} - {self.customer.name}'

    @property
    def human_id(self) -> str:
        """
        人类可读编号（虚拟编号，不落库）。

        规则：XS + YYYYMMDD{6位id}
        """
        return build_business_human_id('XS', self.created_at, self.id)

    class Meta:
        verbose_name = '销售单'
        verbose_name_plural = '销售单'

    def get_direction(self) -> int:
        return -1

    def get_counterparty_field_name(self) -> str:
        return 'customer'


class SalesOrderItem(ModelBase):
    """销售单明细模型"""

    id = models.BigAutoField(primary_key=True)
    sales_order = models.ForeignKey(
        SalesOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name='销售单',
    )
    product = models.ForeignKey(
        basic_info_models.Product,
        on_delete=models.PROTECT,
        related_name='sales_order_items',
        verbose_name='产品',
    )
    printing_job = models.ForeignKey(
        'printing.PrintingJob',
        on_delete=models.PROTECT,
        related_name='sales_order_items',
        null=True,
        blank=True,
        verbose_name='关联印染任务',
    )
    price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
    color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量')
    unit = models.CharField(max_length=50, verbose_name='单位')
    spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
    empty_diff_percent = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        verbose_name='空差百分比',
    )
    quantity_of_rolls = models.TextField(
        null=True,
        blank=True,
        verbose_name='各条数数量',
    )
    num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
    consume_detail_ids = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name='消耗入库明细ID列表',
    )
    batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
    order_quantity = models.IntegerField(null=True, blank=True, verbose_name='下单数量')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '销售单明细'
        verbose_name_plural = '销售单明细'

    def split_quantity_of_rolls(self) -> List[int]:
        if self.quantity_of_rolls:
            return [int(value) for value in self.quantity_of_rolls.split(',') if value.strip()]
        return []

    def real_quantity(self):
        return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)

    def diff_quantity(self):
        return round(self.quantity * (self.empty_diff_percent / 100), 2)

    def total_amount(self):
        return calculate_line_amount(
            quantity=self.quantity,
            price=self.price,
            standard_quantity=self.real_quantity(),
        )


class PreSalesOrder(ModelBase):
    """预销售单

    预销售单用于在客户下达采购/要货指令时，提前通知仓库进行库存核对与调货。

    目前阶段：仅定义 PreSalesOrder 与 PreSalesOrderItem 的一对多关系，
    并关联 customer 与 created_by；其他业务对象（如 Warehouse / StockChangeRecord / StockFreeze 等）
    暂不在此模型中建立关联。
    """

    id = models.BigAutoField(primary_key=True)

    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='pre_sales_orders',
        verbose_name='所属商户',
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='pre_sales_orders',
        verbose_name='客户',
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_pre_sales_orders',
        verbose_name='创建者用户',
    )
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='operated_pre_sales_orders',
        null=True,
        blank=True,
        verbose_name='经办人',
    )
    warehouse = models.ForeignKey(
        basic_info_models.WareHouse,
        on_delete=models.PROTECT,
        related_name='pre_sales_orders',
        verbose_name='仓库',
    )
    kind = models.IntegerField(
        choices=SalesOrderKindEnum.choices,
        default=SalesOrderKindEnum.WHOLESALE,
        verbose_name='预销售单类型',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    def __str__(self):
        return f'预销售单 {self.human_id or self.id}'

    def clean(self):
        super().clean()

        errors = {}

        if getattr(self, 'merchant_id', None) and getattr(self, 'customer_id', None):
            if self.customer.merchant_id != self.merchant_id:
                errors['customer'] = '客户所属商户与预销售单所属商户不一致'

        if getattr(self, 'merchant_id', None) and getattr(self, 'warehouse_id', None):
            if self.warehouse.merchant_id != self.merchant_id:
                errors['warehouse'] = '仓库所属商户与预销售单所属商户不一致'

        if getattr(self, 'operator_id', None) and getattr(self, 'merchant_id', None):
            if self.operator.merchant_id != self.merchant_id:
                errors['operator'] = '经办人所属商户与预销售单所属商户不一致'

        if errors:
            raise ValidationError(errors)

    @property
    def customer_name(self) -> str:
        if getattr(self, 'customer_id', None):
            return self.customer.name
        return ''

    @property
    def merchant_name(self) -> str:
        if getattr(self, 'merchant_id', None):
            return self.merchant.name
        return ''

    @property
    def created_by_username(self) -> str:
        if getattr(self, 'created_by_id', None):
            return getattr(self.created_by, 'username', '') or ''
        return ''

    @property
    def operator_name(self) -> str:
        if getattr(self, 'operator_id', None):
            return self.operator.name
        return ''

    @property
    def warehouse_name(self) -> str:
        if getattr(self, 'warehouse_id', None):
            return self.warehouse.name
        return ''

    @property
    def human_id(self) -> str:
        """人类可读编号（虚拟编号，不落库）。

        规则：YS + YYYYMMDD{6位id}
        """
        return build_business_human_id('YS', self.created_at, self.id)

    class Meta:
        verbose_name = '预销售单'
        verbose_name_plural = '预销售单'


class PreSalesOrderItem(ModelBase):
    """预销售单明细"""

    id = models.BigAutoField(primary_key=True)
    pre_sales_order = models.ForeignKey(
        PreSalesOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name='预销售单',
    )

    # 暂不与 Product 建立外键，先用弱关联字段承载。
    product_id = models.BigIntegerField(null=True, blank=True, verbose_name='产品ID')
    product_name = models.CharField(max_length=100, blank=True, default='', verbose_name='产品名称')

    color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='数量')
    unit = models.CharField(max_length=50, blank=True, default='', verbose_name='单位')
    spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
    quantity_of_rolls = models.TextField(
        null=True,
        blank=True,
        verbose_name='各条数数量',
    )
    num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
    order_quantity = models.IntegerField(null=True, blank=True, verbose_name='下单数量')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '预销售单明细'
        verbose_name_plural = '预销售单明细'


class AllocationRecord(ModelBase):
    """配货记录（执行记录）"""

    id = models.BigAutoField(primary_key=True)
    pre_sales_order_item = models.ForeignKey(
        PreSalesOrderItem,
        on_delete=models.CASCADE,
        related_name='allocation_records',
        verbose_name='预销售单明细',
    )
    stock_ids = models.TextField(blank=True, null=True, verbose_name='库存明细ID列表')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='配货数量')
    unit = models.CharField(max_length=50, verbose_name='单位')
    status = models.IntegerField(
        choices=AllocationRecordStatusEnum.choices,
        default=AllocationRecordStatusEnum.ACTIVE,
        verbose_name='状态',
    )
    scanned_by = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='allocation_records',
        verbose_name='扫码人员',
    )
    scanned_at = models.DateTimeField(auto_now_add=True, verbose_name='扫码时间')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    def __str__(self):
        return f'配货记录 {self.id} - 明细 {self.pre_sales_order_item_id}'

    @property
    def stock_id_list(self) -> List[int]:
        raw = (self.stock_ids or '').strip()
        if not raw:
            return []
        result: List[int] = []
        for value in raw.split(','):
            value = value.strip()
            if not value:
                continue
            try:
                result.append(int(value))
            except ValueError:
                continue
        return result

    class Meta:
        verbose_name = '配货记录'
        verbose_name_plural = '配货记录'


class PrePurchaseOrder(ModelBase):
    """预采购单

    预采购单用于在向供应商下达正式采购单之前，用于沟通与确认货品、数量、仓库等信息。

    当前阶段：仅定义 PrePurchaseOrder 与 PrePurchaseOrderItem 的一对多关系，
    并关联 supplier / warehouse / created_by。
    """

    id = models.BigAutoField(primary_key=True)

    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='pre_purchase_orders',
        verbose_name='所属商户',
    )
    supplier = models.ForeignKey(
        basic_info_models.Supplier,
        on_delete=models.PROTECT,
        related_name='pre_purchase_orders',
        verbose_name='供应商',
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_pre_purchase_orders',
        verbose_name='创建者用户',
    )
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='operated_pre_purchase_orders',
        null=True,
        blank=True,
        verbose_name='经办人',
    )
    warehouse = models.ForeignKey(
        basic_info_models.WareHouse,
        on_delete=models.PROTECT,
        related_name='pre_purchase_orders',
        verbose_name='仓库',
    )
    kind = models.IntegerField(
        choices=PurchaseOrderKindEnum.choices,
        default=PurchaseOrderKindEnum.WHOLESALE,
        verbose_name='预采购单类型',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    def __str__(self):
        return f'预采购单 {self.human_id or self.id}'

    def clean(self):
        super().clean()

        errors = {}

        if getattr(self, 'merchant_id', None) and getattr(self, 'supplier_id', None):
            if self.supplier.merchant_id != self.merchant_id:
                errors['supplier'] = '供应商所属商户与预采购单所属商户不一致'

        if getattr(self, 'merchant_id', None) and getattr(self, 'warehouse_id', None):
            if self.warehouse.merchant_id != self.merchant_id:
                errors['warehouse'] = '仓库所属商户与预采购单所属商户不一致'

        if getattr(self, 'operator_id', None) and getattr(self, 'merchant_id', None):
            if self.operator.merchant_id != self.merchant_id:
                errors['operator'] = '经办人所属商户与预采购单所属商户不一致'

        if errors:
            raise ValidationError(errors)

    @property
    def supplier_name(self) -> str:
        if getattr(self, 'supplier_id', None):
            return self.supplier.name
        return ''

    @property
    def merchant_name(self) -> str:
        if getattr(self, 'merchant_id', None):
            return self.merchant.name
        return ''

    @property
    def created_by_username(self) -> str:
        if getattr(self, 'created_by_id', None):
            return getattr(self.created_by, 'username', '') or ''
        return ''

    @property
    def operator_name(self) -> str:
        if getattr(self, 'operator_id', None):
            return self.operator.name
        return ''

    @property
    def warehouse_name(self) -> str:
        if getattr(self, 'warehouse_id', None):
            return self.warehouse.name
        return ''

    @property
    def human_id(self) -> str:
        """人类可读编号（虚拟编号，不落库）。

        规则：YC + YYYYMMDD{6位id}
        """

        return build_business_human_id('YC', self.created_at, self.id)

    class Meta:
        verbose_name = '预采购单'
        verbose_name_plural = '预采购单'


class PrePurchaseOrderItem(ModelBase):
    """预采购单明细"""

    id = models.BigAutoField(primary_key=True)
    pre_purchase_order = models.ForeignKey(
        PrePurchaseOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name='预采购单',
    )

    # 暂不与 Product 建立外键，先用弱关联字段承载。
    product_id = models.BigIntegerField(null=True, blank=True, verbose_name='产品ID')
    product_name = models.CharField(max_length=100, blank=True, default='', verbose_name='产品名称')

    color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='数量')
    unit = models.CharField(max_length=50, blank=True, default='', verbose_name='单位')
    spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
    quantity_of_rolls = models.TextField(
        null=True,
        blank=True,
        verbose_name='各条数数量',
    )
    num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
    order_quantity = models.IntegerField(null=True, blank=True, verbose_name='下单数量')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '预采购单明细'
        verbose_name_plural = '预采购单明细'


class PurchaseReturnStatusEnum(models.IntegerChoices):
    PENDING = 1, '审批中'
    APPROVED = 2, '审批通过'
    CANCELLED = 3, '作废'


class PurchaseReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
    """采购退货单（库存出库 + 供应商应付减少）"""

    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='purchase_return_orders',
        verbose_name='所属商户',
    )
    supplier = models.ForeignKey(
        basic_info_models.Supplier,
        on_delete=models.PROTECT,
        related_name='purchase_return_orders',
        verbose_name='供应商',
    )
    purchase_order = models.ForeignKey(
        'PurchaseOrder',
        on_delete=models.SET_NULL,
        related_name='return_orders',
        null=True,
        blank=True,
        verbose_name='原采购单',
    )
    return_date = models.DateField(verbose_name='退货日期')
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='purchase_return_orders',
        verbose_name='经办人',
    )
    warehouse = models.ForeignKey(
        basic_info_models.WareHouse,
        on_delete=models.PROTECT,
        related_name='purchase_return_orders',
        verbose_name='仓库',
    )
    status = models.IntegerField(
        choices=PurchaseReturnStatusEnum.choices,
        default=PurchaseReturnStatusEnum.PENDING,
        verbose_name='状态',
    )
    is_red_flushed = models.BooleanField(default=False, db_index=True, verbose_name='已红冲')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')
    red_flushed_at = models.DateTimeField(null=True, blank=True, verbose_name='红冲时间')
    balance_before_snapshot = models.DecimalField(
        max_digits=15, decimal_places=2, null=True, blank=True,
        verbose_name='创建前供应商余额快照',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '采购退货单'
        verbose_name_plural = '采购退货单'

    def __str__(self):
        return f'采购退货 {self.id} - {self.supplier.name}'

    @property
    def human_id(self) -> str:
        """
        人类可读编号（虚拟编号，不落库）。

        规则与 SalesOrder.human_id 保持一致：CT + YYYYMMDD{6位id}
        """
        return build_business_human_id('CT', self.created_at, self.id)

    def get_direction(self) -> int:
        return -1

    def get_counterparty_field_name(self) -> str:
        return 'supplier'


class PurchaseReturnOrderItem(ModelBase):
    id = models.BigAutoField(primary_key=True)
    purchase_return_order = models.ForeignKey(
        PurchaseReturnOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name='采购退货单',
    )
    product = models.ForeignKey(
        basic_info_models.Product,
        on_delete=models.PROTECT,
        related_name='purchase_return_order_items',
        verbose_name='产品',
    )
    price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
    color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量')
    unit = models.CharField(max_length=50, verbose_name='单位')
    spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
    empty_diff_percent = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='空差百分比')
    quantity_of_rolls = models.CharField(max_length=255, null=True, blank=True, verbose_name='各条数数量')
    num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
    consume_detail_ids = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name='关联入库明细ID',
    )
    batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '采购退货明细'
        verbose_name_plural = '采购退货明细'

    def split_quantity_of_rolls(self) -> List[int]:
        if self.quantity_of_rolls:
            return [int(value) for value in self.quantity_of_rolls.split(',') if value.strip()]
        return []

    def real_quantity(self):
        return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)

    def diff_quantity(self):
        return round(self.quantity * (self.empty_diff_percent / 100), 2)

    def total_amount(self):
        return calculate_line_amount(
            quantity=self.quantity,
            price=self.price,
            standard_quantity=self.real_quantity(),
        )


class SalesReturnStatusEnum(models.IntegerChoices):
    PENDING = 1, '审批中'
    APPROVED = 2, '审批通过'
    CANCELLED = 3, '作废'


class SalesReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
    """销售退货单（库存入库 + 客户应收减少）"""

    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='sales_return_orders',
        verbose_name='所属商户',
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='sales_return_orders',
        verbose_name='客户',
    )
    sales_order = models.ForeignKey(
        'SalesOrder',
        on_delete=models.SET_NULL,
        related_name='return_orders',
        null=True,
        blank=True,
        verbose_name='原销售单',
    )
    return_date = models.DateField(verbose_name='退货日期')
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='sales_return_orders',
        verbose_name='经办人',
    )
    warehouse = models.ForeignKey(
        basic_info_models.WareHouse,
        on_delete=models.PROTECT,
        related_name='sales_return_orders',
        verbose_name='仓库',
    )
    status = models.IntegerField(
        choices=SalesReturnStatusEnum.choices,
        default=SalesReturnStatusEnum.PENDING,
        verbose_name='状态',
    )
    is_red_flushed = models.BooleanField(default=False, db_index=True, verbose_name='已红冲')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')
    red_flushed_at = models.DateTimeField(null=True, blank=True, verbose_name='红冲时间')
    balance_before_snapshot = models.DecimalField(
        max_digits=15, decimal_places=2, null=True, blank=True,
        verbose_name='创建前客户余额快照',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '销售退货单'
        verbose_name_plural = '销售退货单'

    def __str__(self):
        return f'销售退货 {self.id} - {self.customer.name}'

    @property
    def human_id(self) -> str:
        """
        人类可读编号（虚拟编号，不落库）。

        规则与 SalesOrder.human_id 保持一致：XT + YYYYMMDD{6位id}
        """
        return build_business_human_id('XT', self.created_at, self.id)

    def get_direction(self) -> int:
        return 1

    def get_counterparty_field_name(self) -> str:
        return 'customer'


class SalesReturnOrderItem(ModelBase):
    id = models.BigAutoField(primary_key=True)
    sales_return_order = models.ForeignKey(
        SalesReturnOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name='销售退货单',
    )
    product = models.ForeignKey(
        basic_info_models.Product,
        on_delete=models.PROTECT,
        related_name='sales_return_order_items',
        verbose_name='产品',
    )
    price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
    color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量')
    unit = models.CharField(max_length=50, verbose_name='单位')
    spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格')
    empty_diff_percent = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='空差百分比')
    quantity_of_rolls = models.CharField(max_length=255, null=True, blank=True, verbose_name='各条数数量')
    num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
    consume_detail_ids = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name='关联入库明细ID',
    )
    batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '销售退货明细'
        verbose_name_plural = '销售退货明细'

    def split_quantity_of_rolls(self) -> List[int]:
        if self.quantity_of_rolls:
            return [int(value) for value in self.quantity_of_rolls.split(',') if value.strip()]
        return []

    def real_quantity(self):
        return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)

    def diff_quantity(self):
        return round(self.quantity * (self.empty_diff_percent / 100), 2)

    def total_amount(self):
        return calculate_line_amount(
            quantity=self.quantity,
            price=self.price,
            standard_quantity=self.real_quantity(),
        )


class ExternalCustomerStatementCategoryEnum(models.TextChoices):

    SALE = 'sale', '外部销售单'
    SALE_RETURN = 'sale_return', '外部销售退货单'


class ExternalCustomerStatementOrder(ModelBase):
    """仅用于对账单计算的外部业务来源单据。"""

    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='external_customer_statement_orders',
        verbose_name='所属商户',
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='external_statement_orders',
        verbose_name='客户',
    )
    category = models.CharField(
        max_length=20,
        choices=ExternalCustomerStatementCategoryEnum.choices,
        verbose_name='外部业务分类',
    )
    external_customer_id = models.CharField(max_length=100, blank=True, default='', verbose_name='外部客户ID')
    external_source_id = models.CharField(max_length=100, db_index=True, verbose_name='外部单号')
    occurred_at = models.DateField(verbose_name='业务日期')
    recorded_at = models.DateTimeField(blank=True, null=True, verbose_name='录单时间')
    settlement_method = models.CharField(max_length=100, blank=True, default='', verbose_name='结算方式')
    total_amount = models.DecimalField(max_digits=15, decimal_places=2, default=Decimal('0'), verbose_name='总金额')
    sf_amount = models.DecimalField(
        max_digits=15, decimal_places=2, default=Decimal('0'),
        verbose_name='现场收款金额',
        help_text='销售单上的现场收款(SfJinE)合计，仅 sale 类型有值',
    )
    zk_amount = models.DecimalField(
        max_digits=15, decimal_places=2, default=Decimal('0'),
        verbose_name='折扣金额',
        help_text='该单据的折扣(ZkJinE)合计',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')
    items_payload = models.JSONField(default=list, blank=True, verbose_name='明细快照')
    extra_payload = models.JSONField(default=dict, blank=True, verbose_name='扩展数据')

    class Meta:
        verbose_name = '外部客户对账来源单'
        verbose_name_plural = '外部客户对账来源单'
        constraints = [
            models.UniqueConstraint(
                fields=['merchant', 'category', 'external_source_id'],
                name='uniq_external_customer_statement_order_source',
            )
        ]

    def __str__(self):
        return f'外部对账来源 {self.external_source_id} ({self.category})'



class PaymentOrderStatusEnum(models.IntegerChoices):
    PENDING = 1, '审批中'
    APPROVED = 2, '审批通过'
    CANCELLED = 3, '作废'


class PaymentOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='payment_orders',
        verbose_name='所属商户',
    )
    supplier = models.ForeignKey(
        basic_info_models.Supplier,
        on_delete=models.PROTECT,
        related_name='payment_orders',
        verbose_name='供应商',
    )
    payment_date = models.DateField(verbose_name='付款日期')
    amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='金额')
    discount_amount = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=Decimal('0'),
        verbose_name='折扣金额',
    )
    bank_account = models.ForeignKey(
        basic_info_models.BankAccount,
        on_delete=models.PROTECT,
        related_name='payment_orders',
        verbose_name='银行账户',
        blank=True,
        null=True,
    )
    markup = models.CharField(max_length=255, blank=True, null=True, verbose_name='附言')
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='payment_orders',
        verbose_name='经办人',
    )
    status = models.IntegerField(
        choices=PaymentOrderStatusEnum.choices,
        default=PaymentOrderStatusEnum.PENDING,
        verbose_name='状态',
    )
    is_red_flushed = models.BooleanField(default=False, db_index=True, verbose_name='已红冲')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')
    red_flushed_at = models.DateTimeField(null=True, blank=True, verbose_name='红冲时间')
    is_external_source = models.BooleanField(
        default=False,
        verbose_name='是否外部来源',
    )
    external_source_id = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        db_index=True,
        verbose_name='外部来源ID',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '付款单'
        verbose_name_plural = '付款单'

    def __str__(self):
        return f'付款单 {self.id} - {self.supplier.name}'

    @property
    def human_id(self) -> str:
        """人类可读编号（虚拟编号，不落库）：FK + YYYYMMDD{6位id}。"""
        return build_business_human_id('FK', self.created_at, self.id)

    @property
    def settlement_amount(self) -> Decimal:
        return calculate_settlement_amount(
            amount=self.amount,
            discount_amount=self.discount_amount or Decimal('0'),
        )

    def get_total_amount(self) -> Decimal:
        return self.settlement_amount

    def get_direction(self) -> int:
        return -1

    def get_counterparty_field_name(self) -> str:
        return 'supplier'


class ReceiptOrderStatusEnum(models.IntegerChoices):
    PENDING = 1, '审批中'
    APPROVED = 2, '审批通过'
    CANCELLED = 3, '作废'


class ReceiptOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
    id = models.BigAutoField(primary_key=True)
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='receipt_orders',
        verbose_name='所属商户',
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='receipt_orders',
        verbose_name='客户',
    )
    receipt_date = models.DateField(verbose_name='收款日期')
    amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='金额')
    discount_amount = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=Decimal('0'),
        verbose_name='折扣金额',
    )
    bank_account = models.ForeignKey(
        basic_info_models.BankAccount,
        on_delete=models.PROTECT,
        related_name='receipt_orders',
        verbose_name='银行账户',
        blank=True,
        null=True,
    )
    markup = models.CharField(max_length=255, blank=True, null=True, verbose_name='附言')
    operator = models.ForeignKey(
        basic_info_models.Employee,
        on_delete=models.PROTECT,
        related_name='receipt_orders',
        verbose_name='经办人',
    )
    status = models.IntegerField(
        choices=ReceiptOrderStatusEnum.choices,
        default=ReceiptOrderStatusEnum.PENDING,
        verbose_name='状态',
    )
    is_red_flushed = models.BooleanField(default=False, db_index=True, verbose_name='已红冲')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')
    red_flushed_at = models.DateTimeField(null=True, blank=True, verbose_name='红冲时间')
    is_external_source = models.BooleanField(
        default=False,
        verbose_name='是否外部来源',
    )
    external_source_id = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        db_index=True,
        verbose_name='外部来源ID',
    )
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')

    class Meta:
        verbose_name = '收款单'
        verbose_name_plural = '收款单'

    def __str__(self):
        return f'收款单 {self.id} - {self.customer.name}'

    @property
    def human_id(self) -> str:
        """人类可读编号（虚拟编号，不落库）：SK + YYYYMMDD{6位id}。"""
        return build_business_human_id('SK', self.created_at, self.id)

    @property
    def settlement_amount(self) -> Decimal:
        return calculate_settlement_amount(
            amount=self.amount,
            discount_amount=self.discount_amount or Decimal('0'),
        )

    def get_total_amount(self) -> Decimal:
        return self.settlement_amount

    def get_direction(self) -> int:
        return -1

    def get_counterparty_field_name(self) -> str:
        return 'customer'


class SupplierBalance(ModelBase):
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='supplier_balances',
        verbose_name='所属商户',
    )
    supplier = models.ForeignKey(
        basic_info_models.Supplier,
        on_delete=models.PROTECT,
        related_name='balances',
        verbose_name='供应商',
    )
    balance = models.DecimalField(max_digits=15, decimal_places=2, default=Decimal('0'), verbose_name='应付余额')

    class Meta:
        verbose_name = '供应商余额'
        verbose_name_plural = '供应商余额'
        unique_together = ('merchant', 'supplier')


class CustomerBalance(ModelBase):
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='customer_balances',
        verbose_name='所属商户',
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='balances',
        verbose_name='客户',
    )
    balance = models.DecimalField(max_digits=15, decimal_places=2, default=Decimal('0'), verbose_name='应收余额')

    class Meta:
        verbose_name = '客户余额'
        verbose_name_plural = '客户余额'
        unique_together = ('merchant', 'customer')


class BalanceChangeTargetEnum(models.IntegerChoices):
    SUPPLIER = 1, '供应商'
    CUSTOMER = 2, '客户'


class BalanceChangeSourceEnum(models.IntegerChoices):
    PURCHASE_ORDER = 1, '采购单'
    SALES_ORDER = 2, '销售单'
    PAYMENT_ORDER = 3, '付款单'
    RECEIPT_ORDER = 4, '收款单'
    PURCHASE_RETURN_ORDER = 5, '采购退货单'
    SALES_RETURN_ORDER = 6, '销售退货单'


class BalanceChangeDirectionEnum(models.IntegerChoices):
    INCREASE = 1, '增加'
    DECREASE = 2, '减少'


class BalanceChangeRecord(ModelBase):
    merchant = models.ForeignKey(
        basic_info_models.Merchant,
        on_delete=models.PROTECT,
        related_name='balance_change_records',
        verbose_name='所属商户',
    )
    supplier = models.ForeignKey(
        basic_info_models.Supplier,
        on_delete=models.PROTECT,
        related_name='balance_change_records',
        verbose_name='供应商',
        null=True,
        blank=True,
    )
    customer = models.ForeignKey(
        basic_info_models.Customer,
        on_delete=models.PROTECT,
        related_name='balance_change_records',
        verbose_name='客户',
        null=True,
        blank=True,
    )
    target_type = models.IntegerField(
        choices=BalanceChangeTargetEnum.choices,
        verbose_name='业务主体类型',
    )
    source_type = models.IntegerField(
        choices=BalanceChangeSourceEnum.choices,
        verbose_name='来源业务类型',
    )
    source_id = models.BigIntegerField(verbose_name='来源业务ID')
    delta = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='变动金额')
    direction = models.IntegerField(
        choices=BalanceChangeDirectionEnum.choices,
        verbose_name='方向',
    )
    balance_before = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='变动前余额')
    balance_after = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='变动后余额')
    request_id = models.CharField(max_length=64, null=True, blank=True, verbose_name='幂等请求ID')
    remarks = models.TextField(blank=True, null=True, verbose_name='备注')
    extra_meta = models.JSONField(default=dict, blank=True, verbose_name='扩展信息')
    offset_to = models.BigIntegerField(null=True, blank=True, verbose_name='冲抵目标ID')
    offset_at = models.DateTimeField(null=True, blank=True, verbose_name='冲抵时间')
    offset_id = models.BigIntegerField(null=True, blank=True, verbose_name='冲抵来源ID')
    cancelled = models.BooleanField(default=False, verbose_name='已被冲抵')
    cancelled_at = models.DateTimeField(null=True, blank=True, verbose_name='被冲抵时间')
    red_flush_id = models.UUIDField(null=True, blank=True, db_index=True, verbose_name='红冲批次ID')

    class Meta:
        verbose_name = '余额变动记录'
        verbose_name_plural = '余额变动记录'
        indexes = [
            models.Index(fields=['merchant', 'source_type', 'source_id'], name='balance_change_source_idx'),
        ]
        constraints = [
            models.CheckConstraint(
                check=(
                    (models.Q(supplier__isnull=False, customer__isnull=True))
                    | (models.Q(supplier__isnull=True, customer__isnull=False))
                ),
                name='balance_change_single_counterparty',
            ),
        ]

    def __str__(self):
        counterparty = self.supplier or self.customer
        return f'余额变动 {self.id} - {counterparty}'
