from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal, ROUND_FLOOR

from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.master_data import Bom, BomItem, Item
from app.models.operations import ProductionBatchLedger, ProductionBatchLedgerTxn, StockLot
from app.services.operations import to_decimal


INBOUND_TOLERANCE_MULTIPLIER = Decimal("1.15")


@dataclass
class ProductionBatchLedgerSnapshot:
    production_ledger_id: int
    material_lot_no: str
    product_item_id: int
    material_item_id: int
    gross_weight_per_piece_kg: Decimal
    net_weight_per_piece_kg: Decimal
    total_issued_weight_kg: Decimal
    finished_inbound_qty: Decimal
    surplus_inbound_weight_kg: Decimal
    scrap_inbound_weight_kg: Decimal
    writeoff_weight_kg: Decimal
    outside_weight_kg: Decimal
    reference_finished_qty: Decimal
    used_material_weight_kg: Decimal
    theoretical_scrap_weight_kg: Decimal
    theoretical_surplus_weight_kg: Decimal


def _round_weight(value: Decimal) -> Decimal:
    return to_decimal(value, "0.001")


def _non_negative(value: Decimal) -> Decimal:
    return value if value > 0 else Decimal("0")


def _append_txn(
    db: Session,
    *,
    ledger: ProductionBatchLedger,
    txn_type: str,
    qty_delta: Decimal = Decimal("0"),
    weight_delta_kg: Decimal = Decimal("0"),
    source_doc_type: str | None = None,
    source_doc_id: int | None = None,
    source_line_id: int | None = None,
    biz_time: datetime | None = None,
    operator_user_id: int | None = None,
    remark: str | None = None,
) -> ProductionBatchLedgerTxn:
    txn = ProductionBatchLedgerTxn(
        production_ledger_id=ledger.id,
        txn_type=txn_type,
        qty_delta=qty_delta,
        weight_delta_kg=weight_delta_kg,
        outside_weight_after_kg=to_decimal(ledger.outside_weight_kg),
        source_doc_type=source_doc_type,
        source_doc_id=source_doc_id,
        source_line_id=source_line_id,
        biz_time=biz_time or datetime.now(),
        operator_user_id=operator_user_id,
        remark=remark,
    )
    db.add(txn)
    db.flush()
    return txn


def _get_bom_weight_profile(db: Session, product_item_id: int, material_item_id: int) -> tuple[Decimal, Decimal]:
    row = db.execute(
        select(
            BomItem.gross_weight_per_piece_kg.label("gross_weight_per_piece_kg"),
            BomItem.net_weight_per_piece_kg.label("net_weight_per_piece_kg"),
        )
        .join(Bom, Bom.id == BomItem.bom_id)
        .where(
            Bom.product_item_id == product_item_id,
            BomItem.material_item_id == material_item_id,
            Bom.status == "ACTIVE",
        )
        .order_by(Bom.is_default.desc(), BomItem.seq_no, BomItem.id)
        .limit(1)
    ).mappings().first()
    gross = to_decimal(row["gross_weight_per_piece_kg"] if row else 0)
    net = to_decimal(row["net_weight_per_piece_kg"] if row else 0)
    if gross <= 0:
        product = db.get(Item, product_item_id)
        gross = to_decimal(product.unit_weight_kg if product else 0)
    if net <= 0 or net > gross:
        net = gross
    return gross, net


def calculate_production_batch_ledger_snapshot(
    db: Session,
    production_ledger_id: int,
    *,
    smart_reporting_enabled: bool,
    selected_finished_qty: Decimal | None = None,
) -> ProductionBatchLedgerSnapshot:
    _ = smart_reporting_enabled
    ledger = db.get(ProductionBatchLedger, production_ledger_id)
    if not ledger:
        raise HTTPException(status_code=404, detail="生产台账不存在")
    gross, net = _get_bom_weight_profile(db, ledger.product_item_id, ledger.material_item_id)
    total_issued = to_decimal(ledger.total_issued_weight_kg)
    finished_qty = to_decimal(ledger.finished_inbound_qty)
    basis_qty = finished_qty + to_decimal(selected_finished_qty)
    reference_finished_qty = Decimal("0")
    if gross > 0:
        reference_finished_qty = (total_issued / gross).to_integral_value(rounding=ROUND_FLOOR)
    used_weight = _round_weight(basis_qty * gross)
    theoretical_scrap = _round_weight(_non_negative(basis_qty * _non_negative(gross - net) - to_decimal(ledger.scrap_inbound_weight_kg)))
    theoretical_surplus = _round_weight(_non_negative(total_issued - used_weight - to_decimal(ledger.surplus_inbound_weight_kg)))
    return ProductionBatchLedgerSnapshot(
        production_ledger_id=ledger.id,
        material_lot_no=ledger.material_lot_no,
        product_item_id=ledger.product_item_id,
        material_item_id=ledger.material_item_id,
        gross_weight_per_piece_kg=gross,
        net_weight_per_piece_kg=net,
        total_issued_weight_kg=total_issued,
        finished_inbound_qty=finished_qty,
        surplus_inbound_weight_kg=to_decimal(ledger.surplus_inbound_weight_kg),
        scrap_inbound_weight_kg=to_decimal(ledger.scrap_inbound_weight_kg),
        writeoff_weight_kg=to_decimal(ledger.writeoff_weight_kg),
        outside_weight_kg=to_decimal(ledger.outside_weight_kg),
        reference_finished_qty=reference_finished_qty,
        used_material_weight_kg=used_weight,
        theoretical_scrap_weight_kg=theoretical_scrap,
        theoretical_surplus_weight_kg=theoretical_surplus,
    )


def post_finished_inbound_to_ledger(
    db: Session,
    *,
    production_ledger_id: int,
    finished_qty: Decimal,
    operator_user_id: int | None,
    source_doc_type: str | None,
    source_doc_id: int | None,
    source_line_id: int | None,
    biz_time: datetime | None = None,
    remark: str | None = None,
) -> ProductionBatchLedger:
    qty = to_decimal(finished_qty)
    if qty < 0:
        raise HTTPException(status_code=400, detail="成品入库数量不能小于0")
    ledger = db.get(ProductionBatchLedger, production_ledger_id)
    if not ledger:
        raise HTTPException(status_code=404, detail="生产台账不存在")
    gross, _ = _get_bom_weight_profile(db, ledger.product_item_id, ledger.material_item_id)
    used_weight = _round_weight(qty * gross)
    ledger.finished_inbound_qty = to_decimal(ledger.finished_inbound_qty) + qty
    ledger.outside_weight_kg = _round_weight(_non_negative(to_decimal(ledger.outside_weight_kg) - used_weight))
    db.add(ledger)
    db.flush()
    _append_txn(
        db,
        ledger=ledger,
        txn_type="成品入库",
        qty_delta=qty,
        weight_delta_kg=-used_weight,
        source_doc_type=source_doc_type,
        source_doc_id=source_doc_id,
        source_line_id=source_line_id,
        biz_time=biz_time,
        operator_user_id=operator_user_id,
        remark=remark or "成品入库联动生产台账",
    )
    return ledger


def post_surplus_inbound_to_ledger(
    db: Session,
    *,
    production_ledger_id: int,
    surplus_weight_kg: Decimal,
    operator_user_id: int | None,
    source_doc_type: str | None,
    source_doc_id: int | None,
    source_line_id: int | None,
    biz_time: datetime | None = None,
    remark: str | None = None,
) -> ProductionBatchLedger:
    weight = _round_weight(to_decimal(surplus_weight_kg))
    if weight < 0:
        raise HTTPException(status_code=400, detail="余料入库重量不能小于0")
    ledger = db.get(ProductionBatchLedger, production_ledger_id)
    if not ledger:
        raise HTTPException(status_code=404, detail="生产台账不存在")
    ledger.surplus_inbound_weight_kg = _round_weight(to_decimal(ledger.surplus_inbound_weight_kg) + weight)
    ledger.outside_weight_kg = _round_weight(_non_negative(to_decimal(ledger.outside_weight_kg) - weight))
    db.add(ledger)
    db.flush()
    _append_txn(
        db,
        ledger=ledger,
        txn_type="生产余料入库",
        weight_delta_kg=-weight,
        source_doc_type=source_doc_type,
        source_doc_id=source_doc_id,
        source_line_id=source_line_id,
        biz_time=biz_time,
        operator_user_id=operator_user_id,
        remark=remark or "生产余料入库联动生产台账",
    )
    return ledger


def post_scrap_inbound_to_ledger(
    db: Session,
    *,
    production_ledger_id: int,
    scrap_weight_kg: Decimal,
    operator_user_id: int | None,
    source_doc_type: str | None,
    source_doc_id: int | None,
    source_line_id: int | None,
    biz_time: datetime | None = None,
    remark: str | None = None,
) -> ProductionBatchLedger:
    weight = _round_weight(to_decimal(scrap_weight_kg))
    if weight < 0:
        raise HTTPException(status_code=400, detail="废料入库重量不能小于0")
    ledger = db.get(ProductionBatchLedger, production_ledger_id)
    if not ledger:
        raise HTTPException(status_code=404, detail="生产台账不存在")
    ledger.scrap_inbound_weight_kg = _round_weight(to_decimal(ledger.scrap_inbound_weight_kg) + weight)
    ledger.outside_weight_kg = _round_weight(_non_negative(to_decimal(ledger.outside_weight_kg) - weight))
    db.add(ledger)
    db.flush()
    _append_txn(
        db,
        ledger=ledger,
        txn_type="生产废料入库",
        weight_delta_kg=-weight,
        source_doc_type=source_doc_type,
        source_doc_id=source_doc_id,
        source_line_id=source_line_id,
        biz_time=biz_time,
        operator_user_id=operator_user_id,
        remark=remark or "生产废料入库联动生产台账",
    )
    return ledger


def _get_production_ledger(
    db: Session,
    *,
    material_lot_id: int,
    product_item_id: int,
) -> ProductionBatchLedger | None:
    return db.scalar(
        select(ProductionBatchLedger)
        .where(
            ProductionBatchLedger.material_lot_id == material_lot_id,
            ProductionBatchLedger.product_item_id == product_item_id,
        )
        .with_for_update()
    )


def record_production_issue_to_ledger(
    db: Session,
    *,
    material_lot_id: int,
    product_item_id: int,
    issued_weight_kg: Decimal,
    operator_user_id: int | None,
    source_doc_type: str | None,
    source_doc_id: int | None,
    source_line_id: int | None,
    biz_time: datetime | None = None,
    remark: str | None = None,
) -> ProductionBatchLedger:
    issued_weight = _round_weight(to_decimal(issued_weight_kg))
    if issued_weight <= 0:
        raise HTTPException(status_code=400, detail="生产出库重量必须大于0")
    lot = db.get(StockLot, material_lot_id)
    if not lot:
        raise HTTPException(status_code=404, detail="材料库存批次号不存在")
    product = db.get(Item, product_item_id)
    if not product:
        raise HTTPException(status_code=404, detail="产品不存在")

    now = biz_time or datetime.now()
    ledger = _get_production_ledger(db, material_lot_id=material_lot_id, product_item_id=product_item_id)
    if ledger and str(ledger.status or "") == "锁单":
        ledger.status = "在生产"
        ledger.miniapp_selectable = 1
        ledger.reopened_at = now
        db.add(ledger)
        db.flush()
        _append_txn(
            db,
            ledger=ledger,
            txn_type="重新开工",
            source_doc_type=source_doc_type,
            source_doc_id=source_doc_id,
            source_line_id=source_line_id,
            biz_time=now,
            operator_user_id=operator_user_id,
            remark="锁单后再次生产出库，生产台账重新进入在生产状态",
        )

    if not ledger:
        ledger = ProductionBatchLedger(
            material_lot_id=material_lot_id,
            material_lot_no=lot.lot_no,
            material_item_id=lot.item_id,
            product_item_id=product_item_id,
            status="在生产",
            miniapp_selectable=1,
            total_issued_weight_kg=Decimal("0"),
            outside_weight_kg=Decimal("0"),
            finished_inbound_qty=Decimal("0"),
            surplus_inbound_weight_kg=Decimal("0"),
            scrap_inbound_weight_kg=Decimal("0"),
            writeoff_weight_kg=Decimal("0"),
            first_issue_time=now,
            last_issue_time=now,
            remark=remark,
        )
        db.add(ledger)
        db.flush()

    ledger.status = "在生产"
    ledger.miniapp_selectable = 1
    ledger.total_issued_weight_kg = _round_weight(to_decimal(ledger.total_issued_weight_kg) + issued_weight)
    ledger.outside_weight_kg = _round_weight(to_decimal(ledger.outside_weight_kg) + issued_weight)
    ledger.last_issue_time = now
    if not ledger.first_issue_time:
        ledger.first_issue_time = now
    db.add(ledger)
    db.flush()
    _append_txn(
        db,
        ledger=ledger,
        txn_type="生产出库",
        weight_delta_kg=issued_weight,
        source_doc_type=source_doc_type,
        source_doc_id=source_doc_id,
        source_line_id=source_line_id,
        biz_time=now,
        operator_user_id=operator_user_id,
        remark=remark or "生产出库累计到生产台账",
    )
    return ledger


def lock_production_batch_ledger(
    db: Session,
    *,
    production_ledger_id: int,
    operator_user_id: int | None,
    biz_time: datetime | None = None,
    remark: str | None = None,
) -> ProductionBatchLedger:
    ledger = db.get(ProductionBatchLedger, production_ledger_id)
    if not ledger:
        raise HTTPException(status_code=404, detail="生产台账不存在")

    now = biz_time or datetime.now()
    remaining = _round_weight(to_decimal(ledger.outside_weight_kg))
    if remaining > 0:
        ledger.writeoff_weight_kg = _round_weight(to_decimal(ledger.writeoff_weight_kg) + remaining)
        ledger.outside_weight_kg = Decimal("0")
        db.add(ledger)
        db.flush()
        _append_txn(
            db,
            ledger=ledger,
            txn_type="结单核销",
            weight_delta_kg=-remaining,
            biz_time=now,
            operator_user_id=operator_user_id,
            remark=remark or "该批材料结单时确认库外剩余重量已无现场存量",
        )

    ledger.status = "锁单"
    ledger.miniapp_selectable = 0
    ledger.locked_at = now
    ledger.lock_count = int(ledger.lock_count or 0) + 1
    db.add(ledger)
    db.flush()
    _append_txn(
        db,
        ledger=ledger,
        txn_type="该批材料结单",
        biz_time=now,
        operator_user_id=operator_user_id,
        remark=remark or "该批材料已结单，小程序不再显示该材料库存批次号",
    )
    return ledger
