from . import models
from django.db import transaction
from django.db.models import Sum
from django.utils import timezone
from sse.services import push_simple_message_with_object_id
from basic_info import models as basic_models
from decimal import Decimal, InvalidOperation, ROUND_DOWN
import logging
from typing import List, Dict, Any, Tuple


logger = logging.getLogger(__name__)


def _raise_unimplemented_mode():
    raise ValueError('仓库出入库模式为【严进严出】，该模式暂未支持当前操作')


def _ensure_strict_mode(warehouse: basic_models.WareHouse, *, allow_restrict_in_out: bool = False):
    if warehouse.mode == basic_models.WareHouseModeEnum.RESTRICT_IN:
        return
    if warehouse.mode == basic_models.WareHouseModeEnum.RESTRICT_IN_OUT:
        if allow_restrict_in_out:
            return
        _raise_unimplemented_mode()
    raise ValueError('仓库出入库模式为【宽进宽出】，请使用宽松模式接口创建出入库记录')


def _ensure_relaxed_mode(warehouse: basic_models.WareHouse):
    if warehouse.mode == basic_models.WareHouseModeEnum.UNRESTRICTED:
        return
    if warehouse.mode == basic_models.WareHouseModeEnum.RESTRICT_IN_OUT:
        _raise_unimplemented_mode()
    raise ValueError('仓库出入库模式为【严进宽出】，请使用严谨模式接口创建出入库记录')


def _create_details_from_quantities(
    *,
    stock_change_record: models.StockChangeRecord,
    merchant: basic_models.Merchant,
    products: List[Dict[str, Any]],
) -> Tuple[List[models.StockChangeDetail], int]:
    created_details: List[models.StockChangeDetail] = []
    created_count = 0

    for product_data in products:
        product_id = product_data.get('product')
        quantities = product_data.get('quantity') or []

        if not product_id or not quantities:
            raise ValueError('产品数据不完整，缺少 product 或 quantity')

        try:
            product = basic_models.Product.objects.get(id=product_id)
        except basic_models.Product.DoesNotExist:
            raise ValueError(f'产品ID {product_id} 不存在')

        if product.merchant_id != merchant.id:
            raise ValueError(f'产品ID {product_id} 不属于当前商户')

        for quantity in quantities:
            detail = models.StockChangeDetail.objects.create(
                stock_change_record=stock_change_record,
                product=product,
                quantity=quantity,
                merchant=merchant,
                unit=product.unit,
            )
            created_details.append(detail)
            created_count += 1

    return created_details, created_count


def _create_consumption_details(
    *,
    stock_change_record: models.StockChangeRecord,
    merchant: basic_models.Merchant,
    products: List[Dict[str, Any]],
) -> List[models.StockChangeDetail]:
    created_details: List[models.StockChangeDetail] = []

    for product_data in products:
        product_id = product_data.get('product')
        consume_ids = product_data.get('consume_with') or []

        if not product_id or not consume_ids:
            raise ValueError('严进严出出库必须提供 consume_with 列表')

        try:
            product = basic_models.Product.objects.get(id=product_id)
        except basic_models.Product.DoesNotExist:
            raise ValueError(f'产品ID {product_id} 不存在')

        if product.merchant_id != merchant.id:
            raise ValueError(f'产品ID {product_id} 不属于当前商户')

        inbound_details = models.StockChangeDetail.objects.select_related('stock_change_record').filter(
            id__in=consume_ids
        )
        if inbound_details.count() != len(consume_ids):
            raise ValueError('部分入库明细不存在')

        inbound_map = {detail.id: detail for detail in inbound_details}

        for detail_id in consume_ids:
            inbound_detail = inbound_map.get(detail_id)
            if inbound_detail is None:
                raise ValueError('部分入库明细不存在')
            if inbound_detail.stock_change_record.warehouse_id != stock_change_record.warehouse_id:
                raise ValueError('入库明细与当前仓库不一致')
            if inbound_detail.stock_change_record.merchant_id != merchant.id:
                raise ValueError('入库明细与当前商户不一致')
            if not inbound_detail.stock_change_record.is_incoming:
                raise ValueError('只能引用入库明细进行出库')
            if inbound_detail.is_consumed:
                raise ValueError(f'入库明细 {inbound_detail.id} 已被消耗')
            if inbound_detail.product_id != product_id:
                raise ValueError('入库明细与当前产品不匹配')

            detail = models.StockChangeDetail.objects.create(
                stock_change_record=stock_change_record,
                product=product,
                quantity=inbound_detail.quantity,
                merchant=merchant,
                unit=inbound_detail.unit,
                consume_with=inbound_detail,
            )
            inbound_detail.is_consumed = True
            inbound_detail.save(update_fields=['is_consumed'])
            created_details.append(detail)

    return created_details


def create_stock_change_record_with_details(
    *,
    merchant: basic_models.Merchant,
    created_by,
    type: int,
    warehouse_id: int,
    source_type: int,
    source_id: int | None = None,
    products: List[Dict[str, Any]] | None = None,
) -> Tuple[models.StockChangeRecord, List[models.StockChangeDetail], int]:
    """
    创建库存变动记录及其明细

    参数:
        merchant: 当前商户
        created_by: 操作人（可为空）
        type: 出入库类型
        warehouse_id: 仓库ID
        source_type: 来源类型
        source_id: 来源单据ID，可选
        products: 产品及数量列表
    """

    if not merchant:
        raise ValueError('必须提供商户信息')
    if not warehouse_id:
        raise ValueError('必须提供仓库ID')
    if not products:
        raise ValueError('产品列表不能为空')

    try:
        warehouse = basic_models.WareHouse.objects.get(id=warehouse_id)
    except basic_models.WareHouse.DoesNotExist:
        raise ValueError(f'仓库ID {warehouse_id} 不存在')

    if warehouse.merchant_id != merchant.id:
        raise ValueError('仓库不属于当前商户')

    allow_restrict_in_out = (
        warehouse.mode == basic_models.WareHouseModeEnum.RESTRICT_IN_OUT
        and type == models.StockChangeTypeEnum.ADD
    )
    is_strict_outgoing = (
        warehouse.mode == basic_models.WareHouseModeEnum.RESTRICT_IN_OUT
        and type == models.StockChangeTypeEnum.REMOVE
    )
    if not is_strict_outgoing:
        _ensure_strict_mode(warehouse, allow_restrict_in_out=allow_restrict_in_out)

    created_details: List[models.StockChangeDetail] = []
    created_count = 0

    with transaction.atomic():
        stock_change_record = models.StockChangeRecord.objects.create(
            type=type,
            warehouse=warehouse,
            source_type=source_type,
            source_id=source_id,
            merchant=merchant,
            created_by=created_by,
        )
        logger.info('创建新库存变动记录 ID: %s', stock_change_record.id)

        if is_strict_outgoing:
            created_details = _create_consumption_details(
                stock_change_record=stock_change_record,
                merchant=merchant,
                products=products,
            )
            created_count = len(created_details)
        else:
            created_details, created_count = _create_details_from_quantities(
                stock_change_record=stock_change_record,
                merchant=merchant,
                products=products,
            )

        if warehouse.merchant.auto_complete_stock_change:
            make_stock_change_completed(stock_change_record)
            logger.info('自动确认库存变动记录 ID: %s', stock_change_record.id)

    return stock_change_record, created_details, created_count


def _to_decimal(value, field_name: str) -> Decimal:
    try:
        return Decimal(str(value))
    except (InvalidOperation, TypeError):
        raise ValueError(f'{field_name} 必须是合法的数值')


def _to_positive_int(value, field_name: str) -> int:
    if value is None:
        raise ValueError(f'{field_name} 不能为空')
    decimal_value = _to_decimal(value, field_name)
    if decimal_value <= 0:
        raise ValueError(f'{field_name} 必须大于 0')
    if decimal_value != decimal_value.to_integral_value():
        raise ValueError(f'{field_name} 必须为整数')
    return int(decimal_value)


def _split_quantities(total: Decimal, unit_size: Decimal) -> List[Decimal]:
    if total <= 0:
        raise ValueError('quantity.value 必须大于 0')
    if unit_size <= 0:
        raise ValueError('quantity.unit_count 必须大于 0')

    quantities: List[Decimal] = []
    num_full = int(total // unit_size)
    remainder = total % unit_size

    if num_full == 0:
        quantities.append(total)
    else:
        quantities.extend([unit_size] * num_full)
        if remainder > 0:
            quantities.append(remainder)

    return quantities


def _split_quantities_evenly(total: Decimal, rolls: int) -> List[Decimal]:
    if total <= 0:
        raise ValueError('quantity.value 必须大于 0')
    if rolls <= 0:
        raise ValueError('quantity.num_of_rolls 必须大于 0')

    quantum = Decimal('0.01')
    base = (total / Decimal(rolls)).quantize(quantum, rounding=ROUND_DOWN)
    if base == 0 and total < quantum:
        return [total]

    quantities: List[Decimal] = [base for _ in range(rolls)]
    distributed = base * rolls
    remainder = (total - distributed).quantize(quantum)

    idx = 0
    while remainder > 0 and quantities:
        increment = min(quantum, remainder)
        quantities[idx] += increment
        remainder -= increment
        idx = (idx + 1) % rolls

    return [qty for qty in quantities if qty > 0]


def create_stock_change_record_relaxed(
    *,
    merchant: basic_models.Merchant,
    created_by,
    type: int,
    warehouse_id: int,
    source_type: int,
    source_id: int | None = None,
    products: List[Dict[str, Any]] | None = None,
) -> Tuple[models.StockChangeRecord, List[models.StockChangeDetail], int]:
    """
    宽松模式：根据总数量和单条数量自动拆分明细
    """

    if not merchant:
        raise ValueError('必须提供商户信息')
    if not warehouse_id:
        raise ValueError('必须提供仓库ID')
    if not products:
        raise ValueError('产品列表不能为空')

    try:
        warehouse = basic_models.WareHouse.objects.get(id=warehouse_id)
    except basic_models.WareHouse.DoesNotExist:
        raise ValueError(f'仓库ID {warehouse_id} 不存在')

    if warehouse.merchant_id != merchant.id:
        raise ValueError('仓库不属于当前商户')
    _ensure_relaxed_mode(warehouse)

    created_details: List[models.StockChangeDetail] = []

    with transaction.atomic():
        stock_change_record = models.StockChangeRecord.objects.create(
            type=type,
            warehouse=warehouse,
            source_type=source_type,
            source_id=source_id,
            merchant=merchant,
            created_by=created_by,
        )
        logger.info('创建宽松模式库存变动记录 ID: %s', stock_change_record.id)

        for product_data in products:
            product_id = product_data.get('product')
            quantity_data = product_data.get('quantity') or {}

            if not product_id:
                raise ValueError('产品ID不能为空')

            total_value = _to_decimal(quantity_data.get('value'), 'quantity.value')
            roll_count = quantity_data.get('num_of_rolls')
            unit_size = quantity_data.get('unit_count', 1)

            try:
                product = basic_models.Product.objects.get(id=product_id)
            except basic_models.Product.DoesNotExist:
                raise ValueError(f'产品ID {product_id} 不存在')

            if product.merchant_id != merchant.id:
                raise ValueError(f'产品ID {product_id} 不属于当前商户')

            if roll_count is not None:
                roll_count = _to_positive_int(roll_count, 'quantity.num_of_rolls')
                quantities = _split_quantities_evenly(total_value, roll_count)
            else:
                unit_size = _to_decimal(unit_size, 'quantity.unit_count')
                quantities = _split_quantities(total_value, unit_size)

            for quantity in quantities:
                detail = models.StockChangeDetail.objects.create(
                    stock_change_record=stock_change_record,
                    product=product,
                    quantity=quantity,
                    merchant=merchant,
                    unit=product.unit,
                )
                created_details.append(detail)

        if warehouse.merchant.auto_complete_stock_change:
            make_stock_change_completed(stock_change_record)
            logger.info('自动确认宽松模式库存变动记录 ID: %s', stock_change_record.id)

    return stock_change_record, created_details, len(created_details)


class StockFlowService:
    """
    统一出入库服务，屏蔽仓库模式差异。

    items 结构支持以下字段：
        - product_id: 产品ID，必填
        - quantities: List[Decimal | str]，用于严谨/严进严出入库
        - value: Decimal | str，总数量，用于宽进宽出
        - num_of_rolls: Decimal | int，每条数量，用于宽进宽出
        - consume_detail_ids: List[int]，严进严出出库消耗的入库明细ID
    """

    def __init__(self, *, merchant: basic_models.Merchant, created_by):
        if merchant is None:
            raise ValueError('StockFlowService 初始化必须提供商户')
        self.merchant = merchant
        self.created_by = created_by

    def stock_in(
        self,
        *,
        warehouse_id: int,
        source_type: int,
        source_id: int | None = None,
        items: List[Dict[str, Any]],
    ):
        warehouse = self._get_and_validate_warehouse(warehouse_id)
        products = self._build_products_payload(warehouse, is_incoming=True, items=items)
        return self._create_record(
            warehouse=warehouse,
            record_type=models.StockChangeTypeEnum.ADD,
            source_type=source_type,
            source_id=source_id,
            products=products,
        )

    def stock_out(
        self,
        *,
        warehouse_id: int,
        source_type: int,
        source_id: int | None = None,
        items: List[Dict[str, Any]],
    ):
        warehouse = self._get_and_validate_warehouse(warehouse_id)
        products = self._build_products_payload(warehouse, is_incoming=False, items=items)
        return self._create_record(
            warehouse=warehouse,
            record_type=models.StockChangeTypeEnum.REMOVE,
            source_type=source_type,
            source_id=source_id,
            products=products,
        )

    def offset_stock_change(
        self,
        *,
        source_record_id: int,
        reason: str,
        items: List[Dict[str, Any]] | None = None,
        request_id: str | None = None,
        extra_meta: Dict[str, Any] | None = None,
        red_flush_id=None,
    ) -> Tuple[models.StockChangeRecord, List[models.StockChangeDetail], int]:
        """
        创建针对既有库存变动记录的红冲（对冲）占位接口。

        Args:
            source_record_id: 需要被红冲的库存变动记录 ID。
            reason: 红冲原因，必须明确记录以便审计。
            items: 可选的部分红冲明细（支持宽进/严进等结构）；为空时表示全量对冲。
            request_id: 幂等键；同一 request_id 的请求应视为一次操作。
            extra_meta: 额外的上下文信息（触发来源、操作者备注等）。
            red_flush_id: 业务红冲批次 ID，用于跨模块审计追踪。

        Returns:
            新生成的反向 `StockChangeRecord`、其明细列表以及明细数量。
        """
        if not reason or not reason.strip():
            raise ValueError('reason 不能为空')
        if items:
            raise ValueError('当前版本暂不支持部分红冲，请省略 items 参数')

        offset_record, created_details = self._perform_stock_offset(
            source_record_id=source_record_id,
            reason=reason.strip(),
            request_id=request_id,
            extra_meta=extra_meta or {},
            red_flush_id=red_flush_id,
        )
        return offset_record, created_details, len(created_details)

    def _perform_stock_offset(
        self,
        *,
        source_record_id: int,
        reason: str,
        request_id: str | None,
        extra_meta: Dict[str, Any],
        red_flush_id,
    ) -> Tuple[models.StockChangeRecord, List[models.StockChangeDetail]]:
        with transaction.atomic():
            source_record = (
                models.StockChangeRecord.objects.select_related('warehouse', 'merchant')
                .prefetch_related('details__product', 'details__consume_with')
                .select_for_update()
                .get(id=source_record_id)
            )

            if source_record.merchant_id != self.merchant.id:
                raise ValueError('库存变动记录不属于当前商户')
            if not source_record.is_finished:
                raise ValueError('仅允许对已完成的库存变动执行红冲')

            if models.StockSnapshot.objects.filter(
                stock_change_record=source_record,
                offset_id__isnull=False,
            ).exists():
                raise ValueError('该库存变动记录已执行红冲')

            source_details = list(source_record.details.all().order_by('id'))
            if not source_details:
                raise ValueError('库存变动记录缺少明细，无法红冲')

            reverse_type = (
                models.StockChangeTypeEnum.ADD
                if source_record.is_outgoing
                else models.StockChangeTypeEnum.REMOVE
            )

            remarks = f'红冲原记录 {source_record.id}: {reason}'
            if request_id:
                remarks = f'[{request_id}] {remarks}'

            offset_record = models.StockChangeRecord.objects.create(
                merchant=self.merchant,
                type=reverse_type,
                warehouse=source_record.warehouse,
                source_type=models.StockChangeSourceEnum.OFFSET,
                source_id=source_record.id,
                created_by=self.created_by,
                red_flush_id=red_flush_id,
                remarks=remarks[:500],
            )

            if red_flush_id and source_record.red_flush_id is None:
                source_record.red_flush_id = red_flush_id
                source_record.save(update_fields=['red_flush_id', 'updated_at'])

            created_details: List[models.StockChangeDetail] = []
            for detail in source_details:
                new_detail = models.StockChangeDetail.objects.create(
                    merchant=self.merchant,
                    stock_change_record=offset_record,
                    product=detail.product,
                    quantity=detail.quantity,
                    unit=detail.unit,
                )
                created_details.append(new_detail)

            if source_record.is_outgoing:
                consume_ids = [detail.consume_with_id for detail in source_details if detail.consume_with_id]
                if consume_ids:
                    models.StockChangeDetail.objects.filter(id__in=consume_ids).update(is_consumed=False)

            if not make_stock_change_completed(offset_record):
                raise ValueError('红冲库存记录创建失败')

            self._link_offset_snapshots(
                source_record=source_record,
                offset_record=offset_record,
            )

        logger.info('库存变动记录 %s 已红冲，生成记录 %s', source_record_id, offset_record.id)
        return offset_record, created_details

    @staticmethod
    def _link_offset_snapshots(
        *,
        source_record: models.StockChangeRecord,
        offset_record: models.StockChangeRecord,
    ):
        original_snapshots = list(
            models.StockSnapshot.objects.filter(stock_change_record=source_record).order_by('id')
        )
        if not original_snapshots:
            return

        new_snapshots = list(
            models.StockSnapshot.objects.filter(stock_change_record=offset_record).order_by('id')
        )
        if not new_snapshots:
            raise ValueError('红冲记录未生成任何库存快照')

        now = timezone.now()
        pair_count = min(len(original_snapshots), len(new_snapshots))

        for index in range(pair_count):
            original_snapshot = original_snapshots[index]
            offset_snapshot = new_snapshots[index]

            original_snapshot.cancelled = True
            original_snapshot.cancelled_at = now
            original_snapshot.offset_id = offset_snapshot.id
            original_snapshot.save(update_fields=['cancelled', 'cancelled_at', 'offset_id'])

            offset_snapshot.offset_to = original_snapshot.id
            offset_snapshot.offset_at = now
            offset_snapshot.save(update_fields=['offset_to', 'offset_at'])

        if len(original_snapshots) != len(new_snapshots):
            logger.warning(
                '红冲快照数量不一致：原始 %s 条，新快照 %s 条',
                len(original_snapshots),
                len(new_snapshots),
            )

    def _get_and_validate_warehouse(self, warehouse_id: int) -> basic_models.WareHouse:
        if not warehouse_id:
            raise ValueError('必须提供仓库ID')
        try:
            warehouse = basic_models.WareHouse.objects.get(id=warehouse_id)
        except basic_models.WareHouse.DoesNotExist:
            raise ValueError(f'仓库ID {warehouse_id} 不存在')
        if warehouse.merchant_id != self.merchant.id:
            raise ValueError('仓库不属于当前商户')
        return warehouse

    def _create_record(
        self,
        *,
        warehouse: basic_models.WareHouse,
        record_type: int,
        source_type: int,
        source_id: int | None,
        products: List[Dict[str, Any]],
    ):
        common_kwargs = dict(
            merchant=self.merchant,
            created_by=self.created_by,
            type=record_type,
            warehouse_id=warehouse.id,
            source_type=source_type,
            source_id=source_id,
            products=products,
        )

        if warehouse.mode == basic_models.WareHouseModeEnum.UNRESTRICTED:
            return create_stock_change_record_relaxed(**common_kwargs)

        # 严谨或严进严出模式统一由严谨服务处理
        return create_stock_change_record_with_details(**common_kwargs)

    def _build_products_payload(
        self,
        warehouse: basic_models.WareHouse,
        *,
        is_incoming: bool,
        items: List[Dict[str, Any]],
    ) -> List[Dict[str, Any]]:
        if not items:
            raise ValueError('产品明细不能为空')

        mode = warehouse.mode
        if mode == basic_models.WareHouseModeEnum.UNRESTRICTED:
            return self._build_relaxed_items(items)

        if mode == basic_models.WareHouseModeEnum.RESTRICT_IN_OUT and not is_incoming:
            return self._build_restrict_out_items(items)

        # 严谨模式 + 严进严出入库复用数量列表
        return self._build_strict_items(items)

    @staticmethod
    def _build_strict_items(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        payload: List[Dict[str, Any]] = []
        for item in items:
            product_id = item.get('product_id')
            quantities = item.get('quantities') or []
            if not product_id or not quantities:
                raise ValueError('严谨模式需要提供 product_id 与 quantities 列表')
            payload.append({'product': product_id, 'quantity': quantities})
        return payload

    @staticmethod
    def _build_relaxed_items(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        payload: List[Dict[str, Any]] = []
        for item in items:
            product_id = item.get('product_id')
            total_value = item.get('value')
            roll_count = item.get('num_of_rolls')
            unit_size = item.get('unit_size') or item.get('unit_count')
            if not product_id or total_value is None:
                raise ValueError('宽进宽出需要提供 product_id 与 value')

            quantity_payload: Dict[str, Any] = {'value': total_value}
            if roll_count is not None:
                quantity_payload['num_of_rolls'] = roll_count
            elif unit_size is not None:
                quantity_payload['unit_count'] = unit_size
            else:
                quantity_payload['unit_count'] = 1

            payload.append({'product': product_id, 'quantity': quantity_payload})
        return payload

    @staticmethod
    def _build_restrict_out_items(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        payload: List[Dict[str, Any]] = []
        for item in items:
            product_id = item.get('product_id')
            consume_ids = item.get('consume_detail_ids') or []
            if not product_id or not consume_ids:
                raise ValueError('严进严出出库需要提供 product_id 与 consume_detail_ids')
            payload.append({
                'product': product_id,
                'consume_with': consume_ids,
            })
        return payload


def _aggregate_transfer_details(details: List[models.StockChangeDetail]) -> Dict[int, Dict[str, Any]]:
    aggregated: Dict[int, Dict[str, Any]] = {}
    for detail in details:
        product_id = detail.product_id
        if product_id not in aggregated:
            aggregated[product_id] = {
                'product': detail.product,
                'unit': detail.unit,
                'total': Decimal('0'),
                'num_of_rolls': 0,
            }
        aggregated[product_id]['total'] += Decimal(detail.quantity)
        aggregated[product_id]['num_of_rolls'] += 1
    return aggregated


def create_transfer_order(
    *,
    merchant: basic_models.Merchant,
    created_by,
    operator: basic_models.Employee,
    from_warehouse_id: int,
    to_warehouse_id: int,
    items: List[Dict[str, Any]],
    transfer_date=None,
    remarks: str | None = '',
    request_id: str | None = None,
) -> models.TransferOrder:
    if not merchant:
        raise ValueError('必须提供商户信息')
    if operator is None:
        raise ValueError('必须提供调拨经办人')
    if operator.merchant_id != merchant.id:
        raise ValueError('经办人不属于当前商户')
    if not items:
        raise ValueError('调拨产品明细不能为空')
    if not from_warehouse_id or not to_warehouse_id:
        raise ValueError('必须提供调出仓和调入仓')
    if from_warehouse_id == to_warehouse_id:
        raise ValueError('调入仓与调出仓不能相同')

    transfer_date = transfer_date or timezone.now().date()

    if request_id:
        existing = models.TransferOrder.objects.filter(merchant=merchant, request_id=request_id).first()
        if existing:
            return existing

    try:
        from_warehouse = basic_models.WareHouse.objects.get(id=from_warehouse_id, merchant=merchant)
    except basic_models.WareHouse.DoesNotExist:
        raise ValueError('调出仓不存在或不属于当前商户')

    try:
        to_warehouse = basic_models.WareHouse.objects.get(id=to_warehouse_id, merchant=merchant)
    except basic_models.WareHouse.DoesNotExist:
        raise ValueError('调入仓不存在或不属于当前商户')

    if from_warehouse.mode != to_warehouse.mode:
        raise ValueError('调拨要求调出仓与调入仓的出入库模式一致')
    if from_warehouse.mode == basic_models.WareHouseModeEnum.RESTRICT_IN_OUT:
        raise ValueError('调拨暂不支持严进严出模式，请先转为其他模式')

    flow_service = StockFlowService(merchant=merchant, created_by=created_by)

    with transaction.atomic():
        transfer_order = models.TransferOrder.objects.create(
            merchant=merchant,
            from_warehouse=from_warehouse,
            to_warehouse=to_warehouse,
            operator=operator,
            created_by=created_by,
            transfer_date=transfer_date,
            status=models.TransferOrderStatusEnum.DRAFT,
            remarks=remarks or '',
            mode=from_warehouse.mode,
            request_id=request_id,
        )

        out_record, out_details, _ = flow_service.stock_out(
            warehouse_id=from_warehouse.id,
            source_type=models.StockChangeSourceEnum.TRANSPORT_OUT,
            source_id=transfer_order.id,
            items=items,
        )
        in_record, _, _ = flow_service.stock_in(
            warehouse_id=to_warehouse.id,
            source_type=models.StockChangeSourceEnum.TRANSPORT_IN,
            source_id=transfer_order.id,
            items=items,
        )

        make_stock_change_completed(out_record)
        make_stock_change_completed(in_record)

        aggregated = _aggregate_transfer_details(out_details)
        transfer_items = [
            models.TransferOrderItem(
                transfer_order=transfer_order,
                product=data['product'],
                unit=data['unit'],
                total_quantity=data['total'],
                num_of_rolls=data['num_of_rolls'],
            )
            for data in aggregated.values()
        ]
        if transfer_items:
            models.TransferOrderItem.objects.bulk_create(transfer_items)

        transfer_order.outgoing_record = out_record
        transfer_order.incoming_record = in_record
        transfer_order.status = models.TransferOrderStatusEnum.COMPLETED
        transfer_order.save(update_fields=['outgoing_record', 'incoming_record', 'status'])

    return transfer_order


def find_inventory(product_id: int, warehouse_id: int) -> models.Inventory | None:
    """根据产品ID和仓库ID查找库存记录"""

    try:
        inventory = models.Inventory.objects.get(
            product_id=product_id,
            warehouse_id=warehouse_id,
        )
        return inventory
    except models.Inventory.DoesNotExist:
        return None


def create_stock_snapshot(stock_change_detail: models.StockChangeDetail, inventory: models.Inventory) -> models.StockSnapshot:
    """创建库存快照记录"""

    if not stock_change_detail or stock_change_detail.id is None:
        raise ValueError("无效的库存变动明细记录")

    delta = stock_change_detail.quantity if stock_change_detail.stock_change_record.is_incoming else -stock_change_detail.quantity

    # 必须在库存变动发生后创建快照
    snapshot = models.StockSnapshot(
        merchant_id=stock_change_detail.merchant_id,
        product_id=inventory.product_id,
        warehouse_id=inventory.warehouse_id,
        delta=delta,
        quantity_before=inventory.quantity - delta,
        quantity_after=inventory.quantity,
        stock_change_record_id=stock_change_detail.stock_change_record.id,
        unit=stock_change_detail.unit,
        num_of_rolls=inventory.num_of_rolls,
    )
    snapshot.save()
    logger.info(
        f'创建库存快照，库存ID: {inventory.id}, 数量: {inventory.quantity}, '
        f'匹数: {inventory.num_of_rolls}'
    )
    return snapshot


def make_stock_change_completed(stock_change_record: models.StockChangeRecord) -> bool:
    """处理库存变动完成后的逻辑, 实际扣减库存也在这里发生"""
    # TODO: 这里可以添加事务处理以确保数据一致性

    if not stock_change_record or stock_change_record.id is None:
        # 无效的库存变动记录
        return False

    if stock_change_record.is_finished:
        return True


    # TODO: 逻辑更改 = 出入库单现在不记录库存总量了
    # 需要根据产品分别统计变动数量（要考虑不同单位）

    for detail in stock_change_record.details.all():
        detail: models.StockChangeDetail

        logger.info(
            f'处理库存变动明细，产品ID: {detail.product_id}, '
            f'数量: {detail.quantity}, 单位: {detail.get_unit_display()}'
        )

        inventory_record = find_inventory(
            product_id=detail.product_id,
            warehouse_id=stock_change_record.warehouse_id,
        )

        # 确定方向
        positive = 1 if stock_change_record.is_incoming else -1

        if inventory_record:
            # 更新现有库存记录
            inventory_record.quantity += detail.quantity * positive
            inventory_record.num_of_rolls += positive
            logger.info(
                f'更新库存记录 {inventory_record.id}，新数量: {inventory_record.quantity}, '
                f'新匹数: {inventory_record.num_of_rolls}'
            )
        else:
            # 创建新库存记录
            inventory_record = models.Inventory(
                merchant_id=stock_change_record.merchant_id,
                product_id=detail.product_id,
                warehouse_id=stock_change_record.warehouse_id,
                quantity=detail.quantity * positive,
                num_of_rolls=1 if stock_change_record.is_incoming else -1,
            )
            logger.info(
                f'创建新库存记录 {inventory_record.id}，数量: {inventory_record.quantity}, '
                f'匹数: {inventory_record.num_of_rolls}'
            )
        inventory_record.save()
        # 发送库存变动通知
        push_simple_message_with_object_id(
            event_type='stock_change',
            message=f'产品ID {detail.product_id} 位于 {stock_change_record.warehouse_id} 的库存已更新',
            object_id=inventory_record.id
        )

        # 无论库存记录原本是否存在都创建库存快照
        create_stock_snapshot(detail, inventory_record)

    # 所有明细处理完毕，标记库存变动记录为已完成
    stock_change_record.is_finished = True
    stock_change_record.finished_at = timezone.now()
    stock_change_record.save()
    logger.info(
        f'库存变动记录 {stock_change_record.id} 标记为已完成。'
    )
    return True


def get_frozen_stock_count(product_id: int, warehouse_id: int) -> int:
    """获取指定产品在指定仓库的冻结库存数量"""
    total_frozen = models.StockFreeze.objects.filter(
        product_id=product_id,
        warehouse_id=warehouse_id,
        status=models.StockFreezeStatusEnum.FROZEN
    ).aggregate(total=Sum('quantity'))['total'] or 0
    return total_frozen
