package com.ffii.fpsms.modules.stock.web.model

import com.fasterxml.jackson.annotation.JsonProperty
import java.math.BigDecimal

data class ItemLotTraceLotInfo(
    val inventoryLotId: Long,
    val lotNo: String,
    val itemId: Long,
    val itemCode: String,
    val itemName: String,
    val expiryDate: String?,
    val productionDate: String?,
    val stockInDate: String?,
    val uom: String,
    val primaryStockInLineId: Long?,
)

data class ItemLotTraceWarehouseLine(
    val inventoryLotLineId: Long,
    val warehouseCode: String,
    val inQty: BigDecimal,
    val outQty: BigDecimal,
    val issueQty: BigDecimal,
    val status: String,
    val availableQty: BigDecimal,
)

data class ItemLotTraceOrigin(
    val stockInLineId: Long,
    val type: String,
    val refCode: String,
    val refId: Long?,
    val supplierCode: String,
    val supplierName: String,
    val dnNo: String,
    val receiptDate: String?,
    val acceptedQty: BigDecimal,
    val status: String,
)

/** Purchase order line context before goods receipt (採購). */
data class ItemLotTracePurchaseEvent(
    val purchaseOrderLineId: Long,
    val purchaseOrderId: Long?,
    val purchaseOrderCode: String,
    val itemCode: String,
    val itemName: String,
    val orderQty: BigDecimal,
    val putAwayQty: BigDecimal,
    val purchaseUnit: String,
    val supplierCode: String,
    val supplierName: String,
    val orderDate: String?,
    val status: String,
)

data class ItemLotTraceQcResult(
    val stockInLineId: Long,
    val qcPassed: Boolean,
    val failQty: BigDecimal,
    val acceptedQty: BigDecimal,
    val remarks: String,
    val handledBy: String,
    val created: String?,
    val qcItemCode: String = "",
    val qcItemName: String = "",
    val qcItemDescription: String = "",
    val qcType: String = "",
)

data class ItemLotTraceMovement(
    val direction: String,
    val qty: BigDecimal,
    val movementType: String,
    val refType: String,
    val refCode: String,
    val refId: Long?,
    val warehouseCode: String,
    val handledBy: String,
    val timestamp: String?,
    val remarks: String,
    /** Populated for DO refType — pick_order.consoCode / ticket metadata for outbound chips. */
    val pickConsoCode: String = "",
    val outboundTicketNo: String = "",
    val deliveryOrderPickOrderId: Long? = null,
    val relationshipId: Long? = null,
    @get:JsonProperty("doOutboundIsExtra")
    val doOutboundIsExtra: Boolean = false,
    @get:JsonProperty("doOutboundIsReplenish")
    val doOutboundIsReplenish: Boolean = false,
    @get:JsonProperty("doOutboundQtyChanged")
    val doOutboundQtyChanged: Boolean = false,
    val processingStatus: String = "",
    val matchStatus: String = "",
)

/** Warehouse putaway: inventory_lot_line created when stock is shelved after receipt/QC. */
data class ItemLotTracePutawayEvent(
    val inventoryLotLineId: Long,
    val stockInLineId: Long?,
    val warehouseCode: String,
    val qty: BigDecimal,
    val handledBy: String,
    val timestamp: String?,
    val refType: String,
    val refCode: String,
    val refId: Long?,
    /** stock_in_line.status — received = pending putaway, completed = put away done. */
    val status: String = "",
)

data class ItemLotTraceOutboundUsage(
    val stockOutLineId: Long,
    val qty: BigDecimal,
    val pickOrderCode: String,
    val pickOrderId: Long?,
    val consoCode: String,
    val jobOrderCode: String,
    val jobOrderId: Long?,
    val deliveryOrderCode: String,
    val handler: String,
    val timestamp: String?,
    val usageType: String,
)

data class ItemLotTraceStockTakeRecordDetail(
    val stockTakeLineId: Long,
    val bookQty: BigDecimal,
    val pickerFirstQty: BigDecimal? = null,
    val pickerFirstBadQty: BigDecimal? = null,
    val pickerSecondQty: BigDecimal? = null,
    val pickerSecondBadQty: BigDecimal? = null,
    val approverQty: BigDecimal? = null,
    val approverBadQty: BigDecimal? = null,
    val stockTakerName: String? = null,
    val approverName: String? = null,
    val stockTakeStartTime: String? = null,
    val stockTakeEndTime: String? = null,
    val approverTime: String? = null,
    val recordStatus: String? = null,
    val lastSelect: Int? = null,
    val varianceQty: BigDecimal? = null,
)

data class ItemLotTraceStockTakeEvent(
    val stockTakeCode: String,
    val lotNo: String = "",
    val itemCode: String = "",
    val itemName: String = "",
    val warehouseCode: String = "",
    val stockTakeSection: String = "",
    val stockTakeRoundId: Long? = null,
    val stockTakeRoundName: String = "",
    val varianceQty: BigDecimal,
    val beforeQty: BigDecimal,
    val afterQty: BigDecimal? = null,
    val approver: String,
    val timestamp: String?,
    val recordDetail: ItemLotTraceStockTakeRecordDetail? = null,
)

data class ItemLotTraceAdjustment(
    val adjustmentType: String,
    val direction: String,
    val qty: BigDecimal,
    val reason: String,
    val handledBy: String,
    val timestamp: String?,
    val refCode: String,
    val warehouseCode: String = "",
)

data class ItemLotTraceTransfer(
    val fromWarehouse: String,
    val toWarehouse: String,
    val qty: BigDecimal,
    val transferCode: String,
    val timestamp: String?,
)

data class ItemLotTraceBomDownstream(
    val jobOrderCode: String,
    val jobOrderId: Long?,
    val finishedItemCode: String,
    val finishedLotNo: String,
    val finishedStockInLineId: Long?,
    val fgQty: BigDecimal,
    val fgUom: String = "",
    val materialQtyUsed: BigDecimal,
)

data class ItemLotTraceBomUpstream(
    val jobOrderCode: String,
    val jobOrderId: Long?,
    val materialItemCode: String,
    val materialLotNo: String,
    val materialStockInLineId: Long?,
    val materialQty: BigDecimal,
    val bomQtyPerUnit: BigDecimal?,
)

data class ItemLotTraceBomRecipeLine(
    val materialItemCode: String,
    val materialItemName: String,
    val qtyPerUnit: BigDecimal,
    val uom: String,
    val bomProcessId: Long? = null,
    val bomProcessSeqNo: Long? = null,
    val assignedStepName: String = "",
)

data class ItemLotTraceStepMaterialRef(
    val materialItemCode: String,
    val materialItemName: String,
    val qtyPerUnit: BigDecimal,
    val uom: String,
)

data class ItemLotTraceBomTrace(
    val direction: String,
    val downstream: List<ItemLotTraceBomDownstream>,
    val upstream: List<ItemLotTraceBomUpstream>,
    val bomRecipe: List<ItemLotTraceBomRecipeLine>,
)

data class ItemLotTraceJoContext(
    val jobOrderId: Long,
    val jobOrderCode: String,
    val planStart: String?,
    /** Earliest jo_pick_order.created for this JO (actual work-order creation time). */
    val createdAt: String?,
    val reqQty: BigDecimal,
    val status: String,
)

data class ItemLotTraceJoPickLine(
    val pickOrderLineId: Long,
    val itemCode: String,
    val itemName: String,
    val requiredQty: BigDecimal,
    val pickedQty: BigDecimal,
    val status: String,
)

data class ItemLotTraceJoPickOrder(
    val pickOrderId: Long,
    val pickOrderCode: String,
    val consoCode: String,
    val status: String,
    val targetDate: String?,
    val completeDate: String?,
    val releasedDate: String?,
    val lines: List<ItemLotTraceJoPickLine>,
)

data class ItemLotTraceMaterialInput(
    val jobOrderCode: String,
    val jobOrderId: Long?,
    val materialItemCode: String,
    val materialItemName: String,
    val materialLotNo: String,
    val materialStockInLineId: Long?,
    val materialInventoryLotId: Long?,
    val materialQty: BigDecimal,
    val materialUom: String = "",
    val bomQtyPerUnit: BigDecimal?,
    val bomProcessId: Long? = null,
    val bomProcessSeqNo: Long? = null,
    val assignedStepName: String = "",
    val pickOrderCode: String,
    val pickOrderId: Long?,
    val consoCode: String,
    val pickedAt: String?,
    val processingStatus: String = "",
    val matchStatus: String = "",
    val stockInOrigin: ItemLotTraceOrigin?,
    val purchaseEvents: List<ItemLotTracePurchaseEvent> = emptyList(),
    val qcResults: List<ItemLotTraceQcResult>,
    val putawayEvents: List<ItemLotTracePutawayEvent> = emptyList(),
    /** When this material was produced by another JO, its upstream pick/prelude chain. */
    val nestedJoPrelude: ItemLotTraceJoPrelude? = null,
    /** Production steps from the JO that produced this material lot. */
    val productionSteps: List<ItemLotTraceProductionStep> = emptyList(),
)

data class ItemLotTraceJoPrelude(
    val jobOrder: ItemLotTraceJoContext,
    val pickOrders: List<ItemLotTraceJoPickOrder>,
    val materialInputs: List<ItemLotTraceMaterialInput>,
)

data class ItemLotTraceProductionStep(
    val processLineId: Long,
    val stepName: String,
    val description: String,
    val equipmentCode: String,
    val equipmentName: String,
    val operatorName: String,
    val startTime: String?,
    val endTime: String?,
    val outputQty: BigDecimal,
    val scrapQty: BigDecimal,
    val defectQty: BigDecimal,
    val status: String,
    val seqNo: Long?,
    val bomProcessId: Long? = null,
    val stepMaterials: List<ItemLotTraceStepMaterialRef> = emptyList(),
)

/** Sibling byproduct lot from the same job order (independent item/lot, traceable). */
data class ItemLotTraceByproductLot(
    val inventoryLotId: Long?,
    val stockInLineId: Long?,
    val lotNo: String,
    val itemCode: String,
    val itemName: String,
    val qty: BigDecimal,
    val uom: String,
    val processStepName: String,
    val producedAt: String?,
    val jobOrderCode: String,
    val jobOrderId: Long?,
)

/** Opening-inventory receipt (type OPEN), distinct from ADJ adjustments. */
data class ItemLotTraceOpenMovement(
    val stockInLineId: Long,
    val qty: BigDecimal,
    val warehouseCode: String,
    val refCode: String,
    val handledBy: String,
    val timestamp: String?,
    val remarks: String,
)

/** Pick/warehouse failure (missing, broken, etc.) from fail_inventory_lot_line. */
data class ItemLotTraceFailEvent(
    val failId: Long,
    val failType: String,
    val category: String,
    val qty: BigDecimal,
    val handlerName: String,
    val recordDate: String?,
    val pickOrderCode: String,
    val pickOrderId: Long?,
    val warehouseCode: String = "",
)

/** Finished-good delivery via DO pick path. */
data class ItemLotTraceDoDelivery(
    val stockOutLineId: Long,
    val qty: BigDecimal,
    val pickOrderCode: String,
    val pickOrderId: Long?,
    val consoCode: String,
    /** Resolved outbound ticket (dop.ticketNo, legacy ticket_no, or TI-* conso). */
    val ticketNo: String = "",
    val deliveryOrderCode: String,
    val deliveryOrderId: Long?,
    val shopCode: String,
    val shopName: String,
    val handler: String,
    val timestamp: String?,
    /** delivery_order_pick_order.releaseType when linked (else legacy release_type). */
    val releaseType: String? = null,
    val deliveryOrderPickOrderId: Long? = null,
    /** Merge lineage on delivery_order_pick_order (equals own id until retired into TI-M). */
    val relationshipId: Long? = null,
    /** DN from workbench dop or legacy do_pick_order_record. */
    val deliveryNoteCode: String = "",
    /** Present when outbound came from completed legacy GoodPick record. */
    val doPickOrderRecordId: Long? = null,
    /** Matches delivery note sequence suffix (加單). */
    @get:JsonProperty("isExtra")
    val isExtra: Boolean = false,
    /** Matches delivery note sequence suffix (補貨). */
    @get:JsonProperty("isReplenish")
    val isReplenish: Boolean = false,
    /**
     * True when pick_order_line.qty differs from SUM(stock_out_line.qty) for that line
     * (改數). Multi-lot picks are aggregated so splitting across lots alone is not 改數.
     */
    @get:JsonProperty("qtyChanged")
    val qtyChanged: Boolean = false,
)

/** DO replenishment created for a source delivery order line (建立補貨). */
data class ItemLotTraceReplenishmentEvent(
    val replenishmentId: Long,
    val replenishmentCode: String,
    val sourceDoCode: String,
    val sourceDoId: Long?,
    val shopCode: String,
    val shopName: String,
    val itemNo: String,
    val itemName: String,
    val reason: String,
    val replenishQty: BigDecimal,
    val handler: String,
    val timestamp: String?,
    val pickOrderLineId: Long?,
    /** Stock-out line that fulfilled this replenishment pick (links to DO_OUT node). */
    val stockOutLineId: Long?,
)

/** Supplier / stock return (outbound not tied to JO pick or DO). */
data class ItemLotTraceReturnEvent(
    val stockOutLineId: Long,
    val qty: BigDecimal,
    val refCode: String,
    val movementType: String,
    val warehouseCode: String,
    val handler: String,
    val timestamp: String?,
    val remarks: String,
)

/** Repack / split: sibling lot linked by transfer, same JO, or non-PO lineage (not independent PO receipts). */
data class ItemLotTraceLotRelation(
    val relationType: String,
    val inventoryLotId: Long?,
    val lotNo: String,
    val itemCode: String,
    val itemName: String,
    val qty: BigDecimal,
    val productLotNo: String,
    val timestamp: String?,
)

/** Same lotNo+itemId may exist on multiple inventory_lot rows (e.g. after transfer / repack). */
data class ItemLotTraceAlternateLocation(
    val inventoryLotId: Long,
    val stockInLineId: Long?,
    val inventoryLotLineId: Long,
    val warehouseCode: String,
    val availableQty: BigDecimal,
)

/**
 * Per-location lifecycle trace block for one alternate inventory_lot sharing the same lotNo+itemId.
 *
 * Contains only location-scoped dimensions (warehouse lines, origins, purchase events, QC results,
 * putaway events, movements, outbound usage, stock take events, adjustments, transfers, open
 * movements, fail events, DO deliveries, return events).
 *
 * Does NOT duplicate lot-level dimensions (bomTrace, joPrelude, productionSteps, byproductLots,
 * lotRelations, traceGraph) — those are already at the top level of [ItemLotTraceResponse].
 * Does NOT contain [alternateLocations] or nested [locationBlocks] to avoid infinite recursion.
 */
data class ItemLotTraceLocationBlock(
    val inventoryLotId: Long,
    val lotNo: String,
    val itemCode: String,
    val itemName: String = "",
    val uom: String = "",
    val expiryDate: String? = null,
    val productionDate: String? = null,
    val stockInDate: String? = null,
    val stockInLineId: Long?,
    val warehouseLines: List<ItemLotTraceWarehouseLine>,
    val origins: List<ItemLotTraceOrigin>,
    val purchaseEvents: List<ItemLotTracePurchaseEvent> = emptyList(),
    val qcResults: List<ItemLotTraceQcResult> = emptyList(),
    val putawayEvents: List<ItemLotTracePutawayEvent> = emptyList(),
    val movements: List<ItemLotTraceMovement> = emptyList(),
    val outboundUsage: List<ItemLotTraceOutboundUsage> = emptyList(),
    val stockTakeEvents: List<ItemLotTraceStockTakeEvent> = emptyList(),
    val adjustments: List<ItemLotTraceAdjustment> = emptyList(),
    val transfers: List<ItemLotTraceTransfer> = emptyList(),
    val openMovements: List<ItemLotTraceOpenMovement> = emptyList(),
    val failEvents: List<ItemLotTraceFailEvent> = emptyList(),
    val doDeliveries: List<ItemLotTraceDoDelivery> = emptyList(),
    val replenishmentEvents: List<ItemLotTraceReplenishmentEvent> = emptyList(),
    val returnEvents: List<ItemLotTraceReturnEvent> = emptyList(),
)

data class ItemLotTraceGraphEdge(
    val fromKey: String,
    val toKey: String,
    val kind: String = "FLOW",
)

data class ItemLotTraceGraphNodeRef(
    val key: String,
    val kind: String,
    val scopeKey: String? = null,
    val refCode: String? = null,
    val traceLotNo: String? = null,
    val traceItemCode: String? = null,
    /** Warehouse code for multi-location visual differentiation. Non-null when this node
     * originates from an alternate location's trace block. */
    val warehouseCode: String? = null,
)

data class ItemLotTraceGraph(
    val nodes: List<ItemLotTraceGraphNodeRef>,
    val edges: List<ItemLotTraceGraphEdge>,
)

/**
 * Lot genealogy / operational lifecycle trace response.
 *
 * v1 commercial scope (intentional): qty, warehouse, QC, document refs, handlers,
 * expiry/production dates, multi-location blocks, and compiled flow graph.
 * Cost / unit price / currency / inventory valuation and external customer /
 * sales-order / invoice fields are out of scope until a finance-module decision.
 */
data class ItemLotTraceResponse(
    val lot: ItemLotTraceLotInfo,
    val warehouseLines: List<ItemLotTraceWarehouseLine>,
    val origins: List<ItemLotTraceOrigin>,
    val purchaseEvents: List<ItemLotTracePurchaseEvent> = emptyList(),
    val qcResults: List<ItemLotTraceQcResult>,
    val putawayEvents: List<ItemLotTracePutawayEvent>,
    val movements: List<ItemLotTraceMovement>,
    val outboundUsage: List<ItemLotTraceOutboundUsage>,
    val stockTakeEvents: List<ItemLotTraceStockTakeEvent>,
    val adjustments: List<ItemLotTraceAdjustment>,
    val transfers: List<ItemLotTraceTransfer>,
    val bomTrace: ItemLotTraceBomTrace,
    val joPrelude: ItemLotTraceJoPrelude?,
    val productionSteps: List<ItemLotTraceProductionStep>,
    val byproductLots: List<ItemLotTraceByproductLot>,
    val openMovements: List<ItemLotTraceOpenMovement>,
    val failEvents: List<ItemLotTraceFailEvent>,
    val doDeliveries: List<ItemLotTraceDoDelivery>,
    val replenishmentEvents: List<ItemLotTraceReplenishmentEvent> = emptyList(),
    val returnEvents: List<ItemLotTraceReturnEvent>,
    val lotRelations: List<ItemLotTraceLotRelation>,
    val alternateLocations: List<ItemLotTraceAlternateLocation> = emptyList(),
    /** Per-location full trace blocks for each alternate [inventoryLotId] sharing the same lotNo+itemId.
     * Excludes the primary lot (whose data is already at the top level).
     * Lot-level dimensions (BOM, JO prelude, production, byproducts, lot relations, traceGraph)
     * are NOT duplicated here — see [ItemLotTraceLocationBlock] for what IS included. */
    val locationBlocks: List<ItemLotTraceLocationBlock> = emptyList(),
    val traceGraph: ItemLotTraceGraph? = null,
)
