from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal, ROUND_UP

from fastapi import HTTPException
from sqlalchemy import Select, and_, case, delete, func, select
from sqlalchemy.orm import Session, aliased

from app.models.master_data import Bom, BomItem, Item, Material, Product, StockBalance
from app.models.document_archive import DocumentArchive
from app.models.operations import Delivery, DeliveryItem, PurchaseOrderItem, WorkOrder
from app.models.org import Employee
from app.models.planning import MaterialDemand
from app.models.sales import Customer, SalesOrder, SalesOrderItem
from app.schemas.database import MaterialDemandRead


def build_serial(prefix: str) -> str:
    return f"{prefix}{datetime.now().strftime('%Y%m%d%H%M%S%f')[-12:]}"


def code_token(value: str | None, fallback: str, max_length: int = 12) -> str:
    token = "".join(ch for ch in str(value or "").upper() if ch.isalnum())
    return (token or fallback)[-max_length:]


def next_doc_no(db: Session, model, column_name: str, prefix: str) -> str:
    column = getattr(model, column_name)
    count = db.scalar(select(func.count(model.id)).where(column.like(f"{prefix}-%"))) or 0
    return f"{prefix}-{count + 1:03d}"


def decimalize(value: float | Decimal, places: str = "0.000001") -> Decimal:
    return Decimal(str(value)).quantize(Decimal(places))


def round_up_by_multiple(value: Decimal, multiple: Decimal) -> Decimal:
    if multiple <= 0:
        return decimalize(value)
    return ((value / multiple).quantize(Decimal("1"), rounding=ROUND_UP) * multiple).quantize(Decimal("0.000001"))


def create_customer_code(db: Session) -> str:
    date_part = date.today().strftime("%Y%m%d")
    return next_doc_no(db, Customer, "customer_code", f"CUS-NB-{date_part}")


def build_mrp_demand_no(db: Session, sales_order: SalesOrder, order_item: SalesOrderItem, material_item: Item) -> str:
    date_part = date.today().strftime("%Y%m%d")
    order_part = code_token(sales_order.order_no, f"SO{sales_order.id:04d}", 12)
    material_part = code_token(material_item.item_code or material_item.item_name, f"RM{material_item.id:04d}", 10)
    prefix = f"MRP-NB-{date_part}-{order_part}-L{int(order_item.line_no or 0):02d}-{material_part}"
    return next_doc_no(db, MaterialDemand, "demand_no", prefix)


def get_sales_order_query(limit: int = 50) -> Select:
    sales_employee = aliased(Employee)
    line_count_subquery = (
        select(
            SalesOrderItem.sales_order_id.label("sales_order_id"),
            func.count(SalesOrderItem.id).label("line_count"),
        )
        .group_by(SalesOrderItem.sales_order_id)
        .subquery()
    )
    mrp_count_subquery = (
        select(
            SalesOrderItem.sales_order_id.label("sales_order_id"),
            func.count(MaterialDemand.id).label("mrp_count"),
        )
        .join(MaterialDemand, MaterialDemand.sales_order_item_id == SalesOrderItem.id)
        .where(MaterialDemand.status != "SUPERSEDED")
        .group_by(SalesOrderItem.sales_order_id)
        .subquery()
    )
    purchase_count_subquery = (
        select(
            SalesOrderItem.sales_order_id.label("sales_order_id"),
            func.count(func.distinct(PurchaseOrderItem.purchase_order_id)).label("purchase_order_count"),
        )
        .join(MaterialDemand, MaterialDemand.sales_order_item_id == SalesOrderItem.id)
        .join(PurchaseOrderItem, PurchaseOrderItem.source_demand_id == MaterialDemand.id)
        .group_by(SalesOrderItem.sales_order_id)
        .subquery()
    )
    delivery_count_subquery = (
        select(
            Delivery.sales_order_id.label("sales_order_id"),
            func.count(Delivery.id).label("delivery_count"),
        )
        .where(Delivery.sales_order_id.is_not(None))
        .group_by(Delivery.sales_order_id)
        .subquery()
    )
    delivery_item_count_subquery = (
        select(
            SalesOrderItem.sales_order_id.label("sales_order_id"),
            func.count(DeliveryItem.id).label("delivery_item_count"),
        )
        .join(DeliveryItem, DeliveryItem.sales_order_item_id == SalesOrderItem.id)
        .group_by(SalesOrderItem.sales_order_id)
        .subquery()
    )
    work_order_count_subquery = (
        select(
            SalesOrderItem.sales_order_id.label("sales_order_id"),
            func.count(WorkOrder.id).label("work_order_count"),
        )
        .join(WorkOrder, WorkOrder.source_sales_order_item_id == SalesOrderItem.id)
        .group_by(SalesOrderItem.sales_order_id)
        .subquery()
    )
    latest_archive_version_subquery = (
        select(
            DocumentArchive.business_id.label("business_id"),
            func.max(DocumentArchive.archive_version).label("archive_version"),
        )
        .where(DocumentArchive.document_type == "销售订单", DocumentArchive.file_format == "PDF")
        .group_by(DocumentArchive.business_id)
        .subquery()
    )
    latest_archive_id_subquery = (
        select(
            DocumentArchive.business_id.label("business_id"),
            func.max(DocumentArchive.id).label("archive_id"),
        )
        .join(
            latest_archive_version_subquery,
            and_(
                latest_archive_version_subquery.c.business_id == DocumentArchive.business_id,
                latest_archive_version_subquery.c.archive_version == DocumentArchive.archive_version,
            ),
        )
        .where(DocumentArchive.document_type == "销售订单", DocumentArchive.file_format == "PDF")
        .group_by(DocumentArchive.business_id)
        .subquery()
    )
    latest_archive_subquery = (
        select(
            DocumentArchive.business_id.label("business_id"),
            DocumentArchive.status.label("archive_status"),
            DocumentArchive.archive_version.label("archive_version"),
            DocumentArchive.created_at.label("archive_created_at"),
            DocumentArchive.error_message.label("archive_error_message"),
        )
        .join(
            latest_archive_id_subquery,
            latest_archive_id_subquery.c.archive_id == DocumentArchive.id,
        )
        .where(DocumentArchive.document_type == "销售订单", DocumentArchive.file_format == "PDF")
        .subquery()
    )

    return (
        select(
            SalesOrder.id.label("order_id"),
            SalesOrder.order_no.label("order_no"),
            SalesOrder.customer_id.label("customer_id"),
            Customer.customer_code.label("customer_code"),
            Customer.customer_name.label("customer_name"),
            SalesOrder.sales_employee_id.label("sales_employee_id"),
            sales_employee.employee_name.label("sales_employee_name"),
            SalesOrder.order_date.label("order_date"),
            SalesOrder.promised_date.label("promised_date"),
            SalesOrder.delivery_address.label("delivery_address"),
            SalesOrder.total_amount.label("total_amount"),
            SalesOrder.status.label("status"),
            func.coalesce(line_count_subquery.c.line_count, 0).label("line_count"),
            func.coalesce(mrp_count_subquery.c.mrp_count, 0).label("mrp_count"),
            func.coalesce(purchase_count_subquery.c.purchase_order_count, 0).label("purchase_order_count"),
            func.coalesce(delivery_count_subquery.c.delivery_count, 0).label("delivery_count"),
            func.coalesce(delivery_item_count_subquery.c.delivery_item_count, 0).label("delivery_item_count"),
            func.coalesce(work_order_count_subquery.c.work_order_count, 0).label("work_order_count"),
            func.coalesce(latest_archive_subquery.c.archive_status, "未生成").label("archive_status"),
            latest_archive_subquery.c.archive_version.label("archive_version"),
            latest_archive_subquery.c.archive_created_at.label("archive_created_at"),
            latest_archive_subquery.c.archive_error_message.label("archive_error_message"),
            case(
                (
                    and_(
                        func.coalesce(purchase_count_subquery.c.purchase_order_count, 0) == 0,
                        func.coalesce(delivery_count_subquery.c.delivery_count, 0) == 0,
                        func.coalesce(delivery_item_count_subquery.c.delivery_item_count, 0) == 0,
                        func.coalesce(work_order_count_subquery.c.work_order_count, 0) == 0,
                    ),
                    True,
                ),
                else_=False,
            ).label("can_delete"),
        )
        .join(Customer, Customer.id == SalesOrder.customer_id)
        .outerjoin(sales_employee, sales_employee.id == SalesOrder.sales_employee_id)
        .outerjoin(line_count_subquery, line_count_subquery.c.sales_order_id == SalesOrder.id)
        .outerjoin(mrp_count_subquery, mrp_count_subquery.c.sales_order_id == SalesOrder.id)
        .outerjoin(purchase_count_subquery, purchase_count_subquery.c.sales_order_id == SalesOrder.id)
        .outerjoin(delivery_count_subquery, delivery_count_subquery.c.sales_order_id == SalesOrder.id)
        .outerjoin(delivery_item_count_subquery, delivery_item_count_subquery.c.sales_order_id == SalesOrder.id)
        .outerjoin(work_order_count_subquery, work_order_count_subquery.c.sales_order_id == SalesOrder.id)
        .outerjoin(latest_archive_subquery, latest_archive_subquery.c.business_id == SalesOrder.id)
        .order_by(SalesOrder.id.desc())
        .limit(limit)
    )


def get_sales_order_items_query(limit: int = 50, sales_order_id: int | None = None) -> Select:
    stmt = (
        select(
            SalesOrderItem.id.label("sales_order_item_id"),
            SalesOrderItem.sales_order_id.label("sales_order_id"),
            SalesOrder.order_no.label("order_no"),
            SalesOrderItem.line_no.label("line_no"),
            SalesOrderItem.product_item_id.label("product_item_id"),
            Item.item_code.label("product_code"),
            Item.item_name.label("product_name"),
            SalesOrderItem.customer_part_no.label("customer_part_no"),
            SalesOrderItem.order_qty.label("order_qty"),
            SalesOrderItem.delivered_qty.label("delivered_qty"),
            SalesOrderItem.unit_price.label("unit_price"),
            SalesOrderItem.line_amount.label("line_amount"),
            SalesOrderItem.promised_date.label("promised_date"),
            SalesOrderItem.status.label("status"),
        )
        .join(SalesOrder, SalesOrder.id == SalesOrderItem.sales_order_id)
        .join(Item, Item.id == SalesOrderItem.product_item_id)
        .order_by(SalesOrderItem.id.desc())
        .limit(limit)
    )
    if sales_order_id:
        stmt = stmt.where(SalesOrderItem.sales_order_id == sales_order_id)
    return stmt


def get_mrp_demands_query(limit: int = 50, sales_order_id: int | None = None) -> Select:
    product_item = aliased(Item)
    material_item = aliased(Item)
    material_ext = aliased(Material)

    stmt = (
        select(
            MaterialDemand.id.label("demand_id"),
            MaterialDemand.demand_no.label("demand_no"),
            SalesOrder.order_no.label("order_no"),
            MaterialDemand.material_item_id.label("material_item_id"),
            product_item.item_code.label("product_code"),
            product_item.item_name.label("product_name"),
            material_item.item_code.label("material_code"),
            material_item.item_name.label("material_name"),
            material_ext.purchase_calc_mode.label("purchase_calc_mode"),
            MaterialDemand.order_qty.label("order_qty"),
            MaterialDemand.theoretical_weight_kg.label("theoretical_weight_kg"),
            MaterialDemand.planned_weight_kg.label("planned_weight_kg"),
            MaterialDemand.shortage_weight_kg.label("shortage_weight_kg"),
            MaterialDemand.suggested_purchase_qty.label("suggested_purchase_qty"),
            MaterialDemand.suggested_purchase_weight_kg.label("suggested_purchase_weight_kg"),
            MaterialDemand.status.label("status"),
            MaterialDemand.generated_at.label("generated_at"),
        )
        .join(SalesOrderItem, SalesOrderItem.id == MaterialDemand.sales_order_item_id)
        .join(SalesOrder, SalesOrder.id == SalesOrderItem.sales_order_id)
        .join(product_item, product_item.id == MaterialDemand.product_item_id)
        .join(material_item, material_item.id == MaterialDemand.material_item_id)
        .outerjoin(material_ext, material_ext.item_id == MaterialDemand.material_item_id)
        .where(MaterialDemand.status != "SUPERSEDED")
        .order_by(MaterialDemand.id.desc())
        .limit(limit)
    )
    if sales_order_id:
        stmt = stmt.where(SalesOrder.id == sales_order_id)
    return stmt


def generate_mrp_for_order(db: Session, sales_order_id: int) -> tuple[SalesOrder, list[MaterialDemandRead]]:
    sales_order = db.get(SalesOrder, sales_order_id)
    if not sales_order:
        raise HTTPException(status_code=404, detail="订单不存在")

    order_items = db.scalars(
        select(SalesOrderItem).where(SalesOrderItem.sales_order_id == sales_order_id).order_by(SalesOrderItem.line_no)
    ).all()
    if not order_items:
        raise HTTPException(status_code=400, detail="订单没有明细，无法生成MRP")

    item_ids = [item.id for item in order_items]
    if item_ids:
        existing_demands = db.scalars(
            select(MaterialDemand).where(MaterialDemand.sales_order_item_id.in_(item_ids))
        ).all()
        existing_demand_ids = [demand.id for demand in existing_demands]
        referenced_demand_ids: set[int] = set()
        if existing_demand_ids:
            referenced_demand_ids = set(
                db.scalars(
                    select(PurchaseOrderItem.source_demand_id).where(
                        PurchaseOrderItem.source_demand_id.in_(existing_demand_ids)
                    )
                ).all()
            )

        deletable_demand_ids = [
            demand_id for demand_id in existing_demand_ids if demand_id not in referenced_demand_ids
        ]
        if deletable_demand_ids:
            db.execute(delete(MaterialDemand).where(MaterialDemand.id.in_(deletable_demand_ids)))

        for demand in existing_demands:
            if demand.id in referenced_demand_ids:
                demand.status = "SUPERSEDED"
                demand.remark = "已被重新生成的MRP替代，因已被采购订单引用而保留追溯"
                db.add(demand)

        db.flush()

    generated_at = datetime.now()
    created_rows: list[MaterialDemand] = []

    for order_item in order_items:
        product = db.get(Item, order_item.product_item_id)
        if not product:
            raise HTTPException(status_code=400, detail=f"产品ID {order_item.product_item_id} 不存在")

        bom = db.scalar(
            select(Bom).where(Bom.product_item_id == product.id, Bom.is_default == 1, Bom.status == "ACTIVE")
        )
        if not bom:
            raise HTTPException(status_code=400, detail=f"产品 {product.item_name} 缺少默认BOM")

        bom_items = db.scalars(select(BomItem).where(BomItem.bom_id == bom.id).order_by(BomItem.seq_no)).all()
        if not bom_items:
            raise HTTPException(status_code=400, detail=f"产品 {product.item_name} 的BOM没有明细")

        order_qty = decimalize(order_item.order_qty)

        for bom_item in bom_items:
            material_item = db.get(Item, bom_item.material_item_id)
            if not material_item:
                raise HTTPException(status_code=400, detail=f"BOM原材料ID {bom_item.material_item_id} 不存在")

            material_ext = db.scalar(select(Material).where(Material.item_id == material_item.id))
            if not material_ext:
                raise HTTPException(status_code=400, detail=f"原材料 {material_item.item_name} 缺少采购扩展信息")

            loss_multiplier = Decimal("1") + decimalize(bom_item.loss_rate or 0, "0.0001")
            material_unit_weight = Decimal(str(material_item.unit_weight_kg or 0))

            available_stock_weight = Decimal(
                str(
                    db.scalar(
                        select(func.coalesce(func.sum(StockBalance.weight_available_kg), 0)).where(
                            StockBalance.item_id == material_item.id
                        )
                    )
                    or 0
                )
            )

            gross_weight_per_piece = decimalize(bom_item.gross_weight_per_piece_kg)
            if gross_weight_per_piece <= 0 and material_unit_weight > 0:
                gross_weight_per_piece = decimalize(bom_item.qty_per_piece) * material_unit_weight
            theoretical_weight = order_qty * gross_weight_per_piece
            planned_weight = theoretical_weight * loss_multiplier

            shortage_weight = max(Decimal("0"), planned_weight - available_stock_weight)

            suggested_purchase_weight = shortage_weight
            purchase_multiple_weight = Decimal(str(material_ext.purchase_multiple_weight_kg or 0))
            min_purchase_weight = Decimal(str(material_ext.min_purchase_qty or 0))
            if suggested_purchase_weight > 0:
                if purchase_multiple_weight > 0:
                    suggested_purchase_weight = round_up_by_multiple(suggested_purchase_weight, purchase_multiple_weight)
                elif min_purchase_weight > 0 and suggested_purchase_weight < min_purchase_weight:
                    suggested_purchase_weight = min_purchase_weight.quantize(Decimal("0.000001"))

            demand = MaterialDemand(
                demand_no=build_mrp_demand_no(db, sales_order, order_item, material_item),
                sales_order_item_id=order_item.id,
                product_item_id=product.id,
                material_item_id=material_item.id,
                bom_item_id=bom_item.id,
                order_qty=order_qty,
                theoretical_weight_kg=theoretical_weight,
                planned_weight_kg=planned_weight,
                available_stock_weight_kg=available_stock_weight,
                shortage_weight_kg=shortage_weight,
                suggested_purchase_qty=Decimal("0"),
                suggested_purchase_weight_kg=suggested_purchase_weight,
                status="NEW",
                generated_at=generated_at,
                remark="由订单自动生成",
            )
            db.add(demand)
            created_rows.append(demand)

    db.commit()

    rows = db.execute(get_mrp_demands_query(limit=200, sales_order_id=sales_order_id)).mappings().all()
    return sales_order, [MaterialDemandRead.model_validate(dict(row)) for row in rows]
