package com.ffii.fpsms.modules.deliveryOrder.service

import com.ffii.core.utils.PdfUtils
import org.springframework.context.annotation.Lazy
import com.ffii.core.utils.ZebraPrinterUtil
import com.ffii.fpsms.m18.entity.M18DataLogRepository
import com.ffii.fpsms.m18.service.M18DataLogService
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrder
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderResponse
import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderStatusRequest
import com.ffii.fpsms.modules.master.service.CurrencyService
import com.ffii.fpsms.modules.master.service.ShopService
import com.ffii.fpsms.modules.user.entity.UserRepository
import com.ffii.fpsms.modules.user.service.UserService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.deliveryOrder.web.models.DoDetailResponse
import com.ffii.fpsms.modules.deliveryOrder.web.models.DoDetailLineResponse
import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderRequest
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderLineRequest
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService
import java.time.LocalDate
import java.math.BigDecimal
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import java.time.LocalDateTime
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder
import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus
import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDeliveryNoteRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDeliveryNoteRequest
import com.ffii.fpsms.modules.master.service.PrinterService
import java.time.format.DateTimeFormatter
import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository
import java.time.LocalTime

import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOutRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.stock.entity.StockOutLine
import com.ffii.fpsms.modules.stock.web.model.StockOutStatus
import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPolRequest
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import net.sf.jasperreports.engine.JasperCompileManager
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.JasperPrint
import org.springframework.core.io.ClassPathResource
import java.io.File
import java.io.FileNotFoundException
import com.ffii.core.support.JdbcDao;
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.service.InventoryLotService
import net.sf.jasperreports.engine.JasperPrintManager
import net.sf.jasperreports.engine.JRPrintPage
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService  // ✅ 添加这行
import com.ffii.fpsms.modules.deliveryOrder.web.models.*
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssue // ✅ 添加
import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository // ✅ 添加
import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory // ✅ 添加
import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus
@Service
open class DeliveryOrderService(
    private val deliveryOrderRepository: DeliveryOrderRepository,
    private val shopService: ShopService,
    private val currencyService: CurrencyService,
    private val m18DataLogService: M18DataLogService,
    private val m18DataLogRepository: M18DataLogRepository,
    private val userService: UserService,
    private val userRepository: UserRepository,
    private val pickOrderService: PickOrderService,
    @Lazy private val doPickOrderService: DoPickOrderService,
    private val truckRepository: TruckRepository,
    private val pickOrderRepository: PickOrderRepository,
    private val suggestedPickLotService: SuggestedPickLotService,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val stockOutRepository: StockOutRepository,
    private val stockOutLineRepository: StockOutLIneRepository,
    private val pickOrderLineRepository: PickOrderLineRepository,
    private val printerService: PrinterService,
    private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
    private val purchaseOrderRepository: PurchaseOrderRepository,
    private val inventoryLotService: InventoryLotService,
    private val suggestedPickLotRepository: SuggestPickLotRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val jdbcDao: JdbcDao,
    private val pickExecutionIssueRepository: PickExecutionIssueRepository,
) {

    open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? {
        return deliveryOrderRepository.findTopByM18DataLogIdAndDeletedIsFalseOrderByModifiedDesc(m18DataLogId)
    }

    open fun getDoList(): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findDeliveryOrderInfoByDeletedIsFalse();
    }

    /*
    open fun getDetailedDo(id: Long) : DeliveryOrder? {
        return deliveryOrderRepository.findByIdAndDeletedIsFalse(id);
    }
    */
    open fun getDetailedDo(id: Long): DoDetailResponse? {
        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(id) ?: return null

        return DoDetailResponse(
            id = deliveryOrder.id!!,
            code = deliveryOrder.code ?: "",
            supplierCode = deliveryOrder.supplier?.code,
            shopCode = deliveryOrder.shop?.code,
            shopName = deliveryOrder.shop?.name,
            currencyCode = deliveryOrder.currency?.code,
            orderDate = deliveryOrder.orderDate,
            estimatedArrivalDate = deliveryOrder.estimatedArrivalDate,
            completeDate = deliveryOrder.completeDate,
            status = deliveryOrder.status?.value,
            deliveryOrderLines = deliveryOrder.deliveryOrderLines.map { line ->
                DoDetailLineResponse(
                    id = line.id!!,
                    itemNo = line.itemNo,
                    qty = line.qty,
                    price = line.price,
                    status = line.status?.value,
                    itemName = line.item?.name,
                    uomCode = line.uom?.code,
                    uom = line.uom?.udfudesc,
                    shortUom = line.uom?.udfShortDesc
                )
            }
        )
    }

    open fun searchByCode(code: String?): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndDeletedIsFalse(code);
    }

    open fun searchByShopName(shopName: String): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByShopNameContainsAndDeletedIsFalse(shopName);
    }

    open fun searchByStatus(status: DeliveryOrderStatus): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByStatusAndDeletedIsFalse(status);
    }

    open fun searchBetweenOrderDate(start: LocalDateTime?, end: LocalDateTime?): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findByOrderDateBetweenAndDeletedIsFalse(start, end);
    }


    open fun getFullList(): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllBy();
    }

    open fun searchAll(
        code: String?,
        shopName: String?,
        status: DeliveryOrderStatus?,
        orderStartDate: LocalDateTime?,
        orderEndDate: LocalDateTime?,
        estArrStartDate: LocalDateTime?,
        estArrEndDate: LocalDateTime?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndOrderDateBetweenAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
            code,
            shopName,
            status,
            orderStartDate,
            orderEndDate,
            estArrStartDate,
            estArrEndDate
        );
    }

    open fun searchWithoutStatus(
        code: String?,
        shopName: String?,
        orderStartDate: LocalDateTime?,
        orderEndDate: LocalDateTime?,
        estArrStartDate: LocalDateTime?,
        estArrEndDate: LocalDateTime?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndOrderDateBetweenAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
            code,
            shopName,
            orderStartDate,
            orderEndDate,
            estArrStartDate,
            estArrEndDate
        );
    }

    open fun searchWithoutEstArrDate(
        code: String?,
        shopName: String?,
        status: DeliveryOrderStatus?,
        orderStartDate: LocalDateTime?,
        orderEndDate: LocalDateTime?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndOrderDateBetweenAndDeletedIsFalse(
            code,
            shopName,
            status,
            orderStartDate,
            orderEndDate
        );
    }

    open fun searchWithoutEstArrDateAndStatus(
        code: String?,
        shopName: String?,
        orderStartDate: LocalDateTime?,
        orderEndDate: LocalDateTime?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndOrderDateBetweenAndDeletedIsFalse(
            code,
            shopName,
            orderStartDate,
            orderEndDate
        );
    }

    open fun searchWithoutOrderDate(
        code: String?,
        shopName: String?,
        status: DeliveryOrderStatus?,
        estArrStartDate: LocalDateTime?,
        estArrEndDate: LocalDateTime?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
            code,
            shopName,
            status,
            estArrStartDate,
            estArrEndDate
        );
    }

    open fun searchWithoutOrderDateAndStatus(
        code: String?,
        shopName: String?,
        estArrStartDate: LocalDateTime?,
        estArrEndDate: LocalDateTime?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndEstimatedArrivalDateBetweenAndDeletedIsFalse(
            code,
            shopName,
            estArrStartDate,
            estArrEndDate
        );
    }

    open fun searchWithoutDate(
        code: String?,
        shopName: String?,
        status: DeliveryOrderStatus?
    ): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndStatusAndDeletedIsFalse(
            code,
            shopName,
            status
        );
    }

    open fun searchCodeAndShopName(code: String?, shopName: String?): List<DeliveryOrderInfo> {
        return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndDeletedIsFalse(code, shopName);
    }

    open fun getWarehouseOrderByItemId(itemId: Long): Int? {
        val inventoryLots = inventoryLotService.findByItemId(itemId)
        if (inventoryLots.isNotEmpty()) {
            val inventoryLotId = inventoryLots.first().id?.toInt()
            return inventoryLotId?.let { lotId ->
                // 查询 warehouse 的 order 字段
                val sql = """
                    SELECT w.`order` as warehouseOrder
                    FROM fpsmsdb.inventory_lot_line ill
                    JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
                    WHERE ill.inventoryLotId = :lotId
                    AND ill.deleted = false
                    ORDER BY w.`order` ASC
                    LIMIT 1
                """.trimIndent()
                val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId))
                if (result.isNotEmpty()) {
                    (result.first()["warehouseOrder"] as Number?)?.toInt()
                } else null
            }
        }
        return null
    }

    // ✅ 新增方法2：获取 warehouse 的 code 字段（用于显示路由）
    open fun getWarehouseCodeByItemId(itemId: Long): String? {
        val inventoryLots = inventoryLotService.findByItemId(itemId)
        if (inventoryLots.isNotEmpty()) {
            val inventoryLotId = inventoryLots.first().id?.toInt()
            return inventoryLotId?.let { lotId ->
                val sql = """
                    SELECT w.code as warehouseCode
                    FROM fpsmsdb.inventory_lot_line ill
                    JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
                    WHERE ill.inventoryLotId = :lotId
                    AND ill.deleted = false
                    ORDER BY w.`order` ASC
                    LIMIT 1
                """.trimIndent()
                val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId))
                if (result.isNotEmpty()) {
                    result.first()["warehouseCode"] as String?
                } else null
            }
        }
        return null
    }

    open fun updateDeliveryOrderStatus(request: SaveDeliveryOrderStatusRequest): SaveDeliveryOrderResponse {
        val deliveryOrder = checkNotNull(
            request.id?.let { deliveryOrderRepository.findById(it).getOrNull() }
        ) { "ID must be set" }

        val status = checkNotNull(
            request.status?.let { status -> DeliveryOrderStatus.entries.find { it.value == status } }
        ) { "Status must be set" }

        deliveryOrder.apply {
            this.status = status
        }

        val savedDeliveryOrder = deliveryOrderRepository.save(deliveryOrder).let {
            SaveDeliveryOrderResponse(
                id = it.id,
                code = it.code,
                supplierCode = it.supplier?.code,
                shopCode = it.shop?.code,
                currencyCode = it.currency?.code,
                orderDate = it.orderDate,
                estimatedArrivalDate = it.estimatedArrivalDate,
                completeDate = it.completeDate,
                status = it.status?.value
            )
        }

        return savedDeliveryOrder
    }

    @Throws(IOException::class)
    @Transactional
    open fun saveDeliveryOrder(request: SaveDeliveryOrderRequest): SaveDeliveryOrderResponse {
        val deliveryOrder =
            request.id?.let { deliveryOrderRepository.findById(it).getOrDefault(DeliveryOrder()) } ?: DeliveryOrder()
        val supplier = request.m18SupplierId?.let { shopService.findVendorByM18Id(it) }
            ?: request.supplierId?.let { shopService.findById(it) }
        val shop = request.m18ShopId?.let { shopService.findShopByM18Id(it) }
            ?: request.shopId?.let { shopService.findById(it) }
        val currency = request.m18CurrencyId?.let { currencyService.findByM18Id(it) }
            ?: request.currencyId?.let { currencyService.findById(it) }
        val status = request.status?.let { status -> DeliveryOrderStatus.entries.find { it.value == status } }
        val m18DataLog = request.m18DataLogId?.let { m18DataLogRepository.findById(it).getOrNull() }
        val handler = request.handlerId?.let { userRepository.findById(it).getOrNull() }

        //Need check duplicate?
        deliveryOrder.apply {
            code = request.code
            this.supplier = supplier
            this.shop = shop
            this.currency = currency
            orderDate = request.orderDate
            estimatedArrivalDate = request.estimatedArrivalDate
            completeDate = request.completeDate
            this.status = status
            this.m18DataLog = m18DataLog
            this.handler = handler
            m18BeId = request.m18BeId
        }

        val savedDeliveryOrder = deliveryOrderRepository.saveAndFlush(deliveryOrder).let {
            SaveDeliveryOrderResponse(
                id = it.id,
                code = it.code,
                supplierCode = it.supplier?.code,
                shopCode = it.shop?.code,
                currencyCode = it.currency?.code,
                orderDate = it.orderDate,
                estimatedArrivalDate = it.estimatedArrivalDate,
                completeDate = it.completeDate,
                status = it.status?.value
            )
        }

        return savedDeliveryOrder
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun releaseDeliveryOrder(request: ReleaseDoRequest): MessageResponse {
        println("�� DEBUG: Starting releaseDeliveryOrder for DO ID: ${request.id}, User ID: ${request.userId}")

        val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
            ?: throw NoSuchElementException("Delivery Order not found")

        println("�� DEBUG: Found delivery order - ID: ${deliveryOrder.id}, Shop: ${deliveryOrder.shop?.code}, Status: ${deliveryOrder.status}")

        deliveryOrder.apply {
            status = DeliveryOrderStatus.PENDING
        }
        deliveryOrderRepository.save(deliveryOrder)

    val pols = deliveryOrder.deliveryOrderLines.map {
        SavePickOrderLineRequest(
            itemId = it.item?.id,
            qty = it.qty ?: BigDecimal.ZERO,
            uomId = it.uom?.id,
        )
    }
    val po = SavePickOrderRequest(
        doId = deliveryOrder.id,
        type = PickOrderType.DELIVERY_ORDER,
        targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(),
        pickOrderLine = pols
    )

        val createdPickOrder = pickOrderService.create(po)
        println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}")

        val consoCode = pickOrderService.assignConsoCode()
        val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

        if (pickOrderEntity != null) {
            pickOrderEntity.consoCode = consoCode
            pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
            pickOrderRepository.saveAndFlush(pickOrderEntity)
            println("�� DEBUG: Assigned consoCode $consoCode to pick order ${createdPickOrder.id}")

            // ✅ Debug: Check pick order lines
            println("�� DEBUG: Pick order has ${pickOrderEntity.pickOrderLines?.size ?: 0} pick order lines")
            pickOrderEntity.pickOrderLines?.forEach { line ->
                println("🔍 DEBUG: Pick order line - Item ID: ${line.item?.id}, Qty: ${line.qty}")
            }


            val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
            val suggestions = suggestedPickLotService.suggestionForPickOrderLines(
                SuggestedPickLotForPolRequest(pickOrderLines = lines)
            )

            val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
            println("🔍 DEBUG: Saved ${saveSuggestedPickLots.size} suggested pick lots")
            val insufficientCount = suggestions.suggestedList.count { it.suggestedLotLine == null }
            if (insufficientCount > 0) {
                println("⚠️ WARNING: $insufficientCount items have insufficient stock (issues auto-created)")
            }

            // ✅ Hold inventory quantities
            val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
                saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
            )

            saveSuggestedPickLots.forEach { lot ->
                if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) {
                    val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
                    if (lineIndex >= 0) {
                        inventoryLotLines[lineIndex].holdQty =
                            (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
                    }
                }
            }
            inventoryLotLineRepository.saveAll(inventoryLotLines)

            // ✅ Create stock out record and pre-create stock out lines
            val stockOut = StockOut().apply {
                this.type = "job"
                this.consoPickOrderCode = consoCode
                this.status = StockOutStatus.PENDING.status
                this.handler = request.userId
            }
            val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

            // ✅ Pre-create stock out lines for suggested lots
            saveSuggestedPickLots.forEach { lot ->
                val polId = lot.pickOrderLine?.id
                val illId = lot.suggestedLotLine?.id
                if (polId != null && illId != null) {
                    val existingLines =
                        stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
                    if (existingLines.isEmpty()) {
                        val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null)
                        val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null)

                        if (pickOrderLine != null && inventoryLotLine != null) {
                            val line = StockOutLine().apply {
                                this.stockOut = savedStockOut
                                this.pickOrderLine = pickOrderLine
                                this.inventoryLotLine = inventoryLotLine
                                this.item = pickOrderLine.item
                                this.status = StockOutLineStatus.PENDING.status
                                this.qty = 0.0
                            }
                            stockOutLineRepository.save(line)
                        }
                    }
                }
            }
        }

        // ✅ CREATE do_pick_order_record entries
        // 第 471-555 行附近 - 修复创建逻辑

// ✅ CREATE do_pick_order_record entries
        val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
        val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))

        println("🔍 DEBUG: Target date: $targetDate, Date prefix: $datePrefix")
        val truck = deliveryOrder.shop?.id?.let { shopId ->
            println("🔍 DEBUG: Looking for truck with shop ID: $shopId")
            val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
            println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId")

            // ✅ 移除提前返回，总是分析 items 分布
            // 分析 DO order lines 中的 items 分布
            // ✅ 分析 items 来确定 storeId
            val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct()
            val inventoryQuery = """
    SELECT 
        w.store_id as floor, 
        COUNT(DISTINCT il.itemId) as item_count
    FROM inventory_lot il
    INNER JOIN inventory i ON i.itemId = il.itemId AND i.deleted = 0 AND i.onHandQty > 0
    INNER JOIN inventory_lot_line ill ON ill.inventoryLotId = il.id AND ill.deleted = 0
    INNER JOIN warehouse w ON w.id = ill.warehouseId AND w.deleted = 0 AND w.store_id IN ('2F', '4F')
    WHERE il.itemId IN (${itemIds.joinToString(",")}) AND il.deleted = 0
    GROUP BY w.store_id
""".trimIndent()

            val inventoryResults = jdbcDao.queryForList(inventoryQuery)
            val floorItemCount = mutableMapOf<String, Int>()
            inventoryResults.forEach { row ->
                floorItemCount[row["floor"] as? String ?: "Other"] = (row["item_count"] as? Number)?.toInt() ?: 0
            }

            println("🔍 DEBUG: Floor item count distribution: $floorItemCount")
            println("🔍 DEBUG: Total items: ${itemIds.size}, Items on 4F: ${floorItemCount["4F"] ?: 0}")

// ✅ 新逻辑：只有所有 items 都在 4F，才算 4F，否则算 2F
            val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) {
                "4F"  // 所有 items 都在 4F
            } else {
                "2F"  // 只要有任何 item 不在 4F，就算 2F
            }

            println("🔍 DEBUG: Preferred floor: $preferredFloor (All items on 4F: ${preferredFloor == "4F"})")

// ✅ 查找 truck
            val truck = deliveryOrder.shop?.id?.let { shopId ->
                println("🔍 DEBUG: Looking for truck with shop ID: $shopId")
                val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
                println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId")

                val preferredStoreId = when (preferredFloor) {
                    "2F" -> 2
                    "4F" -> 4
                    else -> 2
                }

                // 优先选择匹配的 truck
                val selectedTruck = if (trucks.size > 1) {
                    trucks.find { it.storeId == preferredStoreId }
                        ?: trucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
                } else {
                    trucks.firstOrNull()
                }

                println("🔍 DEBUG: Selected truck: ID=${selectedTruck?.id}, StoreId=${selectedTruck?.storeId}")
                selectedTruck
            }

// ✅ 检查 truck 和 preferredFloor 是否匹配
            val truckStoreId = truck?.storeId
            val expectedStoreId = when (preferredFloor) {
                "2F" -> 2
                "4F" -> 4
                else -> 2
            }

            if (truck == null || truckStoreId != expectedStoreId) {
                val errorMsg =
                    "Items preferredFloor ($preferredFloor) does not match available truck (Truck Store: $truckStoreId). Skipping DO ${deliveryOrder.id}."
                println("⚠️ $errorMsg")
                throw IllegalStateException(errorMsg)  // 或返回错误响应
            }

            println("✅ DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor")

    // ✅ storeId 基于 items 的 preferredFloor
                val storeId = "$preferredFloor/F"
                val loadingSequence = truck.loadingSequence ?: 999

                println("🔍 DEBUG: Creating DoPickOrder - Floor: $preferredFloor, Store: $storeId, Truck: ${truck.id}")

                val doPickOrder = DoPickOrder(
                    storeId = storeId,
                    ticketNo = "TEMP-${System.currentTimeMillis()}",
                    ticketStatus = DoPickOrderStatus.pending,
                    truckId = truck.id,
                    doOrderId = deliveryOrder.id,
                    pickOrderId = createdPickOrder.id,
                    truckDepartureTime = truck.departureTime,
                    shopId = deliveryOrder.shop?.id,
                    handledBy = null,
                    pickOrderCode = createdPickOrder.code,
                    deliveryOrderCode = deliveryOrder.code,
                    loadingSequence = loadingSequence,
                    ticketReleaseTime = null,
                    truckLanceCode = truck.truckLanceCode,
                    shopCode = deliveryOrder.shop?.code,
                    shopName = deliveryOrder.shop?.name,
                    requiredDeliveryDate = targetDate
                )

                val savedDoPickOrder = doPickOrderService.save(doPickOrder)
                println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}")
                truck
            }
            return MessageResponse(
                id = deliveryOrder.id,
                code = deliveryOrder.code,
                name = deliveryOrder.shop?.name,
                type = null,
                message = null,
                errorPosition = null,
                entity = mapOf("status" to deliveryOrder.status?.value)
            )
        }

        open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String {
            try {
                val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId)
                val pickOrderLineIds = pickOrderLines.mapNotNull { it.id }
                val suggestedPickLots = suggestedPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds)

                val lotNumbers = suggestedPickLots
                    .filter { it.pickOrderLine?.item?.id == itemId }
                    .mapNotNull { it.suggestedLotLine?.inventoryLot?.lotNo }
                    .distinct()

                return lotNumbers.joinToString(", ")
            } catch (e: Exception) {
                println("Error getting lot numbers for item $itemId in pick order $pickOrderId: ${e.message}")
                return ""
            }
        }


        //Delivery Note
        @Throws(IOException::class)
        @Transactional
        open fun exportDeliveryNote(request: ExportDeliveryNoteRequest): Map<String, Any> {
            val DELIVERYNOTE_PDF = "DeliveryNote/DeliveryNotePDF.jrxml"
            val resource = ClassPathResource(DELIVERYNOTE_PDF)
            if (!resource.exists()) {
                throw FileNotFoundException("Report file not fount: $DELIVERYNOTE_PDF")
            }
            val inputStream = resource.inputStream
            val deliveryNote = JasperCompileManager.compileReport(inputStream)
            val deliveryNoteInfo =
                deliveryOrderRepository.findDeliveryOrderInfoById(request.deliveryOrderIds).toMutableList()

            val fields = mutableListOf<MutableMap<String, Any>>()
            val params = mutableMapOf<String, Any>()

            val deliveryOrderEntity = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.deliveryOrderIds)
            val selectedTruckNo = deliveryOrderEntity?.shop?.id?.let { shopId ->
                val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
                trucks.firstOrNull()?.truckLanceCode
            } ?: ""

            val selectedPickOrder = pickOrderRepository.findById(request.pickOrderIds).orElse(null)


            for (info in deliveryNoteInfo) {
                val sortedLines = info.deliveryOrderLines.sortedBy { line ->
                    line.itemId?.let { itemId ->
                        getWarehouseOrderByItemId(itemId)  // ✅ 改用 warehouse order
                    } ?: Int.MAX_VALUE
                }

                sortedLines.forEachIndexed { index, line ->

                    val field = mutableMapOf<String, Any>()

                    field["sequenceNumber"] = (index + 1).toString()
                    field["itemNo"] = line.itemNo
                    field["itemName"] = line.itemName ?: ""
                    field["uom"] = line.uom ?: ""
                    field["qty"] = line.qty.toString()
                    field["shortName"] = line.uomShortDesc ?: ""

                    val route = line.itemId?.let { itemId ->
                        getWarehouseCodeByItemId(itemId)  // ✅ 使用新方法
                    } ?: ""
                    field["route"] = route

                    val lotNo = line.itemId?.let { itemId ->
                        getLotNumbersForPickOrderByItemId(itemId, request.pickOrderIds)
                    } ?: ""
                    field["lotNo"] = lotNo

                    fields.add(field)
                }
            }

            if (request.isDraft) {
                params["dnTitle"] = "送貨單(初稿)"
                params["colQty"] = "所需數量"
                params["totalCartonTitle"] = ""
            } else {
                params["dnTitle"] = "送貨單"
                params["colQty"] = "數量"
                params["totalCartonTitle"] = "總箱數："
            }

            params["numOfCarton"] = request.numOfCarton.toString()
            if (params["numOfCarton"] == "0") {
                params["numOfCarton"] = ""
            }

            params["deliveryOrderCode"] = deliveryNoteInfo[0].code
            params["shopName"] = deliveryNoteInfo[0].shopName ?: ""
            params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: ""
            params["deliveryDate"] =
                deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
            params["truckNo"] = selectedTruckNo
            params["ShopPurchaseOrderNo"] = deliveryNoteInfo[0].code
            params["FGPickOrderNo"] = selectedPickOrder?.code ?: ""


            return mapOf(
                "report" to PdfUtils.fillReport(deliveryNote, fields, params),
                "filename" to deliveryNoteInfo[0].code
            )
        }


        //Print Delivery Note
        @Transactional
        open fun printDeliveryNote(request: PrintDeliveryNoteRequest) {
            //val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")

            val pdf = exportDeliveryNote(
                ExportDeliveryNoteRequest(
                    deliveryOrderIds = request.deliveryOrderId,
                    numOfCarton = request.numOfCarton,
                    isDraft = request.isDraft,
                    pickOrderIds = request.pickOrderId
                )
            )

            val jasperPrint = pdf["report"] as JasperPrint

            val tempPdfFile = File.createTempFile("print_job_", ".pdf")

            try {
                JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath)

                //val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty
                //printer.ip?.let { ip -> printer.port?.let { port ->
                //    ZebraPrinterUtil.printPdfToZebra(tempPdfFile, ip, port, printQty, ZebraPrinterUtil.PrintDirection.ROTATED)
                //}}
            } finally {
                //tempPdfFile.delete()
            }

        }

        //Carton Labels
        open fun exportDNLabels(request: ExportDNLabelsRequest): Map<String, Any> {
            val DNLABELS_PDF = "DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml"
            val resource = ClassPathResource(DNLABELS_PDF)
            if (!resource.exists()) {
                throw FileNotFoundException("Label file not found: $DNLABELS_PDF")
            }
            val inputStream = resource.inputStream
            val cartonLabel = JasperCompileManager.compileReport(inputStream)
            val cartonLabelInfo =
                deliveryOrderRepository.findDeliveryOrderInfoById(request.deliveryOrderIds).toMutableList()
            val params = mutableMapOf<String, Any>()
            val fields = mutableListOf<MutableMap<String, Any>>()
            for (info in cartonLabelInfo) {
                val field = mutableMapOf<String, Any>()
            }

            params["shopPurchaseOrderNo"] = cartonLabelInfo[0].code
            params["deliveryOrderCode"] = cartonLabelInfo[0].code
            params["shopAddress"] = cartonLabelInfo[0].shopAddress ?: ""
            params["shopName"] = cartonLabelInfo[0].shopName ?: ""

            for (cartonNumber in 1..request.numOfCarton) {
                val field = mutableMapOf<String, Any>()
                fields.add(field)
            }

            return mapOf(
                "report" to PdfUtils.fillReport(cartonLabel, fields, params),
                "filename" to "${cartonLabelInfo[0].code}_carton_labels"
            )

        }


        //Print Carton Labels
        @Transactional
        open fun printDNLabels(request: PrintDNLabelsRequest) {
            val printer =
                printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")

            val pdf = exportDNLabels(
                ExportDNLabelsRequest(
                    deliveryOrderIds = request.deliveryOrderId,
                    numOfCarton = request.numOfCarton
                )
            )

            val jasperPrint = pdf["report"] as JasperPrint

            val tempPdfFile = File.createTempFile("print_job_", ".pdf")

            try {
                JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath)


                val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty
                printer.ip?.let { ip ->
                    printer.port?.let { port ->
                        ZebraPrinterUtil.printPdfToZebra(
                            tempPdfFile,
                            ip,
                            port,
                            printQty,
                            ZebraPrinterUtil.PrintDirection.ROTATED
                        )
                    }
                }

                println("Test PDF saved to: ${tempPdfFile.absolutePath}")

            } finally {
                //tempPdfFile.delete()
            }

        }
        @Transactional(rollbackFor = [Exception::class])
open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult {
    println("🔍 DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}")

    val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id)
        ?: throw NoSuchElementException("Delivery Order not found")

    // ✅ 检查状态，跳过已完成或已发布的DO
    if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) {
        throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release")
    }

    // ✅ 更新状态为released (使用RECEIVING表示已发布)
    deliveryOrder.apply {
        status = DeliveryOrderStatus.RECEIVING  // 使用RECEIVING表示已发布状态
    }
    deliveryOrderRepository.save(deliveryOrder)

    // 创建 pick order
    val pols = deliveryOrder.deliveryOrderLines.map {
        SavePickOrderLineRequest(
            itemId = it.item?.id,
            qty = it.qty ?: BigDecimal.ZERO,
            uomId = it.uom?.id,
        )
    }
    val po = SavePickOrderRequest(
        doId = deliveryOrder.id,
        type = PickOrderType.DELIVERY_ORDER,
        targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(),
        pickOrderLine = pols
    )

    val createdPickOrder = pickOrderService.create(po)
    val consoCode = pickOrderService.assignConsoCode()
    val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null)

    if (pickOrderEntity != null) {
        pickOrderEntity.consoCode = consoCode
        pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED
        pickOrderRepository.saveAndFlush(pickOrderEntity)

        // 创建 suggestions 和 hold inventory
        val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
        val suggestions = suggestedPickLotService.suggestionForPickOrderLines(
            SuggestedPickLotForPolRequest(pickOrderLines = lines)
        )
        val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
        val insufficientCount = suggestions.suggestedList.count { it.suggestedLotLine == null }
        if (insufficientCount > 0) {
            println("⚠️ WARNING: $insufficientCount items have insufficient stock (issues auto-created)")
        }
        val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
            saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
        )

        saveSuggestedPickLots.forEach { lot ->
            if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) {
                val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine)
                if (lineIndex >= 0) {
                    inventoryLotLines[lineIndex].holdQty =
                        (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO)
                }
            }
        }
        inventoryLotLineRepository.saveAll(inventoryLotLines)

        // 创建 stock out
        val stockOut = StockOut().apply {
            this.type = "job"
            this.consoPickOrderCode = consoCode
            this.status = StockOutStatus.PENDING.status
            this.handler = request.userId
        }
        val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

        saveSuggestedPickLots.forEach { lot ->
            val polId = lot.pickOrderLine?.id
            val illId = lot.suggestedLotLine?.id
            if (polId != null && illId != null) {
                val existingLines =
                    stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
                if (existingLines.isEmpty()) {
                    val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null)
                    val inventoryLotLine = inventoryLotLineRepository.findById(illId).orElse(null)

                    if (pickOrderLine != null && inventoryLotLine != null) {
                        val line = StockOutLine().apply {
                            this.stockOut = savedStockOut
                            this.pickOrderLine = pickOrderLine
                            this.inventoryLotLine = inventoryLotLine
                            this.item = pickOrderLine.item
                            this.status = StockOutLineStatus.PENDING.status
                            this.qty = 0.0
                        }
                        stockOutLineRepository.save(line)
                    }
                }
            }
        }
    }

    // 分析楼层分布
    val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now()
    val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct()
    
    val inventoryQuery = """
        SELECT 
            w.store_id as floor, 
            COUNT(DISTINCT il.itemId) as item_count
        FROM inventory_lot il
        INNER JOIN inventory i ON i.itemId = il.itemId AND i.deleted = 0 AND i.onHandQty > 0
        INNER JOIN inventory_lot_line ill ON ill.inventoryLotId = il.id AND ill.deleted = 0
        INNER JOIN warehouse w ON w.id = ill.warehouseId AND w.deleted = 0 AND w.store_id IN ('2F', '4F')
        WHERE il.itemId IN (${itemIds.joinToString(",")}) AND il.deleted = 0
        GROUP BY w.store_id
    """.trimIndent()

    val inventoryResults = jdbcDao.queryForList(inventoryQuery)
    val floorItemCount = mutableMapOf<String, Int>()
    inventoryResults.forEach { row ->
        floorItemCount[row["floor"] as? String ?: "Other"] = (row["item_count"] as? Number)?.toInt() ?: 0
    }

    val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) {
        "4F"
    } else {
        "2F"
    }

    // ✅ 查找匹配 preferred floor 的 truck
    val truck = deliveryOrder.shop?.id?.let { shopId ->
        val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
        val preferredStoreId = when (preferredFloor) {
            "2F" -> 2
            "4F" -> 4
            else -> 2
        }
        
        // 只选择 store_id 匹配的 truck
        val matchedTrucks = trucks.filter { it.storeId == preferredStoreId }
        
        if (matchedTrucks.isEmpty()) {
            null  // 没有匹配的 truck
        } else {
            matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) }
        }
    }

    // ✅ 如果没有匹配的 truck，抛出异常跳过
    if (truck == null) {
        val errorMsg = "No matching truck found for preferredFloor ($preferredFloor). Skipping DO ${deliveryOrder.id}."
        println("⚠️ $errorMsg")
        throw IllegalStateException(errorMsg)
    }

    println("✅ DEBUG: Matched truck - ID=${truck.id}, Store=${truck.storeId}, Floor=$preferredFloor")

    return ReleaseDoResult(
        deliveryOrderId = deliveryOrder.id!!,
        deliveryOrderCode = deliveryOrder.code,
        pickOrderId = createdPickOrder.id!!,
        pickOrderCode = pickOrderEntity?.code,
        shopId = deliveryOrder.shop?.id,
        shopCode = deliveryOrder.shop?.code,
        shopName = deliveryOrder.shop?.name,
        estimatedArrivalDate = targetDate,
        preferredFloor = preferredFloor,
        truckId = truck.id,
        truckDepartureTime = truck.departureTime,
        truckLanceCode = truck.truckLanceCode,
        loadingSequence = truck.loadingSequence  // ✅ 直接使用 truck 的值
    )
}

        @Transactional
        open fun syncDeliveryOrderStatusFromDoPickOrder(): Int {
            val sql = """
                UPDATE fpsmsdb.delivery_order do
                INNER JOIN fpsmsdb.do_pick_order dpo ON dpo.do_order_id = do.id
                SET do.status = 'completed'
                WHERE dpo.ticket_status = 'completed'
                AND do.status != 'completed'
                AND do.deleted = 0
                AND dpo.deleted = 0
            """.trimIndent()
            
            return jdbcDao.executeUpdate(sql, emptyMap<String, Any>())
        }
    }
