package com.ffii.fpsms.modules.pickOrder.service

import com.ffii.core.support.JdbcDao
import java.util.Locale
import kotlin.system.measureTimeMillis

/**
 * Shared hierarchical FG payload (fgInfo + merged pickOrders with lines/lots) for both
 * legacy [do_pick_order_line] and workbench [delivery_order_pick_order] flows.
 */
fun assembleHierarchicalFgPayload(
    jdbcDao: JdbcDao,
    doPickOrderId: Long,
    doPickOrderInfo: Map<String, Any?>,
    pickOrdersInfo: List<Map<String, Any?>>,
    /** DO workbench: treat pickable qty as in - out (ignore hold). Legacy FG uses in - out - hold. */
    availableQtyInOutOnly: Boolean = false,
    /** Optional perf sink for endpoint-level timing visibility. */
    timingSink: ((String, Long) -> Unit)? = null,
): Map<String, Any?> {
    val availExpr =
        if (availableQtyInOutOnly) {
            "(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0))"
        } else {
            "(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))"
        }
    val doTicketStatus = doPickOrderInfo["doTicketStatus"]

    val allPickOrderLines = mutableListOf<Map<String, Any?>>()
    val lineCountsPerPickOrder = mutableListOf<Int>()
    val pickOrderIds = mutableListOf<Long>()
    val pickOrderCodes = mutableListOf<String>()
    val doOrderIds = mutableListOf<Long>()
    val deliveryOrderCodes = mutableListOf<String>()

    val poRows = pickOrdersInfo.mapNotNull { poInfo ->
        val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() ?: return@mapNotNull null
        val pickOrderCode = poInfo["pick_order_code"] as? String
        val doOrderId = (poInfo["do_order_id"] as? Number)?.toLong()
        val deliveryOrderCode = poInfo["delivery_order_code"] as? String
        pickOrderCode?.let { pickOrderCodes.add(it) }
        doOrderId?.let { doOrderIds.add(it) }
        deliveryOrderCode?.let { deliveryOrderCodes.add(it) }
        pickOrderIds.add(pickOrderId)
        pickOrderId to poInfo
    }

    var linesQueryTotalMs = 0L
    var lineTransformTotalMs = 0L
    var allLinesResults: List<Map<String, Any?>> = emptyList()
    if (pickOrderIds.isNotEmpty()) {
        val inClause = pickOrderIds.joinToString(",")
        val linesSql = """
        WITH target_pol AS (
    SELECT pol.id
    FROM fpsmsdb.pick_order_line pol
    JOIN fpsmsdb.pick_order po ON po.id = pol.poId
    WHERE po.id IN ($inClause)
      AND po.deleted = false
      AND pol.deleted = false
),
ll AS (
    SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId
    FROM fpsmsdb.suggested_pick_lot spl
    JOIN target_pol tp ON tp.id = spl.pickOrderLineId
    WHERE spl.deleted = false

    UNION

    SELECT sol.pickOrderLineId, sol.inventoryLotLineId AS lotLineId
    FROM fpsmsdb.stock_out_line sol
    JOIN target_pol tp ON tp.id = sol.pickOrderLineId
    WHERE sol.deleted = false
),
sm AS (
    SELECT s.pickOrderLineId, s.suggestedLotLineId, MAX(s.id) AS maxId
    FROM fpsmsdb.suggested_pick_lot s
    JOIN target_pol tp ON tp.id = s.pickOrderLineId
    WHERE s.deleted = false
    GROUP BY s.pickOrderLineId, s.suggestedLotLineId
)
SELECT 
    po.id as pickOrderId,
    po.code as pickOrderCode,
    po.consoCode as pickOrderConsoCode,
    DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
    po.type as pickOrderType,
    po.status as pickOrderStatus,
    po.assignTo as pickOrderAssignTo,

    pol.id as pickOrderLineId,
    pol.qty as pickOrderLineRequiredQty,
    pol.status as pickOrderLineStatus,

    i.id as itemId,
    i.code as itemCode,
    i.name as itemName,
    i.item_Order as itemOrder,
    uc.code as uomCode,
    uc.udfudesc as uomDesc,
    uc.udfShortDesc as uomShortDesc,

    ill.id as lotId,
    il.lotNo,
    DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
    w.name as location,
    COALESCE(uc.udfudesc, 'N/A') as stockUnit,
    il.stockInLineId as stockInLineId,
    w.`order` as routerIndex,
    w.code as routerRoute,

    CASE 
        WHEN sol.status = 'rejected' THEN NULL
        ELSE $availExpr
    END as availableQty,

    COALESCE(spl.qty, sol.qty, 0) as requiredQty,
    COALESCE(sol.qty, 0) as actualPickQty,
    spl.id as suggestedPickLotId,
    ill.status as lotStatus,
    sol.id as stockOutLineId,
    sol.status as stockOutLineStatus,
    COALESCE(sol.qty, 0) as stockOutLineQty,
    COALESCE(ill.inQty, 0) as inQty,
    COALESCE(ill.outQty, 0) as outQty,
    COALESCE(ill.holdQty, 0) as holdQty,

    CASE 
        WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
        WHEN sol.status = 'rejected' THEN 'rejected'
        WHEN LOWER(TRIM(COALESCE(sol.status, ''))) IN ('completed', 'partially_completed') THEN 'available'
        WHEN $availExpr <= 0 THEN 'insufficient_stock'
        WHEN ill.status = 'unavailable' THEN 'status_unavailable'
        ELSE 'available'
    END as lotAvailability,

    CASE 
        WHEN sol.status = 'completed' THEN 'completed'
        WHEN sol.status = 'rejected' THEN 'rejected'
        WHEN sol.status = 'created' THEN 'pending'
        ELSE 'pending'
    END as processingStatus
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
JOIN fpsmsdb.items i ON i.id = pol.itemId
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId

LEFT JOIN ll ON ll.pickOrderLineId = pol.id

LEFT JOIN sm
  ON sm.pickOrderLineId = pol.id
 AND sm.suggestedLotLineId <=> ll.lotLineId
LEFT JOIN fpsmsdb.suggested_pick_lot spl
  ON spl.id = sm.maxId
 AND spl.deleted = false

LEFT JOIN fpsmsdb.stock_out_line sol
  ON sol.pickOrderLineId = pol.id
 AND ((sol.inventoryLotLineId = ll.lotLineId)
   OR (sol.inventoryLotLineId IS NULL AND ll.lotLineId IS NULL))
 AND sol.deleted = false

LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
WHERE po.id IN ($inClause)
  AND po.deleted = false
ORDER BY 
    po.id ASC,
    COALESCE(w.`order`, 999999) ASC,
    pol.id ASC,
    il.lotNo ASC
        """.trimIndent()

        linesQueryTotalMs = measureTimeMillis {
            allLinesResults = jdbcDao.queryForList(linesSql)
        }
        timingSink?.invoke("assemble.batchQueryMs", linesQueryTotalMs)
    }

    val linesByPickOrderId = allLinesResults.groupBy { (it["pickOrderId"] as? Number)?.toLong() ?: -1L }

    poRows.forEach { (pickOrderId, _) ->
        val linesResults = linesByPickOrderId[pickOrderId].orEmpty()
        var pickOrderLines: List<Map<String, Any?>> = emptyList()
        val transformMs = measureTimeMillis {
            val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() }

            pickOrderLines = lineGroups.map { (lineId, lineRows) ->
                val firstLineRow = lineRows.firstOrNull()

                if (firstLineRow == null) {
                    null
                } else {
                    val lots = if (lineRows.any { it["lotId"] != null }) {
                        lineRows.filter { it["lotId"] != null }.map { lotRow ->
                            mapOf(
                                "id" to lotRow["lotId"],
                                "lotNo" to lotRow["lotNo"],
                                "expiryDate" to lotRow["expiryDate"],
                                "location" to lotRow["location"],
                                "stockUnit" to lotRow["stockUnit"],
                                "availableQty" to lotRow["availableQty"],
                                "requiredQty" to lotRow["requiredQty"],
                                "actualPickQty" to lotRow["actualPickQty"],
                                "inQty" to lotRow["inQty"],
                                "outQty" to lotRow["outQty"],
                                "holdQty" to lotRow["holdQty"],
                                "lotStatus" to lotRow["lotStatus"],
                                "lotAvailability" to lotRow["lotAvailability"],
                                "processingStatus" to lotRow["processingStatus"],
                                "suggestedPickLotId" to lotRow["suggestedPickLotId"],
                                "stockOutLineId" to lotRow["stockOutLineId"],
                                "stockOutLineStatus" to lotRow["stockOutLineStatus"],
                                "stockOutLineQty" to lotRow["stockOutLineQty"],
                                "stockInLineId" to lotRow["stockInLineId"],
                                "router" to mapOf(
                                    "id" to null,
                                    "index" to lotRow["routerIndex"],
                                    "route" to lotRow["routerRoute"],
                                    "area" to lotRow["routerRoute"],
                                    "itemCode" to lotRow["itemId"],
                                    "itemName" to lotRow["itemName"],
                                    "uomId" to lotRow["uomCode"],
                                    "noofCarton" to lotRow["requiredQty"],
                                ),
                            )
                        }
                    } else {
                        emptyList()
                    }

                    val stockouts = lineRows
                        .filter { it["stockOutLineId"] != null }
                        .distinctBy { row -> row["stockOutLineId"] }
                        .map { row ->
                            val lotId = row["lotId"]
                            val noLot = (lotId == null)
                            mapOf(
                                "id" to row["stockOutLineId"],
                                "status" to row["stockOutLineStatus"],
                                "qty" to row["stockOutLineQty"],
                                "requiredQty" to row["requiredQty"],
                                "suggestedPickLotQty" to row["requiredQty"],
                                "suggestedPickLotId" to row["suggestedPickLotId"],
                                "lotId" to lotId,
                                "lotNo" to (row["lotNo"] ?: ""),
                                "location" to (row["location"] ?: ""),
                                "availableQty" to row["availableQty"],
                                "stockInLineId" to row["stockInLineId"],
                                "noLot" to noLot,
                            )
                        }

                    mapOf(
                        "id" to lineId,
                        "requiredQty" to firstLineRow["pickOrderLineRequiredQty"],
                        "status" to firstLineRow["pickOrderLineStatus"],
                        "itemOrder" to firstLineRow["itemOrder"],
                        "item" to mapOf(
                            "id" to firstLineRow["itemId"],
                            "code" to firstLineRow["itemCode"],
                            "name" to firstLineRow["itemName"],
                            "uomCode" to firstLineRow["uomCode"],
                            "uomDesc" to firstLineRow["uomDesc"],
                            "uomShortDesc" to firstLineRow["uomShortDesc"],
                        ),
                        "lots" to lots,
                        "stockouts" to stockouts,
                    )
                }
            }.filterNotNull()
        }
        lineTransformTotalMs += transformMs
        timingSink?.invoke("assemble.po.${pickOrderId ?: 0}.transformMs", transformMs)

        lineCountsPerPickOrder.add(pickOrderLines.size)
        allPickOrderLines.addAll(pickOrderLines)
    }

    val doStoreFloorKey = doPickOrderInfo["store_id"]?.toString()?.trim()?.uppercase(Locale.ROOT)
        ?.replace("/", "")
        ?.replace(" ", "")
        ?: ""
    val sortMs = measureTimeMillis {
        when (doStoreFloorKey) {
            "2F" -> {
                allPickOrderLines.sortWith(
                    compareBy(
                        { line ->
                            val v = line["itemOrder"] ?: line["itemorder"]
                            when (v) {
                                is Number -> v.toInt()
                                else -> 999999
                            }
                        },
                        { line -> (line["id"] as? Number)?.toLong() ?: Long.MAX_VALUE },
                    ),
                )
            }
            "4F" -> {
            }
            else -> {
                allPickOrderLines.sortWith(
                    compareBy { line ->
                        val lots = line["lots"] as? List<Map<String, Any?>>
                        val firstLot = lots?.firstOrNull()
                        val router = firstLot?.get("router") as? Map<String, Any?>
                        val indexValue = router?.get("index")
                        when (indexValue) {
                            is Number -> indexValue.toInt()
                            is String -> {
                                val parts = indexValue.split("-")
                                if (parts.size > 1) {
                                    parts.last().toIntOrNull() ?: 999999
                                } else {
                                    indexValue.toIntOrNull() ?: 999999
                                }
                            }
                            else -> 999999
                        }
                    },
                )
            }
        }
    }
    timingSink?.invoke("assemble.linesQueryTotalMs", linesQueryTotalMs)
    timingSink?.invoke("assemble.lineTransformTotalMs", lineTransformTotalMs)
    timingSink?.invoke("assemble.sortMs", sortMs)

    val fgInfo = mapOf(
        "doPickOrderId" to doPickOrderId,
        "ticketNo" to doPickOrderInfo["ticket_no"],
        "storeId" to doPickOrderInfo["store_id"],
        "shopCode" to doPickOrderInfo["ShopCode"],
        "shopName" to doPickOrderInfo["ShopName"],
        "truckLanceCode" to doPickOrderInfo["TruckLanceCode"],
        "departureTime" to doPickOrderInfo["truck_departure_time"],
    )
    val allConsoCodes = pickOrdersInfo.mapNotNull { it["consoCode"] as? String }.distinct()

    val mergedPickOrder = if (pickOrdersInfo.isNotEmpty()) {
        val firstPickOrderInfo = pickOrdersInfo.first()
        mapOf(
            "pickOrderIds" to pickOrderIds,
            "pickOrderCodes" to pickOrderCodes,
            "doOrderIds" to doOrderIds,
            "deliveryOrderCodes" to deliveryOrderCodes,
            "lineCountsPerPickOrder" to lineCountsPerPickOrder,
            "consoCodes" to allConsoCodes,
            "status" to doTicketStatus,
            "targetDate" to firstPickOrderInfo["targetDate"],
            "pickOrderLines" to allPickOrderLines,
        )
    } else {
        null
    }

    return mapOf(
        "fgInfo" to fgInfo,
        "pickOrders" to listOfNotNull(mergedPickOrder),
    )
}
