package com.ffii.fpsms.modules.stock.service

import com.ffii.core.response.RecordsRes
import com.ffii.core.utils.PdfUtils
import com.ffii.core.utils.QrCodeUtil
import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo
import com.ffii.fpsms.modules.stock.entity.projection.LotLineToQrcode
import com.ffii.fpsms.modules.stock.web.InventoryLotLineController
import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineStatusRequest
import org.springframework.context.annotation.Lazy
import com.ffii.fpsms.modules.stock.web.model.ExportQrCodeRequest
import com.ffii.fpsms.modules.stock.web.model.SaveInventoryLotLineRequest
import com.ffii.fpsms.modules.stock.web.model.SearchInventoryLotLineInfoRequest
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.sf.jasperreports.engine.JasperCompileManager
import org.springframework.core.io.ClassPathResource
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.FileNotFoundException
import java.io.IOException
import java.math.BigDecimal
import java.time.format.DateTimeFormatter
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineQuantitiesRequest
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisRequest
import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisResponse
import com.ffii.fpsms.modules.stock.web.model.ScannedLotInfo
import com.ffii.fpsms.modules.stock.web.model.SameItemLotInfo
import com.ffii.fpsms.modules.stock.web.model.WorkbenchItemLotsResponse
import com.ffii.fpsms.modules.jobOrder.service.JobOrderService
import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest
import com.ffii.fpsms.modules.master.service.PrinterService
import com.ffii.fpsms.modules.stock.web.model.PrintLabelForInventoryLotLineRequest
import com.ffii.fpsms.modules.jobOrder.web.model.PrintFGStockInLabelRequest
import com.ffii.core.utils.ZebraPrinterUtil
import net.sf.jasperreports.engine.JasperExportManager
import java.io.File
import net.sf.jasperreports.engine.JasperPrint

@Service
open class InventoryLotLineService(
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val warehouseRepository: WarehouseRepository,
    private val itemUomRespository: ItemUomRespository,
    private val stockInLineRepository: StockInLineRepository,
    private val inventoryRepository: InventoryRepository,
    private val printerService: PrinterService,
    @Lazy
    private val jobOrderService: JobOrderService
) {
    @Transactional
    open fun markExpiredLotLinesAsUnavailable(): Int {
        return inventoryLotLineRepository.markExpiredLotLinesAsUnavailable()
    }

    open fun findById(id: Long): Optional<InventoryLotLine> {
        return inventoryLotLineRepository.findById(id)
    }

    open fun allInventoryLotLinesByItemIdIn(itemIds: List<Long>): List<InventoryLotLineInfo> {
        return inventoryLotLineRepository.findInventoryLotLineInfoByInventoryLotItemIdIn(itemIds)
    }

    open fun allInventoryLotLinesByPage(request: SearchInventoryLotLineInfoRequest): RecordsRes<InventoryLotLineInfo> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);

        val response = inventoryLotLineRepository.findInventoryLotLineInfoByItemId(request.itemId, pageable)

        val records = response.content
        val total = response.totalElements
        return RecordsRes<InventoryLotLineInfo>(records, total.toInt());
    }

    /**
     * Same rules as [saveInventoryLotLine]: only stay AVAILABLE if previously AVAILABLE and remaining > 0.
     */
    open fun deriveInventoryLotLineStatus(
        previousStatus: InventoryLotLineStatus?,
        inQty: BigDecimal?,
        outQty: BigDecimal?,
        holdQty: BigDecimal?
    ): InventoryLotLineStatus {
     //   val remainingQty =
           // (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO) - (holdQty ?: BigDecimal.ZERO)

            val remainingQty =
            (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO)
        val status = previousStatus
        val qtyStatus =
            if (remainingQty > BigDecimal.ZERO) InventoryLotLineStatus.AVAILABLE else InventoryLotLineStatus.UNAVAILABLE
        return when {
            status == InventoryLotLineStatus.AVAILABLE && qtyStatus == InventoryLotLineStatus.AVAILABLE ->
                InventoryLotLineStatus.AVAILABLE
            else -> InventoryLotLineStatus.UNAVAILABLE
        }
    }
    fun deriveInventoryLotLineStatusForWorkbench(
        inQty: BigDecimal?,
        outQty: BigDecimal?
    ): InventoryLotLineStatus {
        val remaining = (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO)
        return if (remaining > BigDecimal.ZERO) {
            InventoryLotLineStatus.AVAILABLE
        } else {
            InventoryLotLineStatus.UNAVAILABLE
        }
    }
    open fun saveInventoryLotLine(request: SaveInventoryLotLineRequest): InventoryLotLine {
        val inventoryLotLine =
            request.id?.let { inventoryLotLineRepository.findById(it).getOrNull() } ?: InventoryLotLine()
        val inventoryLot = request.inventoryLotId?.let { inventoryLotRepository.findById(it).getOrNull() }
        val warehouse = request.warehouseId?.let { warehouseRepository.findById(it).getOrNull() }
        val stockUom = request.stockUomId?.let { itemUomRespository.findById(it).getOrNull() }
        val status = request.status?.let { _status -> InventoryLotLineStatus.entries.find { it.value == _status } }

        inventoryLotLine.apply {
            this.inventoryLot = inventoryLot
            this.warehouse = warehouse
            inQty = request.inQty
            outQty = request.outQty
            holdQty = request.holdQty
            this.stockUom = stockUom
            this.status = deriveInventoryLotLineStatus(status, request.inQty, request.outQty, request.holdQty)
            remarks = request.remarks
        }

        return inventoryLotLineRepository.save(inventoryLotLine)
    }
    @Transactional
    open fun updateInventoryLotLineStatus(request: UpdateInventoryLotLineStatusRequest): MessageResponse {
        // Get the existing inventory lot line
        val existingLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()

        // Create update request with existing data and new status
        val updateRequest = SaveInventoryLotLineRequest(
            id = existingLotLine.id,
            inventoryLotId = existingLotLine.inventoryLot?.id,
            warehouseId = existingLotLine.warehouse?.id,
            inQty = existingLotLine.inQty,
            outQty = existingLotLine.outQty,

            holdQty = existingLotLine.holdQty,
            stockUomId = existingLotLine.stockUom?.id,
            status = request.status,
            remarks = existingLotLine.remarks
        )

        val updatedLotLine = saveInventoryLotLine(updateRequest)

        //  ADD THIS: Update inventory table after lot line status change
        updateInventoryTable(updatedLotLine)

        return MessageResponse(
            id = updatedLotLine.id,
            name = updatedLotLine.id.toString(),
            code = "SUCCESS",
            type = "inventory_lot_line",
            message = "Inventory lot line status updated successfully",
            errorPosition = null
        )
    }

    //  ADD THIS: New method to update inventory table
    private fun updateInventoryTable(inventoryLotLine: InventoryLotLine) {
        try {
            // Get the item ID from the inventory lot
            val itemId = inventoryLotLine.inventoryLot?.item?.id
            if (itemId == null) {
                println("Cannot update inventory table: itemId is null for lot line ${inventoryLotLine.id}")
                return
            }

            // Calculate onHoldQty (sum of holdQty from available lots only)
            val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE.value)
                .sumOf { it.holdQty ?: BigDecimal.ZERO }

            // Calculate unavailableQty (sum of inQty from unavailable lots only)
            val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE.value)
            .sumOf {
                val inQty = it.inQty ?: BigDecimal.ZERO
                val outQty = it.outQty ?: BigDecimal.ZERO
                val remainingQty = inQty.minus(outQty)
                remainingQty
            }

            // Update the inventory table
            val inventory = inventoryRepository.findByItemId(itemId).orElse(null)
            if (inventory != null) {
                inventory.onHoldQty = onHoldQty
                inventory.unavailableQty = unavailableQty
                inventoryRepository.save(inventory)

                println("Updated inventory for item $itemId: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty")
            } else {
                println("Inventory not found for item $itemId")
            }
        } catch (e: Exception) {
            println("Error updating inventory table for lot line ${inventoryLotLine.id}: ${e.message}")
            e.printStackTrace()
        }
    }
    @Throws(IOException::class)
    @Transactional
    open fun exportStockInLineQrcode(request: LotLineToQrcode): Map<String, Any> {
        val QRCODE_PDF = "qrCodeLabel/poItemPDF.jrxml"
        val resource = ClassPathResource(QRCODE_PDF)
        if (!resource.exists()) {
            println("here")
            throw FileNotFoundException("Report file not found: $QRCODE_PDF")
        }
        val inputStream = resource.inputStream
        val poLabel = JasperCompileManager.compileReport(inputStream)

        // 1. 从 inventoryLotLineId 获取 inventory_lot_line
        val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()

        // 2. 通过 inventory_lot_line.inventoryLot.stockInLine 获取 stock_in_line 信息（用于其他字段）
        val stockInLineInfo = inventoryLotLine.inventoryLot?.stockInLine?.id?.let {
            stockInLineRepository.findStockInLineInfoById(it).orElseThrow()
        } ?: throw NoSuchElementException("StockInLine not found for inventoryLotLineId=${request.inventoryLotLineId}")

        val qrCodeInfo = listOf(stockInLineInfo)
        val fields = mutableListOf<MutableMap<String ,Any>>()

        for (info in qrCodeInfo) {
            val field = mutableMapOf<String, Any>()
            val qrContent = QrContent(itemId = info.itemId, stockInLineId = info.id)
            val qrCodeContent = (Json.encodeToString(qrContent))

            // 使用 stockInLineInfo 的其他字段
            field["itemName"] = info.itemName ?: "N/A"
            field["itemNo"] = info.itemNo
            field["poCode"] = info.poCode ?: "N/A"
            field["itemType"] = info.itemType ?: "N/A"

            // 3. 计算 inventory_lot_line 的剩余数量（onHandQty）
            val zero = BigDecimal.ZERO
            val onHandQty = (inventoryLotLine.inQty ?: zero)
            // .minus(inventoryLotLine.outQty ?: zero)
            //  .minus(inventoryLotLine.holdQty ?: zero)

            field["acceptedQty"] = "%.2f".format(info.acceptedQty)

            val stockItemUom = itemUomRespository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse(info.itemId)
            field["uom"] = stockItemUom?.uom?.udfudesc ?: info.uom?.udfudesc?.toString() ?: "N/A"

            field["productionDate"] = info.productionDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
            field["expiryDate"] = info.expiryDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
            field["lotNo"] = info.lotNo!!
            field["supplier"] = info.supplier ?: "N/A"
            val image = QrCodeUtil.generateQRCodeImage(qrCodeContent)
            field["qrCode"] = image
            fields.add(field)
        }
        val params: MutableMap<String, Any> = mutableMapOf(
            "poCode" to (qrCodeInfo[0].poCode ?: "N/A"),
            "isTransfer" to (request.isTransfer ?: "")
        )
        return mapOf(
            "report" to PdfUtils.fillReport(poLabel,fields, params),
            "fileName" to (qrCodeInfo[0].poCode ?: qrCodeInfo[0].lotNo ?: "N/A")
        );
    }

    @Throws(IOException::class)
    @Transactional
    open fun exportLabelForInventoryLotLine(inventoryLotLineId: Long): Map<String, Any> {
        val inventoryLotLine = inventoryLotLineRepository.findById(inventoryLotLineId).orElseThrow()
        val stockInLine = inventoryLotLine.inventoryLot?.stockInLine

        return when {
            stockInLine?.jobOrder != null -> {
                val result = jobOrderService.exportFGStockInLabel(
                    ExportFGStockInLabelRequest(stockInLineId = stockInLine.id!!)
                )
                mapOf(
                    "report" to (result["report"]!!),
                    "fileName" to (result["filename"] ?: "label")
                )
            }
            stockInLine?.stockTransferRecord != null -> {
                val targetLocation = stockInLine.stockTransferRecord?.targetLocation ?: ""
                exportStockInLineQrcode(
                    LotLineToQrcode(inventoryLotLineId = inventoryLotLineId, isTransfer = "轉倉至 $targetLocation")
                )
            }
            else -> exportStockInLineQrcode(LotLineToQrcode(inventoryLotLineId = inventoryLotLineId))
        }
    }

    @Transactional
    open fun printLabelForInventoryLotLine(request: PrintLabelForInventoryLotLineRequest) {
        val printer = printerService.findById(request.printerId)
            ?: throw NoSuchElementException("No such printer")
        val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()
        val stockInLine = inventoryLotLine.inventoryLot?.stockInLine

        when {
            stockInLine?.jobOrder != null -> {
                jobOrderService.printFGStockInLabel(
                    PrintFGStockInLabelRequest(
                        stockInLineId = stockInLine.id!!,
                        printerId = request.printerId,
                        printQty = request.printQty
                    )
                )
            }
            else -> {
                val pdf = if (stockInLine?.stockTransferRecord != null) {
                    val targetLocation = stockInLine.stockTransferRecord?.targetLocation ?: ""
                    exportStockInLineQrcode(
                        LotLineToQrcode(inventoryLotLineId = request.inventoryLotLineId, isTransfer = "已轉倉至 $targetLocation")
                    )
                } else {
                    exportStockInLineQrcode(LotLineToQrcode(inventoryLotLineId = request.inventoryLotLineId))
                }
                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,
                                printer.dpi
                            )
                        }
                    }
                } finally {
                    tempPdfFile.delete()
                }
            }
        }
    }

    @Transactional
open fun updateInventoryLotLineQuantities(request: UpdateInventoryLotLineQuantitiesRequest): MessageResponse {
    try {
        val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()

        // Handle quantity updates based on operation
        var newHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO
        var newOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO

        when (request.operation) {
            "pick" -> {
                // Move from hold_qty to out_qty
                newHoldQty = newHoldQty.minus(request.qty)
                newOutQty = newOutQty.plus(request.qty)
            }
            "release" -> {
                // Move from out_qty back to hold_qty (if needed)
                newOutQty = newOutQty.minus(request.qty)
                newHoldQty = newHoldQty.plus(request.qty)
            }
            else -> {
                throw IllegalArgumentException("Unknown operation: ${request.operation}")
            }
        }

        // Validate quantities
        if (newHoldQty < BigDecimal.ZERO || newOutQty < BigDecimal.ZERO) {
            throw IllegalArgumentException("Invalid quantities: holdQty=$newHoldQty, outQty=$newOutQty")
        }

        val updateRequest = SaveInventoryLotLineRequest(
            id = inventoryLotLine.id,
            inventoryLotId = inventoryLotLine.inventoryLot?.id,
            warehouseId = inventoryLotLine.warehouse?.id,
            stockUomId = inventoryLotLine.stockUom?.id,
            inQty = inventoryLotLine.inQty,
            outQty = newOutQty,
            holdQty = newHoldQty,
            status = inventoryLotLine.status?.value, // Keep existing status
            remarks = inventoryLotLine.remarks
        )

        val updatedInventoryLotLine = saveInventoryLotLine(updateRequest)

        return MessageResponse(
            id = updatedInventoryLotLine.id,
            name = "Inventory lot line quantities updated",
            code = "SUCCESS",
            type = "inventory_lot_line",
            message = "Updated: holdQty=${newHoldQty}, outQty=${newOutQty}, operation=${request.operation}",
            errorPosition = null
        )
    } catch (e: Exception) {
        return MessageResponse(
            id = null,
            name = "Failed to update inventory lot line quantities",
            code = "ERROR",
            type = "inventory_lot_line",
            message = "Error: ${e.message}",
            errorPosition = null
        )
    }
}

    /**
     * Legacy / FG label modal: remaining qty = in - out - hold.
     */
    open fun analyzeQrCode(request: QrCodeAnalysisRequest): QrCodeAnalysisResponse {
        return analyzeQrCodeInternal(request, availableInOutOnly = false)
    }

    /**
     * DO workbench label modal: pickable qty = in - out (aligned with workbench scan-pick).
     */
    open fun analyzeQrCodeWorkbench(request: QrCodeAnalysisRequest): QrCodeAnalysisResponse {
        return analyzeQrCodeInternal(request, availableInOutOnly = true)
    }

    /**
     * DO workbench label modal fallback (no stockInLineId/no-lot row):
     * list available lots by item only, using pickable qty = in - out.
     */
    open fun listWorkbenchAvailableLotsByItem(itemId: Long): WorkbenchItemLotsResponse {
        val source = inventoryLotLineRepository
            .findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE)
            .asSequence()
            .filter { !it.deleted && it.inventoryLot?.item != null }
            .toList()
        val item = source.firstOrNull()?.inventoryLot?.item
        if (item == null) {
            return WorkbenchItemLotsResponse(
                itemId = itemId,
                itemCode = "",
                itemName = "",
                sameItemLots = emptyList()
            )
        }
        val sameItemLots = source
            .mapNotNull { lotLine ->
                val lot = lotLine.inventoryLot ?: return@mapNotNull null
                val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null
                val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null
                val inQty = lotLine.inQty ?: BigDecimal.ZERO
                val outQty = lotLine.outQty ?: BigDecimal.ZERO
                val remainingQty = inQty.minus(outQty)
                if (remainingQty <= BigDecimal.ZERO) return@mapNotNull null
                SameItemLotInfo(
                    lotNo = lotNo,
                    inventoryLotLineId = lotLine.id ?: return@mapNotNull null,
                    stockInLineId = lot.stockInLine?.id,
                    availableQty = remainingQty,
                    uom = uomDesc,
                    warehouseCode = lotLine.warehouse?.code,
                    warehouseName = lotLine.warehouse?.name,
                )
            }

        return WorkbenchItemLotsResponse(
            itemId = item.id ?: itemId,
            itemCode = item.code ?: "",
            itemName = item.name ?: "",
            sameItemLots = sameItemLots,
        )
    }

    private fun analyzeQrCodeInternal(
        request: QrCodeAnalysisRequest,
        availableInOutOnly: Boolean,
    ): QrCodeAnalysisResponse {
        val stockInLine = stockInLineRepository.findById(request.stockInLineId).orElseThrow()

        val scannedInventoryLotLine =
            stockInLine.inventoryLotLine
                ?: stockInLine.inventoryLot?.inventoryLotLines?.firstOrNull()
                ?: throw IllegalStateException("No inventory lot line found for stockInLineId=${request.stockInLineId}")

        val item = scannedInventoryLotLine.inventoryLot?.item
            ?: throw IllegalStateException("Item not found for lot line id=${scannedInventoryLotLine.id}")

        val sameItemLots = inventoryLotLineRepository
            .findAllByInventoryLotItemIdAndStatus(request.itemId, InventoryLotLineStatus.AVAILABLE)
            .asSequence()
            .filter { it.id != scannedInventoryLotLine.id }
            .mapNotNull { lotLine ->
                val lot = lotLine.inventoryLot ?: return@mapNotNull null
                val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null
                val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null
                val whCode = lotLine.warehouse?.code
                val whName = lotLine.warehouse?.name

                val inQty = lotLine.inQty ?: BigDecimal.ZERO
                val outQty = lotLine.outQty ?: BigDecimal.ZERO
                val holdQty = lotLine.holdQty ?: BigDecimal.ZERO
                val remainingQty =
                    if (availableInOutOnly) inQty.minus(outQty) else inQty.minus(outQty).minus(holdQty)

                if (remainingQty > BigDecimal.ZERO) {
                    SameItemLotInfo(
                        lotNo = lotNo,
                        inventoryLotLineId = lotLine.id!!,
                        stockInLineId = lot.stockInLine?.id,
                        availableQty = remainingQty,
                        uom = uomDesc,
                        warehouseCode = whCode,
                        warehouseName = whName,
                    )
                } else {
                    null
                }
            }
            .toList()

        val scannedLotNo = stockInLine.lotNo
            ?: stockInLine.inventoryLot?.stockInLine?.lotNo
            ?: throw IllegalStateException("Lot number not found for stockInLineId=${request.stockInLineId}")

        return QrCodeAnalysisResponse(
            itemId = request.itemId,
            itemCode = item.code ?: "",
            itemName = item.name ?: "",
            scanned = ScannedLotInfo(
                stockInLineId = request.stockInLineId,
                lotNo = scannedLotNo,
                inventoryLotLineId = scannedInventoryLotLine.id
                    ?: throw IllegalStateException("inventoryLotLineId missing on scanned lot line"),
                warehouseCode = scannedInventoryLotLine.warehouse?.code,
                warehouseName = scannedInventoryLotLine.warehouse?.name,
            ),
            sameItemLots = sameItemLots,
        )
    }
}