from __future__ import annotations

from decimal import Decimal, InvalidOperation
from typing import Any, Dict, List, Optional

from django.conf import settings
from django.db import transaction
from django.core.exceptions import ValidationError
from django.utils import timezone

from basic_info import models as basic_info_models
from stock import models as stock_models

from . import models
from . import services as business_services
from .notifications import enqueue_business_order_speech


def render_pre_sales_order_created_markdown(
    *,
    pre_sales_order_id: str,
    human_id: str,
    created_at: str,
    sender_label: str,
    customer_name: str,
    warehouse_name: str,
    kind: str,
    items_count: int,
) -> str:
    followup_url_template = getattr(
        settings,
        "PRE_SALES_ORDER_CREATED_FOLLOWUP_URL_TEMPLATE",
        "",
    )
    followup_url = ""
    if followup_url_template:
        try:
            followup_url = str(followup_url_template).format(
                pre_sales_order_id=str(pre_sales_order_id or "")
            )
        except Exception:
            followup_url = ""
    followup_line = (
        f"- **跟进**：[点击跟进]({followup_url})\n"
        f"  {followup_url}\n"
        if followup_url
        else ""
    )

    template = getattr(
        settings,
        "PRE_SALES_ORDER_CREATED_WECOM_MARKDOWN_TEMPLATE",
        (
            "### 预销售单创建\n"
            "\n"
            "- **预销售单ID**：`{pre_sales_order_id}`\n"
            "- **订单编号**：`{human_id}`\n"
            "- **创建时间**：`{created_at}`\n"
            "- **发送者**：{sender_label}\n"
            "- **客户**：{customer_name}\n"
            "- **仓库**：{warehouse_name}\n"
            "- **类型**：{kind}\n"
            "- **明细条数**：`{items_count}`\n"
            "{followup_line}"
        ),
    )

    created_at_str = (created_at or "").strip()
    if not created_at_str:
        created_at_str = timezone.localtime(timezone.now()).strftime("%Y-%m-%d %H:%M:%S")

    return template.format(
        pre_sales_order_id=str(pre_sales_order_id or "-") or "-",
        human_id=str(human_id or "-") or "-",
        created_at=str(created_at_str),
        sender_label=str(sender_label or "系统自动发送"),
        customer_name=str(customer_name or "-"),
        warehouse_name=str(warehouse_name or "-"),
        kind=str(kind or "-"),
        items_count=int(items_count) if items_count is not None else 0,
        followup_line=str(followup_line),
    )


def _to_decimal(value: Any, *, field_name: str) -> Decimal:
    if value is None or value == '':
        raise ValueError(f'{field_name} 不能为空')
    try:
        return Decimal(str(value))
    except (InvalidOperation, ValueError) as exc:
        raise ValueError(f'{field_name} 格式不正确') from exc


def _to_optional_int(value: Any) -> Optional[int]:
    if value is None or value == '':
        return None
    try:
        parsed = int(value)
    except (TypeError, ValueError) as exc:
        raise ValueError('order_quantity 必须为整数') from exc
    if parsed < 0:
        raise ValueError('order_quantity 不能为负数')
    return parsed


def _to_optional_positive_int(value: Any, *, field_name: str) -> Optional[int]:
    if value is None or value == '':
        return None
    try:
        parsed = int(value)
    except (TypeError, ValueError) as exc:
        raise ValueError(f'{field_name} 必须为整数') from exc
    if parsed <= 0:
        raise ValueError(f'{field_name} 必须大于 0')
    return parsed


def _normalize_pre_sales_items(
    *,
    merchant: basic_info_models.Merchant,
    items: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
    if not isinstance(items, list) or not items:
        raise ValueError('items 需要为非空数组')

    product_ids: List[int] = []
    for item in items:
        if not isinstance(item, dict):
            raise ValueError('items 每一项需要为对象')
        product_id = item.get('product_id') or item.get('product')
        if not product_id:
            raise ValueError('明细缺少 product_id')
        try:
            product_ids.append(int(product_id))
        except (TypeError, ValueError) as exc:
            raise ValueError('product_id 必须为整数') from exc

    products_by_id = basic_info_models.Product.objects.filter(
        merchant=merchant,
        id__in=product_ids,
    ).in_bulk(field_name='id')

    normalized: List[Dict[str, Any]] = []
    for item in items:
        product_id = int(item.get('product_id') or item.get('product'))
        product = products_by_id.get(product_id)
        if not product:
            raise ValueError(f'产品 {product_id} 不存在')

        quantity = _to_decimal(item.get('quantity'), field_name='quantity')
        unit = (item.get('unit') or '').strip()
        if not unit:
            unit = str(product.unit)
        if not unit:
            raise ValueError('unit 不能为空')

        normalized.append(
            {
                'product_id': product_id,
                'product_name': (item.get('product_name') or product.name or '').strip(),
                'color': item.get('color'),
                'quantity': quantity,
                'unit': unit,
                'spec': item.get('spec'),
                'quantity_of_rolls': item.get('quantity_of_rolls'),
                'num_of_rolls': _to_optional_positive_int(item.get('num_of_rolls'), field_name='num_of_rolls'),
                'order_quantity': _to_optional_int(item.get('order_quantity')),
                'remarks': item.get('remarks'),
            }
        )

    return normalized


def _normalize_pre_purchase_items(
    *,
    merchant: basic_info_models.Merchant,
    items: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
    # 与预销售单明细字段保持一致的弱关联产品输入结构
    return _normalize_pre_sales_items(merchant=merchant, items=items)


def list_pre_sales_orders(*, merchant: basic_info_models.Merchant):
    return models.PreSalesOrder.objects.filter(merchant=merchant).select_related(
        'merchant', 'customer', 'warehouse', 'created_by'
    ).prefetch_related('items')


def get_pre_sales_order(*, merchant: basic_info_models.Merchant, pre_sales_order_id: int) -> models.PreSalesOrder:
    try:
        return models.PreSalesOrder.objects.select_related(
            'merchant', 'customer', 'warehouse', 'created_by'
        ).prefetch_related('items').get(id=pre_sales_order_id, merchant=merchant)
    except models.PreSalesOrder.DoesNotExist as exc:
        raise ValueError('预销售单不存在') from exc


def create_pre_sales_order(
    *,
    merchant: basic_info_models.Merchant,
    customer_id: int | str | None,
    warehouse_id: int | str | None,
    operator: basic_info_models.Employee,
    created_by=None,
    kind: int | str | None = None,
    items: List[Dict[str, Any]],
    remarks: str | None = '',
) -> models.PreSalesOrder:
    if not customer_id:
        raise ValueError('缺少客户 ID')
    if not warehouse_id:
        raise ValueError('缺少仓库 ID')

    try:
        customer_id_int = int(customer_id)
    except (TypeError, ValueError) as exc:
        raise ValueError('customer 必须为整数') from exc

    try:
        warehouse_id_int = int(warehouse_id)
    except (TypeError, ValueError) as exc:
        raise ValueError('warehouse 必须为整数') from exc

    kind_int: int | None
    if kind is None or kind == '':
        kind_int = None
    else:
        try:
            kind_int = int(kind)
        except (TypeError, ValueError) as exc:
            raise ValueError('kind 必须为整数') from exc

    try:
        customer = basic_info_models.Customer.objects.get(id=customer_id_int, merchant=merchant)
    except basic_info_models.Customer.DoesNotExist as exc:
        raise ValueError(f'客户 {customer_id_int} 不存在') from exc

    try:
        warehouse = basic_info_models.WareHouse.objects.get(id=warehouse_id_int, merchant=merchant)
    except basic_info_models.WareHouse.DoesNotExist as exc:
        raise ValueError(f'仓库 {warehouse_id_int} 不存在') from exc

    if operator.merchant_id != merchant.id:
        raise ValueError('经办人所属商户与预销售单所属商户不一致')

    normalized_items = _normalize_pre_sales_items(merchant=merchant, items=items)

    with transaction.atomic():
        pre_sales_order = models.PreSalesOrder.objects.create(
            merchant=merchant,
            customer=customer,
            warehouse=warehouse,
            created_by=created_by,
            operator=operator,
            kind=kind_int or models.SalesOrderKindEnum.WHOLESALE,
            remarks=remarks,
        )

        # enforce cross-merchant consistency
        try:
            pre_sales_order.full_clean()
        except ValidationError as exc:
            raise ValueError(str(exc)) from exc

        models.PreSalesOrderItem.objects.bulk_create(
            [
                models.PreSalesOrderItem(
                    pre_sales_order=pre_sales_order,
                    product_id=item['product_id'],
                    product_name=item.get('product_name') or '',
                    color=item.get('color'),
                    quantity=item.get('quantity'),
                    unit=item.get('unit') or '',
                    spec=item.get('spec'),
                    quantity_of_rolls=item.get('quantity_of_rolls'),
                    num_of_rolls=item.get('num_of_rolls') or 1,
                    order_quantity=item.get('order_quantity'),
                    remarks=item.get('remarks'),
                )
                for item in normalized_items
            ]
        )

        # domain event: pre sales order created
        # must be fired only after commit to avoid triggering on rollbacks
        def _send_created_signal():
            try:
                from .signals import pre_sales_order_created

                pre_sales_order_created.send(
                    sender=models.PreSalesOrder,
                    instance=pre_sales_order,
                    created_by=created_by,
                    operator=operator,
                    items_count=len(normalized_items),
                )
            except Exception:
                import logging

                logging.getLogger(__name__).exception(
                    '[business.pre_order_services] 触发 pre_sales_order_created signal 失败（已忽略）'
                )

        transaction.on_commit(_send_created_signal)

    pre_sales_order.refresh_from_db()
    enqueue_business_order_speech(order=pre_sales_order, order_label='预销售单', action_label='创建')
    return pre_sales_order


def update_pre_sales_order(
    *,
    pre_sales_order: models.PreSalesOrder,
    customer_id: int | str | None = None,
    warehouse_id: int | str | None = None,
    kind: int | str | None = None,
    items: List[Dict[str, Any]] | None = None,
    remarks: str | None = None,
) -> models.PreSalesOrder:
    merchant = pre_sales_order.merchant

    new_customer = pre_sales_order.customer
    if customer_id is not None:
        if customer_id == '':
            raise ValueError('customer 必须为整数')
        try:
            customer_id_int = int(customer_id)
        except (TypeError, ValueError) as exc:
            raise ValueError('customer 必须为整数') from exc
        try:
            new_customer = basic_info_models.Customer.objects.get(id=customer_id_int, merchant=merchant)
        except basic_info_models.Customer.DoesNotExist as exc:
            raise ValueError(f'客户 {customer_id_int} 不存在') from exc

    new_warehouse = pre_sales_order.warehouse
    if warehouse_id is not None:
        if warehouse_id == '':
            raise ValueError('warehouse 必须为整数')
        try:
            warehouse_id_int = int(warehouse_id)
        except (TypeError, ValueError) as exc:
            raise ValueError('warehouse 必须为整数') from exc
        try:
            new_warehouse = basic_info_models.WareHouse.objects.get(id=warehouse_id_int, merchant=merchant)
        except basic_info_models.WareHouse.DoesNotExist as exc:
            raise ValueError(f'仓库 {warehouse_id_int} 不存在') from exc

    if kind is None or kind == '':
        new_kind = pre_sales_order.kind
    else:
        try:
            new_kind = int(kind)
        except (TypeError, ValueError) as exc:
            raise ValueError('kind 必须为整数') from exc
    new_remarks = remarks if remarks is not None else pre_sales_order.remarks

    if items is None:
        raise ValueError('items 需要为非空数组')

    normalized_items = _normalize_pre_sales_items(merchant=merchant, items=items)

    with transaction.atomic():
        pre_sales_order.customer = new_customer
        pre_sales_order.warehouse = new_warehouse
        pre_sales_order.kind = new_kind
        pre_sales_order.remarks = new_remarks

        try:
            pre_sales_order.full_clean()
        except ValidationError as exc:
            raise ValueError(str(exc)) from exc

        pre_sales_order.save(update_fields=['customer', 'warehouse', 'kind', 'remarks', 'updated_at'])

        pre_sales_order.items.all().delete()
        models.PreSalesOrderItem.objects.bulk_create(
            [
                models.PreSalesOrderItem(
                    pre_sales_order=pre_sales_order,
                    product_id=item['product_id'],
                    product_name=item.get('product_name') or '',
                    color=item.get('color'),
                    quantity=item.get('quantity'),
                    unit=item.get('unit') or '',
                    spec=item.get('spec'),
                    quantity_of_rolls=item.get('quantity_of_rolls'),
                    num_of_rolls=item.get('num_of_rolls') or 1,
                    order_quantity=item.get('order_quantity'),
                    remarks=item.get('remarks'),
                )
                for item in normalized_items
            ]
        )

    pre_sales_order.refresh_from_db()
    return pre_sales_order


def delete_pre_sales_order(*, pre_sales_order: models.PreSalesOrder) -> None:
    with transaction.atomic():
        pre_sales_order.delete()


def convert_pre_sales_order_to_sales_order(
    *,
    merchant: basic_info_models.Merchant,
    pre_sales_order_id: int,
    operator: basic_info_models.Employee,
    created_by=None,
    order_date=None,
) -> models.SalesOrder:
    order = models.PreSalesOrder.objects.select_related(
        'customer',
        'warehouse',
        'operator',
        'created_by',
    ).prefetch_related('items', 'items__allocation_records').get(
        id=pre_sales_order_id,
        merchant=merchant,
    )

    warehouse = order.warehouse
    if warehouse.mode != basic_info_models.WareHouseModeEnum.RESTRICT_IN_OUT:
        raise PermissionError('仅支持严进严出仓库模式')

    items_payload: List[Dict[str, Any]] = []
    for item in order.items.all():
        if not item.product_id:
            raise ValueError(f'明细 {item.id} 缺少 product_id')
        if item.quantity is None or str(item.quantity) == '':
            raise ValueError(f'明细 {item.id} 缺少 quantity')

        consume_ids: List[int] = []
        for record in item.allocation_records.all():
            if record.status != models.AllocationRecordStatusEnum.ACTIVE:
                continue
            consume_ids.extend(record.stock_id_list)

        if not consume_ids:
            raise ValueError(f'明细 {item.id} 缺少配货库存明细')

        quantity_from_details: Decimal | None = None
        if consume_ids:
            details_by_id = stock_models.StockChangeDetail.objects.filter(
                id__in=consume_ids,
                merchant=merchant,
            ).in_bulk()
            if details_by_id:
                total = Decimal('0')
                for detail in details_by_id.values():
                    total += detail.quantity
                quantity_from_details = total

        items_payload.append(
            {
                'product_id': item.product_id,
                'price': '0',
                'quantity': str(quantity_from_details or item.quantity),
                'unit': item.unit or None,
                'color': item.color,
                'spec': item.spec,
                'remarks': item.remarks,
                'order_quantity': item.order_quantity,
                'consume_detail_ids': consume_ids,
            }
        )

    resolved_order_date = order_date or (order.created_at.date() if order.created_at else timezone.localdate())

    return business_services.create_sales_order(
        merchant=merchant,
        customer=order.customer,
        order_date=resolved_order_date,
        warehouse=warehouse,
        operator=operator,
        items=items_payload,
        remarks=order.remarks or '',
        created_by=created_by,
        from_pre_sales_order_id=order.id,
    )


def list_pre_purchase_orders(*, merchant: basic_info_models.Merchant):
    return models.PrePurchaseOrder.objects.filter(merchant=merchant).select_related(
        'merchant', 'supplier', 'warehouse', 'created_by'
    ).prefetch_related('items')


def get_pre_purchase_order(*, merchant: basic_info_models.Merchant, pre_purchase_order_id: int) -> models.PrePurchaseOrder:
    try:
        return models.PrePurchaseOrder.objects.select_related(
            'merchant', 'supplier', 'warehouse', 'created_by'
        ).prefetch_related('items').get(id=pre_purchase_order_id, merchant=merchant)
    except models.PrePurchaseOrder.DoesNotExist as exc:
        raise ValueError('预采购单不存在') from exc


def create_pre_purchase_order(
    *,
    merchant: basic_info_models.Merchant,
    supplier_id: int | str | None,
    warehouse_id: int | str | None,
    operator: basic_info_models.Employee,
    created_by=None,
    kind: int | str | None = None,
    items: List[Dict[str, Any]],
    remarks: str | None = '',
) -> models.PrePurchaseOrder:
    if not supplier_id:
        raise ValueError('缺少供应商 ID')
    if not warehouse_id:
        raise ValueError('缺少仓库 ID')

    try:
        supplier_id_int = int(supplier_id)
    except (TypeError, ValueError) as exc:
        raise ValueError('supplier 必须为整数') from exc

    try:
        warehouse_id_int = int(warehouse_id)
    except (TypeError, ValueError) as exc:
        raise ValueError('warehouse 必须为整数') from exc

    kind_int: int | None
    if kind is None or kind == '':
        kind_int = None
    else:
        try:
            kind_int = int(kind)
        except (TypeError, ValueError) as exc:
            raise ValueError('kind 必须为整数') from exc

    try:
        supplier = basic_info_models.Supplier.objects.get(id=supplier_id_int, merchant=merchant)
    except basic_info_models.Supplier.DoesNotExist as exc:
        raise ValueError(f'供应商 {supplier_id_int} 不存在') from exc

    try:
        warehouse = basic_info_models.WareHouse.objects.get(id=warehouse_id_int, merchant=merchant)
    except basic_info_models.WareHouse.DoesNotExist as exc:
        raise ValueError(f'仓库 {warehouse_id_int} 不存在') from exc

    if operator.merchant_id != merchant.id:
        raise ValueError('经办人所属商户与预采购单所属商户不一致')

    normalized_items = _normalize_pre_purchase_items(merchant=merchant, items=items)

    with transaction.atomic():
        pre_purchase_order = models.PrePurchaseOrder.objects.create(
            merchant=merchant,
            supplier=supplier,
            warehouse=warehouse,
            created_by=created_by,
            operator=operator,
            kind=kind_int or models.PurchaseOrderKindEnum.WHOLESALE,
            remarks=remarks,
        )

        try:
            pre_purchase_order.full_clean()
        except ValidationError as exc:
            raise ValueError(str(exc)) from exc

        models.PrePurchaseOrderItem.objects.bulk_create(
            [
                models.PrePurchaseOrderItem(
                    pre_purchase_order=pre_purchase_order,
                    product_id=item['product_id'],
                    product_name=item.get('product_name') or '',
                    color=item.get('color'),
                    quantity=item.get('quantity'),
                    unit=item.get('unit') or '',
                    spec=item.get('spec'),
                    quantity_of_rolls=item.get('quantity_of_rolls'),
                    num_of_rolls=item.get('num_of_rolls') or 1,
                    order_quantity=item.get('order_quantity'),
                    remarks=item.get('remarks'),
                )
                for item in normalized_items
            ]
        )

    pre_purchase_order.refresh_from_db()
    enqueue_business_order_speech(order=pre_purchase_order, order_label='预采购单', action_label='创建')
    return pre_purchase_order


def update_pre_purchase_order(
    *,
    pre_purchase_order: models.PrePurchaseOrder,
    supplier_id: int | str | None = None,
    warehouse_id: int | str | None = None,
    kind: int | str | None = None,
    items: List[Dict[str, Any]] | None = None,
    remarks: str | None = None,
) -> models.PrePurchaseOrder:
    merchant = pre_purchase_order.merchant

    new_supplier = pre_purchase_order.supplier
    if supplier_id is not None:
        if supplier_id == '':
            raise ValueError('supplier 必须为整数')
        try:
            supplier_id_int = int(supplier_id)
        except (TypeError, ValueError) as exc:
            raise ValueError('supplier 必须为整数') from exc
        try:
            new_supplier = basic_info_models.Supplier.objects.get(id=supplier_id_int, merchant=merchant)
        except basic_info_models.Supplier.DoesNotExist as exc:
            raise ValueError(f'供应商 {supplier_id_int} 不存在') from exc

    new_warehouse = pre_purchase_order.warehouse
    if warehouse_id is not None:
        if warehouse_id == '':
            raise ValueError('warehouse 必须为整数')
        try:
            warehouse_id_int = int(warehouse_id)
        except (TypeError, ValueError) as exc:
            raise ValueError('warehouse 必须为整数') from exc
        try:
            new_warehouse = basic_info_models.WareHouse.objects.get(id=warehouse_id_int, merchant=merchant)
        except basic_info_models.WareHouse.DoesNotExist as exc:
            raise ValueError(f'仓库 {warehouse_id_int} 不存在') from exc

    if kind is None or kind == '':
        new_kind = pre_purchase_order.kind
    else:
        try:
            new_kind = int(kind)
        except (TypeError, ValueError) as exc:
            raise ValueError('kind 必须为整数') from exc

    new_remarks = remarks if remarks is not None else pre_purchase_order.remarks

    if items is None:
        raise ValueError('items 需要为非空数组')
    normalized_items = _normalize_pre_purchase_items(merchant=merchant, items=items)

    with transaction.atomic():
        pre_purchase_order.supplier = new_supplier
        pre_purchase_order.warehouse = new_warehouse
        pre_purchase_order.kind = new_kind
        pre_purchase_order.remarks = new_remarks

        try:
            pre_purchase_order.full_clean()
        except ValidationError as exc:
            raise ValueError(str(exc)) from exc

        pre_purchase_order.save(update_fields=['supplier', 'warehouse', 'kind', 'remarks', 'updated_at'])

        pre_purchase_order.items.all().delete()
        models.PrePurchaseOrderItem.objects.bulk_create(
            [
                models.PrePurchaseOrderItem(
                    pre_purchase_order=pre_purchase_order,
                    product_id=item['product_id'],
                    product_name=item.get('product_name') or '',
                    color=item.get('color'),
                    quantity=item.get('quantity'),
                    unit=item.get('unit') or '',
                    spec=item.get('spec'),
                    quantity_of_rolls=item.get('quantity_of_rolls'),
                    num_of_rolls=item.get('num_of_rolls') or 1,
                    order_quantity=item.get('order_quantity'),
                    remarks=item.get('remarks'),
                )
                for item in normalized_items
            ]
        )

    pre_purchase_order.refresh_from_db()
    return pre_purchase_order


def delete_pre_purchase_order(*, pre_purchase_order: models.PrePurchaseOrder) -> None:
    with transaction.atomic():
        pre_purchase_order.delete()
