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

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

@Component
open class TraceOutboundLoader(
    private val jdbcDao: JdbcDao,
    private val sql: TraceSqlSupport,
    private val deliveryOrderService: DeliveryOrderService,
    private val doReplenishmentService: DoReplenishmentService,
) {
    open fun loadOutboundUsage(inventoryLotId: Long): List<ItemLotTraceOutboundUsage> {
        val materialSql = """
            SELECT
                sol.id AS stockOutLineId,
                COALESCE(sol.qty, 0) AS qty,
                COALESCE(po.code, '') AS pickOrderCode,
                po.id AS pickOrderId,
                COALESCE(po.consoCode, '') AS consoCode,
                COALESCE(jo.code, '') AS jobOrderCode,
                jo.id AS jobOrderId,
                '' AS deliveryOrderCode,
                COALESCE(picker_user.name, jpo_handler_user.name, sol.createdBy, '') AS handler,
                COALESCE(sol.endTime, sol.pickTime, sol.created) AS timestamp,
                'MATERIAL' AS usageType
            FROM stock_out_line sol
            INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0
                AND so.type IN ('job', 'jo')
            INNER JOIN inventory_lot_line ill ON sol.inventoryLotLineId = ill.id AND ill.deleted = 0
            INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.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 job_order jo ON po.joId = jo.id AND jo.deleted = 0
            LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
            LEFT JOIN jo_pick_order jpo ON po.id = jpo.pick_order_id AND pol.itemId = jpo.item_id AND jpo.deleted = 0
            LEFT JOIN user jpo_handler_user ON jpo.handled_by = jpo_handler_user.id AND jpo_handler_user.deleted = 0
            WHERE sol.deleted = 0 AND il.id = :inventoryLotId
        """.trimIndent()

        val fgSql = """
            SELECT
                sol.id AS stockOutLineId,
                COALESCE(sol.qty, 0) AS qty,
                COALESCE(po.code, '') AS pickOrderCode,
                po.id AS pickOrderId,
                COALESCE(po.consoCode, '') AS consoCode,
                '' AS jobOrderCode,
                NULL AS jobOrderId,
                COALESCE(do_tbl.code, '') AS deliveryOrderCode,
                COALESCE(picker_user.name, sol.createdBy, '') AS handler,
                COALESCE(sol.endTime, sol.pickTime, sol.created) AS timestamp,
                'FG_DELIVERY' AS usageType
            FROM stock_out_line sol
            INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0 AND so.type = 'do'
            INNER JOIN inventory_lot_line ill ON sol.inventoryLotLineId = ill.id AND ill.deleted = 0
            INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.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 delivery_order do_tbl ON po.doId = do_tbl.id AND do_tbl.deleted = 0
            LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
            WHERE sol.deleted = 0 AND il.id = :inventoryLotId
        """.trimIndent()

        val args = mapOf("inventoryLotId" to inventoryLotId)
        val material = jdbcDao.queryForList(materialSql, args)
        val fg = jdbcDao.queryForList(fgSql, args)
        return (material + fg).map { row -> mapOutboundUsage(row) }
    }

    open fun mapOutboundUsage(row: Map<String, Any?>): ItemLotTraceOutboundUsage =
        ItemLotTraceOutboundUsage(
            stockOutLineId = sql.longVal(row["stockOutLineId"]) ?: 0L,
            qty = sql.decimal(row["qty"]),
            pickOrderCode = sql.str(row["pickOrderCode"]),
            pickOrderId = sql.longVal(row["pickOrderId"]),
            consoCode = sql.str(row["consoCode"]),
            jobOrderCode = sql.str(row["jobOrderCode"]),
            jobOrderId = sql.longVal(row["jobOrderId"]),
            deliveryOrderCode = sql.str(row["deliveryOrderCode"]),
            handler = sql.str(row["handler"]),
            timestamp = sql.formatDateTime(row["timestamp"]),
            usageType = sql.str(row["usageType"]),
        )

    private fun doDeliverySelectSql(): String = """
            SELECT
                sol.id AS stockOutLineId,
                COALESCE(sol.qty, 0) AS qty,
                COALESCE(po.code, '') AS pickOrderCode,
                po.id AS pickOrderId,
                COALESCE(po.consoCode, '') AS consoCode,
                COALESCE(do_tbl.code, so.deliveryOrderCode, dpor.delivery_order_code, '') AS deliveryOrderCode,
                do_tbl.id AS deliveryOrderId,
                COALESCE(do_tbl.isExtra, 0) AS deliveryOrderIsExtra,
                pol.id AS pickOrderLineId,
                pol.qty AS pickOrderLineQty,
                (
                    SELECT COALESCE(SUM(sol_all.qty), 0)
                    FROM stock_out_line sol_all
                    WHERE sol_all.pickOrderLineId = pol.id
                      AND sol_all.deleted = 0
                ) AS pickOrderLinePickedQty,
                il.itemId AS itemId,
                COALESCE(dop_direct.ticketNo, dop_conso.ticketNo) AS dopTicketNo,
                COALESCE(dpo.ticket_no, dpor.ticket_no, '') AS legacyTicketNo,
                COALESCE(
                    dop_direct.releaseType,
                    dop_conso.releaseType,
                    dpo.release_type,
                    dpor.release_type,
                    ''
                ) AS releaseType,
                COALESCE(dop_direct.id, dop_conso.id) AS deliveryOrderPickOrderId,
                COALESCE(dop_direct.relationshipId, dop_conso.relationshipId) AS relationshipId,
                COALESCE(
                    dop_direct.deliveryNoteCode,
                    dop_conso.deliveryNoteCode,
                    dpor.deliveryNoteCode,
                    ''
                ) AS deliveryNoteCode,
                dpor.id AS doPickOrderRecordId,
                COALESCE(po.consoCode, '') AS pickOrderConsoCode,
                COALESCE(so.consoPickOrderCode, '') AS stockOutConsoCode,
                COALESCE(shop_tbl.code, dpo.ShopCode, dpor.ShopCode, '') AS shopCode,
                COALESCE(shop_tbl.name, dpo.ShopName, dpor.ShopName, '') AS shopName,
                COALESCE(picker_user.name, sol.createdBy, '') AS handler,
                COALESCE(sol.endTime, sol.pickTime, sol.created) AS timestamp
            FROM stock_out_line sol
            INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0
            INNER JOIN inventory_lot_line ill ON sol.inventoryLotLineId = ill.id AND ill.deleted = 0
            INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.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 delivery_order_pick_order dop_direct
                ON po.deliveryOrderPickOrderId = dop_direct.id AND dop_direct.deleted = 0
            LEFT JOIN delivery_order_pick_order dop_conso
                ON po.consoCode = dop_conso.ticketNo AND dop_conso.deleted = 0
            LEFT JOIN shop shop_tbl ON do_tbl.shopId = shop_tbl.id AND shop_tbl.deleted = 0
            LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
    """.trimIndent()

    /** FP-MTMS Version Checklist | Functions Ref. No. 24 | v1.0.0 | 2026-07-20 */
    open fun loadDoDeliveries(inventoryLotId: Long): List<ItemLotTraceDoDelivery> {
        val query = """
            ${doDeliverySelectSql()}
            WHERE sol.deleted = 0 AND il.id = :inventoryLotId
              AND (
                  UPPER(COALESCE(so.type, '')) = 'DO'
                  OR po.doId IS NOT NULL
                  OR dpo.id IS NOT NULL
                  OR dpor.id IS NOT NULL
              )
            ORDER BY timestamp DESC
        """.trimIndent()
        return mapDoDeliveryRows(
            jdbcDao.queryForList(query, mapOf("inventoryLotId" to inventoryLotId)),
        )
    }

    /**
     * FP-MTMS Version Checklist | Functions Ref. No. 24 | v1.0.0 | 2026-07-20
     * Fallback when lot-scoped query misses DO picks still present in movements.
     */
    open fun loadDoDeliveriesByPickOrderCodes(pickOrderCodes: List<String>): List<ItemLotTraceDoDelivery> {
        val codes = pickOrderCodes.map { it.trim() }.filter { it.isNotEmpty() }.distinct()
        if (codes.isEmpty()) return emptyList()
        val query = """
            ${doDeliverySelectSql()}
            WHERE sol.deleted = 0
              AND po.code IN (:pickOrderCodes)
              AND (
                  UPPER(COALESCE(so.type, '')) = 'DO'
                  OR po.doId IS NOT NULL
                  OR dpo.id IS NOT NULL
                  OR dpor.id IS NOT NULL
              )
            ORDER BY timestamp DESC
        """.trimIndent()
        return mapDoDeliveryRows(jdbcDao.queryForList(query, mapOf("pickOrderCodes" to codes)))
    }

    private fun mapDoDeliveryRows(rows: List<Map<String, Any?>>): List<ItemLotTraceDoDelivery> {
        val deliveryOrderIds = rows.mapNotNull { sql.longVal(it["deliveryOrderId"]) }.distinct()
        val replenishIndex = doReplenishmentService.buildReplenishPdfIndex(deliveryOrderIds)
        return rows.map { row -> mapDoDelivery(row, replenishIndex) }
    }

    /** FP-MTMS Version Checklist | Functions Ref. No. 24 | v1.0.0 | 2026-07-20 */
    private fun mapDoDelivery(
        row: Map<String, Any?>,
        replenishIndex: DoReplenishmentService.ReplenishPdfIndex,
    ): ItemLotTraceDoDelivery {
        val deliveryOrderId = sql.longVal(row["deliveryOrderId"])
        val pickOrderLineId = sql.longVal(row["pickOrderLineId"])
        val itemId = sql.longVal(row["itemId"])
        val ticketNo = resolveOutboundTicketNo(
            dopTicketNo = sql.str(row["dopTicketNo"]).takeIf { it.isNotBlank() },
            legacyTicketNo = sql.str(row["legacyTicketNo"]).takeIf { it.isNotBlank() },
            pickOrderConsoCode = sql.str(row["pickOrderConsoCode"]).takeIf { it.isNotBlank() },
            stockOutConsoCode = sql.str(row["stockOutConsoCode"]).takeIf { it.isNotBlank() },
        )
        val releaseType = sql.str(row["releaseType"]).takeIf { it.isNotBlank() }
        val deliveryOrderIsExtra = truthy(row["deliveryOrderIsExtra"])
        val pickOrderConsoCode = sql.str(row["pickOrderConsoCode"]).takeIf { it.isNotBlank() }
        val dopId = sql.longVal(row["deliveryOrderPickOrderId"])
        val relationshipId = sql.longVal(row["relationshipId"])
        val isExtra = resolveDoOutboundIsExtra(
            ticketNo = ticketNo,
            pickOrderConsoCode = pickOrderConsoCode,
            releaseType = releaseType,
            deliveryOrderIsExtra = deliveryOrderIsExtra,
            dopId = dopId,
            relationshipId = relationshipId,
        )
        val isReplenish = deliveryOrderId != null &&
            replenishIndex.matches(deliveryOrderId, itemId, pickOrderLineId)
        // Multi-lot picks: compare POL.qty to SUM(SOL.qty) for that line, not a single SOL.
        val pickOrderLineQty = sql.decimalOrNull(row["pickOrderLineQty"])
        val pickOrderLinePickedQty = sql.decimalOrNull(row["pickOrderLinePickedQty"])
        val qtyChanged =
            pickOrderLineId != null &&
                pickOrderLineQty != null &&
                pickOrderLinePickedQty != null &&
                pickOrderLineQty.compareTo(pickOrderLinePickedQty) != 0
        return ItemLotTraceDoDelivery(
            stockOutLineId = sql.longVal(row["stockOutLineId"]) ?: 0L,
            qty = sql.decimal(row["qty"]),
            pickOrderCode = sql.str(row["pickOrderCode"]),
            pickOrderId = sql.longVal(row["pickOrderId"]),
            consoCode = sql.str(row["consoCode"]),
            ticketNo = ticketNo.orEmpty(),
            deliveryOrderCode = sql.str(row["deliveryOrderCode"]),
            deliveryOrderId = deliveryOrderId,
            shopCode = sql.str(row["shopCode"]),
            shopName = sql.str(row["shopName"]),
            handler = sql.str(row["handler"]),
            timestamp = sql.formatDateTime(row["timestamp"]),
            releaseType = releaseType,
            deliveryOrderPickOrderId = dopId,
            relationshipId = relationshipId,
            deliveryNoteCode = sql.str(row["deliveryNoteCode"]),
            doPickOrderRecordId = sql.longVal(row["doPickOrderRecordId"]),
            isExtra = isExtra,
            isReplenish = isReplenish,
            qtyChanged = qtyChanged,
        )
    }

    private fun truthy(v: Any?): Boolean = when (v) {
        null -> false
        is Boolean -> v
        is Number -> v.toInt() != 0
        else -> v.toString().equals("true", ignoreCase = true) || v.toString() == "1"
    }

    companion object {
        /**
         * Ticket priority: workbench [delivery_order_pick_order] → legacy GoodPick ticket
         * ([do_pick_order] / [do_pick_order_record]) → TI-* conso → other conso.
         */
        internal fun resolveOutboundTicketNo(
            dopTicketNo: String?,
            legacyTicketNo: String? = null,
            pickOrderConsoCode: String?,
            stockOutConsoCode: String?,
        ): String? {
            dopTicketNo?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
            legacyTicketNo?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
            return sequenceOf(pickOrderConsoCode, stockOutConsoCode)
                .mapNotNull { it?.trim()?.takeIf { s -> s.isNotEmpty() } }
                .firstOrNull { it.startsWith("TI-", ignoreCase = true) }
                ?: pickOrderConsoCode?.trim()?.takeIf { it.isNotEmpty() }
                ?: stockOutConsoCode?.trim()?.takeIf { it.isNotEmpty() }
        }

        internal fun isExtraReleaseType(releaseType: String?): Boolean =
            when (releaseType?.trim()?.lowercase()) {
                "isextra", "isextrabatch", "isextrasingle" -> true
                else -> false
            }

        internal fun parseTicketTypeLetter(ticketNo: String?): String? {
            val parts = ticketNo?.trim()?.split("-").orEmpty()
            return parts.getOrNull(1)
        }

        internal fun isTiETicket(ticketNo: String?): Boolean =
            parseTicketTypeLetter(ticketNo)?.equals("E", ignoreCase = true) == true ||
                ticketNo?.trim()?.startsWith("TI-E-", ignoreCase = true) == true

        internal fun isTiMTicket(ticketNo: String?): Boolean =
            ticketNo?.trim()?.startsWith("TI-M-", ignoreCase = true) == true

        /**
         * Aligns with [DeliveryOrderService.isExtraDeliveryTicket]:
         * - TI-E / conso TI-E- → extra
         * - TI-M → per DO [isExtra], conso TI-E-, or retired TI-E lineage via [relationshipId]
         * - Other tickets → releaseType / DO flag
         */
        internal fun resolveDoOutboundIsExtra(
            ticketNo: String?,
            pickOrderConsoCode: String?,
            releaseType: String?,
            deliveryOrderIsExtra: Boolean,
            dopId: Long?,
            relationshipId: Long?,
        ): Boolean {
            val resolvedTicket = ticketNo?.trim()?.takeIf { it.isNotEmpty() }
            val conso = pickOrderConsoCode?.trim()?.takeIf { it.isNotEmpty() }
            val consoIsTiE = isTiETicket(conso)

            if (isTiMTicket(resolvedTicket)) {
                if (deliveryOrderIsExtra) return true
                if (consoIsTiE) return true
                if (dopId != null && relationshipId != null && relationshipId != dopId) return true
                return false
            }

            if (isTiETicket(resolvedTicket) || consoIsTiE) return true

            if (isExtraReleaseType(releaseType)) return true
            return deliveryOrderIsExtra
        }
    }

    open fun loadReplenishmentEvents(inventoryLotId: Long): List<ItemLotTraceReplenishmentEvent> {
        val query = """
            SELECT
                dr.id AS replenishmentId,
                dr.code AS replenishmentCode,
                COALESCE(dr.sourceDoCode, '') AS sourceDoCode,
                dr.sourceDoId AS sourceDoId,
                COALESCE(dr.shopCode, '') AS shopCode,
                COALESCE(dr.shopName, '') AS shopName,
                COALESCE(dr.itemNo, '') AS itemNo,
                COALESCE(dr.itemName, '') AS itemName,
                dr.itemId AS itemId,
                COALESCE(dr.reason, '') AS reason,
                COALESCE(dr.replenishQty, 0) AS replenishQty,
                dr.pickOrderLineId AS pickOrderLineId,
                dr.created AS timestamp,
                COALESCE(dr.createdBy, '') AS handler,
                sol.id AS stockOutLineId
            FROM stock_out_line sol
            INNER JOIN inventory_lot_line ill ON sol.inventoryLotLineId = ill.id AND ill.deleted = 0
            INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.deleted = 0
            INNER JOIN pick_order_line pol ON sol.pickOrderLineId = pol.id AND pol.deleted = 0
            INNER JOIN do_replenishment dr ON dr.pickOrderLineId = pol.id AND dr.deleted = 0
                AND dr.itemId = il.itemId
            WHERE sol.deleted = 0 AND il.id = :inventoryLotId
            ORDER BY dr.created DESC
        """.trimIndent()
        return jdbcDao.queryForList(query, mapOf("inventoryLotId" to inventoryLotId))
            .map { row -> mapReplenishmentEvent(row) }
    }

    private fun mapReplenishmentEvent(row: Map<String, Any?>): ItemLotTraceReplenishmentEvent =
        ItemLotTraceReplenishmentEvent(
            replenishmentId = sql.longVal(row["replenishmentId"]) ?: 0L,
            replenishmentCode = sql.str(row["replenishmentCode"]),
            sourceDoCode = sql.str(row["sourceDoCode"]),
            sourceDoId = sql.longVal(row["sourceDoId"]),
            shopCode = sql.str(row["shopCode"]),
            shopName = sql.str(row["shopName"]),
            itemNo = sql.str(row["itemNo"]),
            itemName = sql.str(row["itemName"]),
            reason = sql.str(row["reason"]),
            replenishQty = sql.decimal(row["replenishQty"]),
            handler = sql.str(row["handler"]),
            timestamp = sql.formatDateTime(row["timestamp"]),
            pickOrderLineId = sql.longVal(row["pickOrderLineId"]),
            stockOutLineId = sql.longVal(row["stockOutLineId"]),
        )

    open fun loadReturnEvents(inventoryLotId: Long, lotLineIds: List<Long>): List<ItemLotTraceReturnEvent> {
        if (lotLineIds.isEmpty()) return emptyList()
        val query = """
            SELECT
                sol.id AS stockOutLineId,
                COALESCE(sol.qty, 0) AS qty,
                COALESCE(so.consoPickOrderCode, so.deliveryOrderCode, '') AS refCode,
                COALESCE(UPPER(so.type), UPPER(sol.type), 'RETURN') AS movementType,
                COALESCE(wh.code, '') AS warehouseCode,
                COALESCE(picker_user.name, sol.createdBy, '') AS handler,
                COALESCE(sol.endTime, sol.pickTime, sol.created) AS timestamp,
                COALESCE(so.remarks, '') AS remarks
            FROM stock_out_line sol
            INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0
            INNER 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
            LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
            WHERE sol.deleted = 0
              AND ill.inventoryLotId = :inventoryLotId
              AND sol.inventoryLotLineId IN (:lotLineIds)
              AND (
                  UPPER(COALESCE(so.type, '')) IN ('RETURN', 'RTN', 'SUPPLIER_RETURN')
                  OR UPPER(COALESCE(sol.type, '')) IN ('RETURN', 'RTN')
                  OR COALESCE(so.remarks, '') LIKE '%退貨%'
                  OR COALESCE(so.remarks, '') LIKE '%退货%'
                  OR COALESCE(so.remarks, '') LIKE '%return%'
              )
            ORDER BY timestamp DESC
        """.trimIndent()
        return jdbcDao.queryForList(
            query,
            mapOf("inventoryLotId" to inventoryLotId, "lotLineIds" to lotLineIds),
        ).map { row ->
            ItemLotTraceReturnEvent(
                stockOutLineId = sql.longVal(row["stockOutLineId"]) ?: 0L,
                qty = sql.decimal(row["qty"]),
                refCode = sql.str(row["refCode"]),
                movementType = sql.str(row["movementType"]),
                warehouseCode = sql.str(row["warehouseCode"]),
                handler = sql.str(row["handler"]),
                timestamp = sql.formatDateTime(row["timestamp"]),
                remarks = sql.str(row["remarks"]),
            )
        }
    }
}
