package com.ffii.fpsms.modules.stock.trace

import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.stock.web.model.*
import org.springframework.stereotype.Component

@Component
open class TraceMovementLoader(
    private val jdbcDao: JdbcDao,
    private val sql: TraceSqlSupport,
) {
    open fun loadMovements(inventoryLotId: Long, lotLineIds: List<Long>): List<ItemLotTraceMovement> {
        if (lotLineIds.isEmpty()) return emptyList()
        val query = """
            SELECT * FROM (
                SELECT
                    'IN' AS direction,
                    COALESCE(sil.acceptedQty, ill.inQty, 0) AS qty,
                    COALESCE(UPPER(sil.type), 'IN') AS movementType,
                    CASE
                        WHEN sil.purchaseOrderId IS NOT NULL THEN 'PO'
                        WHEN sil.jobOrderId IS NOT NULL THEN 'JO'
                        WHEN sil.stockTakeLineId IS NOT NULL THEN 'TKE'
                        WHEN sil.stockTransferId IS NOT NULL THEN 'TRANSFER'
                        WHEN UPPER(COALESCE(sil.type, '')) IN ('ADJ', 'OPEN') THEN UPPER(sil.type)
                        ELSE 'STOCK_IN'
                    END AS refType,
                    COALESCE(po.code, jo.code, st.code, si.code, '') AS refCode,
                    COALESCE(po.id, jo.id, st.id, si.id) AS refId,
                    COALESCE(wh.code, '') AS warehouseCode,
                    COALESCE(sil.createdBy, '') AS handledBy,
                    COALESCE(
                        (
                            SELECT MIN(jpo.created)
                            FROM pick_order po
                            INNER JOIN jo_pick_order jpo
                                ON jpo.pick_order_id = po.id AND jpo.deleted = 0
                            WHERE po.joId = sil.jobOrderId AND po.deleted = 0
                        ),
                        sil.created,
                        sil.modified,
                        sil.receiptDate
                    ) AS timestamp,
                    COALESCE(sil.remarks, '') AS remarks,
                    '' AS processingStatus,
                    '' AS matchStatus
                FROM stock_in_line sil
                LEFT JOIN stock_in si ON sil.stockInId = si.id AND si.deleted = 0
                LEFT JOIN purchase_order po ON sil.purchaseOrderId = po.id AND po.deleted = 0
                LEFT JOIN job_order jo ON sil.jobOrderId = jo.id AND jo.deleted = 0
                LEFT JOIN stock_take_line stl ON sil.stockTakeLineId = stl.id AND stl.deleted = 0
                LEFT JOIN stock_take st ON stl.stockTakeId = st.id AND st.deleted = 0
                LEFT JOIN inventory_lot_line ill ON sil.inventoryLotLineId = ill.id AND ill.deleted = 0
                LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0
                WHERE sil.deleted = 0
                  AND (
                    sil.inventoryLotId = :inventoryLotId
                    OR ill.inventoryLotId = :inventoryLotId
                  )

                UNION ALL

                SELECT
                    'OUT' AS direction,
                    COALESCE(sol.qty, 0) AS qty,
                    COALESCE(UPPER(so.type), UPPER(sol.type), 'OUT') AS movementType,
                    CASE
                        WHEN sol.pickOrderLineId IS NOT NULL AND UPPER(COALESCE(po.type, '')) = 'DO' THEN 'DO'
                        WHEN sol.pickOrderLineId IS NOT NULL AND UPPER(COALESCE(po.type, '')) = 'JO' THEN 'JO_PICK'
                        WHEN sol.pickOrderLineId IS NOT NULL AND po.joId IS NOT NULL THEN 'JO_PICK'
                        WHEN sol.pickOrderLineId IS NOT NULL AND po.doId IS NOT NULL THEN 'DO'
                        WHEN sol.pickOrderLineId IS NOT NULL AND dpo.id IS NOT NULL THEN 'DO'
                        WHEN sol.pickOrderLineId IS NOT NULL AND dpor.id IS NOT NULL THEN 'DO'
                        WHEN sol.pickOrderLineId IS NOT NULL THEN 'PO_PICK'
                        WHEN sol.stockTransferId IS NOT NULL THEN 'TRANSFER'
                        WHEN UPPER(COALESCE(so.type, '')) IN ('ADJ', 'TKE') THEN UPPER(so.type)
                        ELSE 'STOCK_OUT'
                    END AS refType,
                    COALESCE(po.code, do_tbl.code, so.consoPickOrderCode, so.deliveryOrderCode, '') AS refCode,
                    COALESCE(po.id, do_tbl.id, so.id) AS refId,
                    COALESCE(wh.code, '') AS warehouseCode,
                    COALESCE(picker_user.name, sol.createdBy, '') AS handledBy,
                    COALESCE(sol.endTime, sol.pickTime, sol.created) AS timestamp,
                    COALESCE(so.remarks, '') AS remarks,
                    CASE
                        WHEN sol.status = 'completed' THEN 'completed'
                        WHEN sol.status = 'rejected' THEN 'rejected'
                        WHEN sol.status = 'created' THEN 'pending'
                        ELSE 'pending'
                    END AS processingStatus,
                    COALESCE(jpo.match_status, '') AS matchStatus
                FROM stock_out_line sol
                INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0
                LEFT JOIN pick_order_line pol ON sol.pickOrderLineId = pol.id AND pol.deleted = 0
                LEFT JOIN pick_order po ON pol.poId = po.id AND po.deleted = 0
                LEFT JOIN do_pick_order dpo
                    ON po.id = dpo.pick_order_id AND dpo.deleted = 0
                LEFT JOIN do_pick_order_record dpor
                    ON po.id = dpor.pick_order_id AND dpor.deleted = 0
                LEFT JOIN delivery_order do_tbl
                    ON do_tbl.deleted = 0
                    AND do_tbl.id = COALESCE(po.doId, dpo.do_order_id, dpor.do_order_id)
                LEFT JOIN inventory_lot_line ill ON sol.inventoryLotLineId = ill.id AND ill.deleted = 0
                LEFT JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.deleted = 0
                LEFT JOIN jo_pick_order jpo ON jpo.pick_order_id = po.id
                    AND jpo.item_id = il.itemId AND jpo.deleted = 0
                LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0
                LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
                WHERE sol.deleted = 0 AND sol.inventoryLotLineId IN (:lotLineIds)
            ) movements
            ORDER BY timestamp DESC
        """.trimIndent()
        return jdbcDao.queryForList(
            query,
            mapOf("inventoryLotId" to inventoryLotId, "lotLineIds" to lotLineIds),
        ).map { row ->
            fun col(vararg keys: String): Any? {
                for (k in keys) {
                    if (row.containsKey(k)) return row[k]
                }
                val lower = row.keys.associateBy { it.lowercase() }
                for (k in keys) {
                    lower[k.lowercase()]?.let { return row[it] }
                }
                return null
            }
            ItemLotTraceMovement(
                direction = sql.str(col("direction")),
                qty = sql.decimal(col("qty")),
                movementType = sql.str(col("movementType")),
                refType = sql.str(col("refType")),
                refCode = sql.str(col("refCode")),
                refId = sql.longVal(col("refId")),
                warehouseCode = sql.str(col("warehouseCode")),
                handledBy = sql.str(col("handledBy")),
                timestamp = sql.formatDateTime(col("timestamp")),
                remarks = sql.str(col("remarks")),
                processingStatus = sql.str(col("processingStatus")),
                matchStatus = sql.str(col("matchStatus")),
            )
        }
    }

    /** FP-MTMS Version Checklist | Functions Ref. No. 25 | v1.0.0 | 2026-07-20 */
    open fun loadStockTakeEvents(lotLineIds: List<Long>): List<ItemLotTraceStockTakeEvent> {
        if (lotLineIds.isEmpty()) return emptyList()
        val query = """
            SELECT
                stl.id AS stockTakeLineId,
                COALESCE(st.code, '') AS stockTakeCode,
                COALESCE(il.lotNo, str.lotNo, '') AS lotNo,
                COALESCE(it.code, str.itemCode, '') AS itemCode,
                COALESCE(it.name, str.itemName, '') AS itemName,
                COALESCE(wh.code, '') AS warehouseCode,
                COALESCE(NULLIF(st.stockTakeSection, ''), NULLIF(str.stockTakeSection, ''), '') AS stockTakeSection,
                COALESCE(st.stockTakeRoundId, str.stockTakeRoundId) AS stockTakeRoundId,
                COALESCE(NULLIF(st.stockTakeRoundName, ''), '') AS stockTakeRoundName,
                COALESCE(str.varianceQty, stl.finalQty - stl.initialQty) AS varianceQty,
                stl.initialQty AS beforeQty,
                stl.finalQty AS afterQty,
                COALESCE(NULLIF(str.approverName, ''), NULLIF(str.stockTakerName, ''), '') AS approver,
                COALESCE(str.approverTime, stl.completeDate, stl.modified) AS timestamp,
                COALESCE(str.bookQty, 0) AS bookQty,
                str.pickerFirstStockTakeQty AS pickerFirstQty,
                str.pickerFirstBadQty AS pickerFirstBadQty,
                str.pickerSecondStockTakeQty AS pickerSecondQty,
                str.pickerSecondBadQty AS pickerSecondBadQty,
                str.approverStockTakeQty AS approverQty,
                str.approverBadQty AS approverBadQty,
                str.stockTakerName AS stockTakerName,
                NULLIF(str.approverName, '') AS approverName,
                str.stockTakeStartTime AS stockTakeStartTime,
                str.stockTakeEndTime AS stockTakeEndTime,
                str.approverTime AS approverTime,
                str.status AS recordStatus,
                str.lastSelect AS lastSelect,
                str.varianceQty AS recordVarianceQty,
                str.id AS stockTakeRecordId
            FROM stock_take_line stl
            INNER JOIN stock_take st ON stl.stockTakeId = st.id AND st.deleted = 0
            LEFT JOIN stocktakerecord str ON stl.stockTakeRecordId = str.id AND str.deleted = 0
            LEFT JOIN inventory_lot_line ill ON stl.inventoryLotLineId = ill.id AND ill.deleted = 0
            LEFT JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.deleted = 0
            LEFT JOIN items it ON il.itemId = it.id AND it.deleted = 0
            LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0
            WHERE stl.deleted = 0 AND stl.inventoryLotLineId IN (:lotLineIds)
            ORDER BY timestamp DESC
        """.trimIndent()
        return jdbcDao.queryForList(query, mapOf("lotLineIds" to lotLineIds)).map { row ->
            val stockTakeLineId = sql.longVal(row["stockTakeLineId"])
            val hasRecordDetail = sql.longVal(row["stockTakeRecordId"]) != null
            ItemLotTraceStockTakeEvent(
                stockTakeCode = sql.str(row["stockTakeCode"]),
                lotNo = sql.str(row["lotNo"]),
                itemCode = sql.str(row["itemCode"]),
                itemName = sql.str(row["itemName"]),
                warehouseCode = sql.str(row["warehouseCode"]),
                stockTakeSection = sql.str(row["stockTakeSection"]),
                stockTakeRoundId = sql.longVal(row["stockTakeRoundId"]),
                stockTakeRoundName = sql.str(row["stockTakeRoundName"]),
                varianceQty = sql.decimalOrNull(row["varianceQty"]) ?: java.math.BigDecimal.ZERO,
                beforeQty = sql.decimalOrNull(row["beforeQty"]) ?: java.math.BigDecimal.ZERO,
                afterQty = sql.decimalOrNull(row["afterQty"]),
                approver = sql.str(row["approver"]),
                timestamp = sql.formatDateTime(row["timestamp"]),
                recordDetail = if (hasRecordDetail) {
                    ItemLotTraceStockTakeRecordDetail(
                        stockTakeLineId = stockTakeLineId ?: 0L,
                        bookQty = sql.decimalOrNull(row["bookQty"]) ?: java.math.BigDecimal.ZERO,
                        pickerFirstQty = sql.decimalOrNull(row["pickerFirstQty"]),
                        pickerFirstBadQty = sql.decimalOrNull(row["pickerFirstBadQty"]),
                        pickerSecondQty = sql.decimalOrNull(row["pickerSecondQty"]),
                        pickerSecondBadQty = sql.decimalOrNull(row["pickerSecondBadQty"]),
                        approverQty = sql.decimalOrNull(row["approverQty"]),
                        approverBadQty = sql.decimalOrNull(row["approverBadQty"]),
                        stockTakerName = sql.strOrNull(row["stockTakerName"]),
                        approverName = sql.strOrNull(row["approverName"]),
                        stockTakeStartTime = sql.formatDateTime(row["stockTakeStartTime"]),
                        stockTakeEndTime = sql.formatDateTime(row["stockTakeEndTime"]),
                        approverTime = sql.formatDateTime(row["approverTime"]),
                        recordStatus = sql.strOrNull(row["recordStatus"]),
                        lastSelect = sql.intVal(row["lastSelect"]),
                        varianceQty = sql.decimalOrNull(row["recordVarianceQty"]),
                    )
                } else null,
            )
        }
    }

    open fun loadAdjustments(inventoryLotId: Long, lotLineIds: List<Long>): List<ItemLotTraceAdjustment> {
        if (lotLineIds.isEmpty()) return emptyList()
        val query = """
            SELECT * FROM (
                SELECT
                    COALESCE(UPPER(sil.type), 'ADJ') AS adjustmentType,
                    'IN' AS direction,
                    COALESCE(sil.acceptedQty, 0) AS qty,
                    COALESCE(sil.remarks, '') AS reason,
                    COALESCE(sil.createdBy, '') AS handledBy,
                    COALESCE(sil.created, sil.modified, sil.receiptDate) AS timestamp,
                    COALESCE(si.code, '') AS refCode,
                    COALESCE(wh.code, '') AS warehouseCode
                FROM stock_in_line sil
                INNER JOIN stock_in si ON sil.stockInId = si.id AND si.deleted = 0
                LEFT JOIN inventory_lot_line ill ON sil.inventoryLotLineId = ill.id AND ill.deleted = 0
                LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0
                WHERE sil.deleted = 0 AND sil.inventoryLotId = :inventoryLotId
                  AND UPPER(COALESCE(sil.type, '')) = 'ADJ'

                UNION ALL

                SELECT
                    COALESCE(UPPER(so.type), 'ADJ') AS adjustmentType,
                    'OUT' AS direction,
                    COALESCE(sol.qty, 0) AS qty,
                    COALESCE(so.remarks, '') AS reason,
                    COALESCE(sol.createdBy, '') AS handledBy,
                    COALESCE(sol.endTime, sol.created) AS timestamp,
                    COALESCE(so.consoPickOrderCode, so.deliveryOrderCode, '') AS refCode,
                    COALESCE(wh.code, '') AS warehouseCode
                FROM stock_out_line sol
                INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0
                LEFT JOIN inventory_lot_line ill ON sol.inventoryLotLineId = ill.id AND ill.deleted = 0
                LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0
                WHERE sol.deleted = 0 AND sol.inventoryLotLineId IN (:lotLineIds)
                  AND UPPER(COALESCE(so.type, sol.type, '')) IN ('ADJ', 'TKE')
            ) adj
            ORDER BY timestamp DESC
        """.trimIndent()
        return jdbcDao.queryForList(
            query,
            mapOf("inventoryLotId" to inventoryLotId, "lotLineIds" to lotLineIds),
        ).map { row ->
            ItemLotTraceAdjustment(
                adjustmentType = sql.str(row["adjustmentType"]),
                direction = sql.str(row["direction"]),
                qty = sql.decimal(row["qty"]),
                reason = sql.str(row["reason"]),
                handledBy = sql.str(row["handledBy"]),
                timestamp = sql.formatDateTime(row["timestamp"]),
                refCode = sql.str(row["refCode"]),
                warehouseCode = sql.str(row["warehouseCode"]),
            )
        }
    }

    open fun loadTransfers(inventoryLotId: Long): List<ItemLotTraceTransfer> {
        val query = """
            SELECT
                COALESCE(str.startLocation, wh_from.code, '') AS fromWarehouse,
                COALESCE(str.targetLocation, wh_to.code, '') AS toWarehouse,
                COALESCE(str.transferredQty, 0) AS qty,
                CONCAT('TR-', str.id) AS transferCode,
                COALESCE(str.created, str.modified, sil.created, sil.modified, sil.receiptDate) AS timestamp
            FROM stock_transfer_record str
            LEFT JOIN stock_in_line sil ON str.stockInLineId = sil.id AND sil.deleted = 0
            LEFT JOIN stock_out_line sol ON str.stockOutLineId = sol.id AND sol.deleted = 0
            LEFT JOIN inventory_lot_line ill_from ON sol.inventoryLotLineId = ill_from.id AND ill_from.deleted = 0
            LEFT JOIN warehouse wh_from ON ill_from.warehouseId = wh_from.id AND wh_from.deleted = 0
            LEFT JOIN inventory_lot_line ill_to ON sil.inventoryLotLineId = ill_to.id AND ill_to.deleted = 0
            LEFT JOIN warehouse wh_to ON ill_to.warehouseId = wh_to.id AND wh_to.deleted = 0
            WHERE str.deleted = 0
              AND (
                sil.inventoryLotId = :inventoryLotId
                OR ill_from.inventoryLotId = :inventoryLotId
              )
            ORDER BY timestamp DESC
        """.trimIndent()
        return jdbcDao.queryForList(query, mapOf("inventoryLotId" to inventoryLotId)).map { row ->
            ItemLotTraceTransfer(
                fromWarehouse = sql.str(row["fromWarehouse"]),
                toWarehouse = sql.str(row["toWarehouse"]),
                qty = sql.decimal(row["qty"]),
                transferCode = sql.str(row["transferCode"]),
                timestamp = sql.formatDateTime(row["timestamp"]),
            )
        }
    }

    open fun loadOpenMovements(inventoryLotId: Long): List<ItemLotTraceOpenMovement> {
        val query = """
            SELECT
                sil.id AS stockInLineId,
                COALESCE(sil.acceptedQty, 0) AS qty,
                COALESCE(wh.code, '') AS warehouseCode,
                COALESCE(si.code, '') AS refCode,
                COALESCE(sil.createdBy, '') AS handledBy,
                COALESCE(sil.created, sil.modified, sil.receiptDate) AS timestamp,
                COALESCE(sil.remarks, '') AS remarks
            FROM stock_in_line sil
            INNER JOIN stock_in si ON sil.stockInId = si.id AND si.deleted = 0
            LEFT JOIN inventory_lot_line ill ON sil.inventoryLotLineId = ill.id AND ill.deleted = 0
            LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0
            WHERE sil.deleted = 0
              AND sil.inventoryLotId = :inventoryLotId
              AND UPPER(COALESCE(sil.type, '')) = 'OPEN'
            ORDER BY timestamp DESC
        """.trimIndent()
        return jdbcDao.queryForList(query, mapOf("inventoryLotId" to inventoryLotId)).map { row ->
            ItemLotTraceOpenMovement(
                stockInLineId = sql.longVal(row["stockInLineId"]) ?: 0L,
                qty = sql.decimal(row["qty"]),
                warehouseCode = sql.str(row["warehouseCode"]),
                refCode = sql.str(row["refCode"]),
                handledBy = sql.str(row["handledBy"]),
                timestamp = sql.formatDateTime(row["timestamp"]),
                remarks = sql.str(row["remarks"]),
            )
        }
    }
}
