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

import com.ffii.core.exception.BadRequestException
import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.core.utils.QrCodeUtil
import com.ffii.fpsms.modules.common.CodeGenerator
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.entity.QcItemRepository
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository
import com.ffii.fpsms.modules.qc.entity.QcResult
import com.ffii.fpsms.modules.qc.entity.QcResultRepository
import com.ffii.fpsms.modules.stock.entity.*
import com.ffii.fpsms.modules.stock.sql.StockSql.SQL.INVENTORY_COUNT
import net.sf.jasperreports.engine.JasperCompileManager
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import com.ffii.core.utils.PdfUtils;
import com.ffii.core.utils.ZebraPrinterUtil
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.master.service.PrinterService
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.StockInLineInfo
import com.ffii.fpsms.modules.stock.web.model.*
import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus
import java.io.FileNotFoundException
import java.time.format.DateTimeFormatter
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import net.sf.jasperreports.engine.JasperExportManager
import net.sf.jasperreports.engine.JasperPrint
import java.io.File
import kotlin.jvm.optionals.getOrNull
import kotlin.math.max

@Serializable
data class QrContent(val itemId: Long, val stockInLineId: Long)

@Service
open class StockInLineService(
    private val jdbcDao: JdbcDao,
    private val poRepository: PurchaseOrderRepository,
    private val polRepository: PurchaseOrderLineRepository,
    private val qcItemsRepository: QcItemRepository,
    private val qcResultRepository: QcResultRepository,
    private val escalationLogService: EscalationLogService,
    private val escalationLogRepository: EscalationLogRepository,
    private val stockInService: StockInService,
    private val stockInRepository: StockInRepository,
    private val stockInLineRepository: StockInLineRepository,
    private val jobOrderRepository: JobOrderRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val itemRepository: ItemsRepository,
    private val warehouseRepository: WarehouseRepository,
    private val itemUomRepository: ItemUomRespository,
    private val printerService: PrinterService,
    private val stockTakeLineRepository: StockTakeLineRepository,
): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) {

    open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo {
        return stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(stockInLineId)
    }
    open fun getReceivedStockInLineInfo(stockInLineId: Long): StockInLineInfo {
        return stockInLineRepository.findStockInLineInfoByIdAndStatusAndDeletedFalse(id = stockInLineId, status = StockInLineStatus.RECEIVED.status).orElseThrow()
    }

    open fun assignLotNo(): String {
        val prefix = "LT"
        val midfix = CodeGenerator.DEFAULT_MIDFIX
        val latestCode = inventoryLotRepository.findLatestLotNoByPrefix("${prefix}-${midfix}")
        return CodeGenerator.generateNo(prefix = prefix, latestCode = latestCode)
    }

    @Throws(IOException::class)
    @Transactional
    open fun create(request: SaveStockInLineRequest): MessageResponse {
        val stockInLine = StockInLine()
        val pol = if (request.purchaseOrderLineId != null)
            request.purchaseOrderLineId?.let { polRepository.findById(it).orElseThrow() }
        else null
        val stl = if (request.stockTakeLineId != null)
            request.stockTakeLineId?.let { stockTakeLineRepository.findById(it).getOrNull() }
        else null
        val jo = if (request.jobOrderId != null)
            request.jobOrderId?.let { jobOrderRepository.findById(it).getOrNull() }
        else null

        var stockIn = if (request.stockInId != null) request.stockInId?.let { stockInRepository.findByIdAndDeletedFalse(it) }
        else if (pol != null) pol.purchaseOrder?.id?.let { stockInRepository.findByPurchaseOrderIdAndDeletedFalse(it) }
        else if (request.stockTakeId != null) request.stockTakeId?.let { stockInRepository.findByStockTakeIdAndDeletedFalse(it) }
        else null

        if (stockIn == null) {
            stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = pol?.purchaseOrder?.id)).entity as StockIn
//            stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn
//        var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId)
        }

        val item = itemRepository.findById(request.itemId).orElseThrow()
        // If request contains valid POL
        if (pol != null) {
            val po = pol.purchaseOrder;
            pol.apply {
                status = PurchaseOrderLineStatus.RECEIVING
            }
            val savedPol = polRepository.saveAndFlush(pol)
            stockInLine.apply {
                this.purchaseOrderLine = savedPol
            }

            // update po status to receiving
            if (po != null) {
                po.apply {
                    status = PurchaseOrderStatus.RECEIVING
                }
                val savedPo = poRepository.save(po)
                stockInLine.apply {
                    this.purchaseOrder = savedPo
                }
            }
        }

        // If request contains valid stock take id
        if (stl != null) {
            stockInLine.apply {
                this.stockTakeLine = stl
            }
        }

        // If request contains valid job order id
        if (jo != null) {
            stockInLine.apply {
                this.jobOrder = jo
            }
        }

//        val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!)
//        if (pol.qty!! < request.acceptedQty) {
//            throw BadRequestException()
//        }
        stockInLine.apply {
            this.item = item
            itemNo = item.code
            this.stockIn = stockIn
            acceptedQty = request.acceptedQty
            dnNo = request.dnNo
            receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now()
            productLotNo = request.productLotNo
            status = StockInLineStatus.PENDING.status
        }
        val savedSIL = saveAndFlush(stockInLine)
        val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedSIL.id!!)
        return MessageResponse(
            id = savedSIL.id,
            code = savedSIL.itemNo,
            name = savedSIL.item!!.name,
            type = "stock in line created: status = pending",
            message = "save success",
            errorPosition = null,
            entity = lineInfo
        )
    }

    @Throws(IOException::class)
    @Transactional
    fun saveInventoryLotWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): InventoryLot {
        val inventoryLot = InventoryLot()
        val args = mapOf(
            "from" to LocalDate.now().atStartOfDay(),
            "to" to LocalDateTime.now(),
            "itemId" to stockInLine.item!!.id
        )
        val inventoryCount = jdbcDao.queryForInt(INVENTORY_COUNT.toString(), args)
//        val newLotNo = CodeGenerator.generateCode(prefix = "MPO", itemId = stockInLine.item!!.id!!, count = inventoryCount)
        val newLotNo = assignLotNo()
        inventoryLot.apply {
            this.item = stockInLine.item
            this.stockInLine = stockInLine
            stockInDate = LocalDateTime.now()
            expiryDate = request.expiryDate// frontend form input
            lotNo = newLotNo
        }
        val savedInventoryLot = inventoryLotRepository.saveAndFlush(inventoryLot)
        return savedInventoryLot
    }

    @Throws(IOException::class)
    @Transactional
    fun saveInventoryLotLineWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): List<InventoryLotLine> {
        val response = request.inventoryLotLines?.let { lines ->
            val saveLines = mutableListOf<InventoryLotLine>();
            lines.forEach { line ->
                val inventoryLotLine = InventoryLotLine()
                val warehouse = warehouseRepository.findById(line.warehouseId!!).orElseThrow()
                val stockItemUom = itemUomRepository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse(
                    itemId = request.itemId
                )
                val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)
                val convertedBaseQty = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
                    (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
                } else {
                    (line.qty)
                }
                inventoryLotLine.apply {
                    this.inventoryLot = stockInLine.inventoryLot
                    this.warehouse = warehouse
                    this.inQty = convertedBaseQty
                    this.status = InventoryLotLineStatus.AVAILABLE
                    this.stockUom = stockItemUom
                }
                saveLines.add(inventoryLotLine)
            }
            inventoryLotLineRepository.saveAll(saveLines)
        }

        return response ?: emptyList();
//        val inventoryLotLine = InventoryLotLine()
//        val warehouse = warehouseRepository.findById(request.warehouseId!!).orElseThrow()
//        val stockItemUom = itemUomRepository.findBaseUnitByItemIdAndStockUnitIsTrueAndDeletedIsFalse(
//            itemId = request.itemId
//        )
//        val convertedBaseQty = if (stockItemUom != null) {
//            (request.acceptQty?:request.acceptedQty) * stockItemUom.ratioN!! / stockItemUom.ratioD!!
//        } else {
//            (request.acceptQty?:request.acceptedQty)
//        }
//        inventoryLotLine.apply {
//            this.inventoryLot = stockInLine.inventoryLot
//            this.warehouse = warehouse
//            this.inQty = convertedBaseQty
//            this.status = InventoryLotLineStatus.AVAILABLE
//            this.stockUom = stockItemUom
//        }
//        val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
//        return savedInventoryLotLine
    }

    @Throws(IOException::class)
    @Transactional
    fun saveQcResultWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine, escLogId: Long?= 0): List<QcResult>? {
        if (!request.qcResult.isNullOrEmpty()) {
            val qcResultEntries = request.qcResult!!.map {
                val qcItem = qcItemsRepository.findById(it.qcItemId).orElseThrow()
                val item = itemRepository.findById(stockInLine.item!!.id!!).orElseThrow()
                val qcResult = QcResult();

                QcResult().apply {
                    this.qcItem = qcItem
                    this.item = item
                    this.stockInLine = stockInLine
                    this.failQty = it.failQty
                    this.qcPassed = it.qcPassed
                    this.type = "qc" // default as qc for now
                    this.remarks = it.remarks
                    this.escalationLog = escalationLogService.find(escLogId).getOrNull()
                }
            }
            return qcResultRepository.saveAllAndFlush(qcResultEntries)
        }
        return null
    }

    @Throws(IOException::class)
    @Transactional
    fun saveEscalationLogWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): EscalationLog? {
        if (request.escalationLog != null) {    // Save Esc log
            val escReq = request.escalationLog;

            var qcTotal = 0;
            var qcFailed = 0;
//            if (request.qcResult!!.isEmpty()) {
            if (request.status == StockInLineStatus.ESCALATED.status) {
                val escLogList = stockInLine.id?.let { escalationLogRepository.findAllByDeletedFalseAndStockInLineIdIn(listOf(it)); }
                val latestLog = escLogList?.maxBy { it.id!! };
                latestLog?.apply {
                    status = if (request.qcAccept == true) EscalationLogStatus.ACCEPTED else EscalationLogStatus.REJECTED
                }
                escalationLogService.save(latestLog);

                val qcRes = qcResultRepository.findQcResultInfoByStockInLineIdAndDeletedFalse(stockInLine.id!!);
                qcTotal = qcRes.size;
                qcFailed = qcRes.count{ !it.qcPassed};
            } else {
                qcTotal = request.qcResult?.size ?: 5;
                qcFailed = request.qcResult?.count { !it.qcPassed } ?: 0;
            }

            escReq?.apply {
                this.stockInLineId = stockInLine.id;
                this.qcTotalCount = qcTotal;
                this.qcFailCount = qcFailed;
            }
            if (escReq != null) {
                val res = escalationLogService.saveEscalationLog(escReq)
                return res.id?.let { escalationLogRepository.findById(it).orElseThrow() };
            };
        } else {    // Update log status
            val escLogList = stockInLine.id?.let { escalationLogRepository.findAllByDeletedFalseAndStockInLineIdIn(listOf(it)); }
            val latestLog = escLogList?.maxBy { it.id!! };
            latestLog?.apply {
                status = if (request.qcAccept == true) EscalationLogStatus.ACCEPTED else EscalationLogStatus.REJECTED
            }
            escalationLogService.save(latestLog);
        }
        return null
    }

    @Throws(IOException::class)
    @Transactional
    open fun updatePurchaseOrderStatus(po : PurchaseOrder) {
        val unfinishedPol = polRepository
            .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(po.id!!,
                PurchaseOrderLineStatus.COMPLETED)
        // If all POL is completed
        if (unfinishedPol.isEmpty()) {
            po.apply {
                status = PurchaseOrderStatus.COMPLETED
            }
            poRepository.saveAndFlush(po)
        } else {

        }
    }
    @Throws(IOException::class)
    @Transactional
    fun updatePurchaseOrderLineStatus(pol: PurchaseOrderLine) {
        val stockInLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(pol.id!!)
        val qcLines = stockInLines.filter { it.status == StockInLineStatus.PENDING.status
                || it.status == StockInLineStatus.ESCALATED.status }
        val receivingLines = stockInLines.filter { it.status == StockInLineStatus.RECEIVED.status
                || it.status == StockInLineStatus.RECEIVING.status }
        // TODO: also check the qty
        if (stockInLines.isEmpty()) {   // No Stock In Line
            pol.apply { status = PurchaseOrderLineStatus.PENDING }
        } else {
            if (qcLines.isEmpty()) {    // No pending QC lines
                if (receivingLines.isEmpty()) { // No receiving lines
                    pol.apply {
                        status = PurchaseOrderLineStatus.COMPLETED
                    }
                } else {    // Only remain receiving lines
                    pol.apply {
                        status = PurchaseOrderLineStatus.RECEIVING
                    }
                }
            } else {
                // still have pending QC lines
                pol.apply {
                    status = PurchaseOrderLineStatus.RECEIVING
                }
            }
        }
        polRepository.saveAndFlush(pol)
    }
    @Throws(IOException::class)
    @Transactional
    open fun update(request: SaveStockInLineRequest): MessageResponse {
        val stockInLine = if (request.id != null) stockInLineRepository.findById(request.id!!).orElseThrow()
        else return MessageResponse(
            id = null,
            code = null,
            name = null,
            type = "Found Null",
            message = "stock in line id is null",
            errorPosition = null,
        )
//        val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInLine!!.stockIn!!.id!!)

        if (stockInLine.expiryDate != null && request.expiryDate == null) {
            request.apply {
                expiryDate = stockInLine.expiryDate
            }
        }
        // TODO: check all status to prevent reverting progress due to multiple users access to the same po?
        // return list of stock in line, update data grid with the list
        stockInLine.apply {
            this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate// maybe need to change the request to LocalDateTime
            this.productLotNo = request.productLotNo ?: this.productLotNo
            this.dnNo = request.dnNo ?: this.dnNo
//            this.dnDate = request.dnDate?.atStartOfDay() ?: this.dnDate
            this.acceptedQty = request.acceptedQty
            this.demandQty = request.acceptQty
            this.invoiceNo = request.invoiceNo
            this.receiptDate = request.receiptDate?.atStartOfDay() ?: this.receiptDate
//            this.status = request.status
            this.expiryDate = stockInLine.expiryDate ?: request.expiryDate
            this.remarks = stockInLine.remarks ?: request.remarks
        }
        if (request.status == StockInLineStatus.RECEIVED.status) {
            // Putaway
            var savedInventoryLotLine: InventoryLotLine? = null

//            savedInventoryLotLine = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
            val savedInventoryLotLines = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
            val inventoryLotLines = stockInLine.inventoryLot?.let { it.id?.let { _id -> inventoryLotLineRepository.findAllByInventoryLotId(_id) } } ?: listOf()

            val purchaseItemUom = itemUomRepository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId)
            val stockItemUom = itemUomRepository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId)
            val ratio = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) {
                (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!)
            } else {
                BigDecimal.ONE
            }

            if (inventoryLotLines.sumOf { it.inQty ?: BigDecimal.ZERO } >= request.acceptQty?.times(ratio)) {
                stockInLine.apply {
                    this.status = if (request.acceptQty?.compareTo(request.acceptedQty)  == 0)
                        StockInLineStatus.COMPLETE.status else StockInLineStatus.PARTIALLY_COMPLETE.status
//                this.inventoryLotLine = savedInventoryLotLine
                }
                // Update JO Status
                if (stockInLine.jobOrder != null) { //TODO Improve
                    val jo = stockInLine.jobOrder
                    jo?.apply {
                        status = JobOrderStatus.COMPLETED
                    }
                    jobOrderRepository.save(jo!!)
                }
            }
        } else if (request.status == StockInLineStatus.PENDING.status || request.status == StockInLineStatus.ESCALATED.status) {
            var escLogId : Long? = 0L;
            // Escalation
            if (request.status == StockInLineStatus.ESCALATED.status) {
                saveEscalationLogWhenStockIn(request, stockInLine)?.id;
            }
            // QC
            if (request.qcAccept == true) {
                // Accepted
                if (request.acceptedQty > BigDecimal(0)) {
                    var savedInventoryLot: InventoryLot? = null

//                    if (request.status == StockInLineStatus.RECEIVED.status) {
                    savedInventoryLot = saveInventoryLotWhenStockIn(request = request, stockInLine = stockInLine)
//                    }
                    //            println("checkpoint1")
                    stockInLine.apply {
                        this.status = StockInLineStatus.RECEIVED.status;
                        this.inventoryLot = stockInLine.inventoryLot ?: savedInventoryLot
                        this.lotNo = stockInLine.lotNo ?: savedInventoryLot?.lotNo
                    }
                } else {
                    return MessageResponse(
                        id = null,
                        code = null,
                        name = null,
                        type = "acceptedQty == 0",
                        message = "acceptedQty cannot be 0",
                        errorPosition = "request.acceptedQty",
                    )
                }
            } else if (request.qcAccept == false) {
                if (request.escalationLog != null) {
                    // Escalated
                    stockInLine.apply {
                        this.status = StockInLineStatus.ESCALATED.status
                    }
                    escLogId = saveEscalationLogWhenStockIn(request, stockInLine)?.id;
                } else {
                    // Rejected
                    stockInLine.apply {
                        this.status = StockInLineStatus.REJECT.status
                    }
                }
            }

            saveQcResultWhenStockIn(request, stockInLine, escLogId)
        }
        val savedStockInLine = saveAndFlush(stockInLine)
        // check if all line completed
        if (savedStockInLine.purchaseOrderLine != null) {
            val pol = savedStockInLine.purchaseOrderLine
            if (pol != null) {
                updatePurchaseOrderLineStatus(pol)
                updatePurchaseOrderStatus(pol.purchaseOrder!!)
            }
        }

        val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedStockInLine.id!!)

        return MessageResponse(
            id = savedStockInLine.id,
            code = null,
            name = null,
            type = "update Success",
            message = "stock in line update status success",
            errorPosition = null,
            entity = listOf(lineInfo)
        )

    }

    @Throws(IOException::class)
    @Transactional
    open fun exportStockInLineQrcode(request: ExportQrCodeRequest): 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)
        val qrCodeInfo = stockInLineRepository.findStockInLineInfoByIdInAndDeletedFalse(request.stockInLineIds).toMutableList()
        val fields = mutableListOf<MutableMap<String ,Any>>()

        for (info in qrCodeInfo) {
            val field = mutableMapOf<String, Any>()
            val qrContent = QrContent(info.itemId, info.id)
            val qrCodeContent = (Json.encodeToString(qrContent))
//            field["itemId"] = info.itemId
            field["itemName"] = info.itemName ?: "N/A"
            field["itemNo"] = info.itemNo
            field["poCode"] = info.poCode ?: "N/A"
            field["itemType"] = info.itemType ?: "N/A"
            field["acceptedQty"] = info.acceptedQty.toString()
            field["uom"] = 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 ?: "N/A"
            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") //TODO change to JO code for JO
        )
        return mapOf(
            "report" to PdfUtils.fillReport(poLabel,fields, params),
            "fileName" to (qrCodeInfo[0].poCode ?: qrCodeInfo[0].joCode ?: qrCodeInfo[0].lotNo).toString()
        );
    }

    @Transactional
    open fun printQrCode(request: PrintQrCodeForSilRequest) {
        val printer = printerService.findById(request.printerId) ?: throw NoSuchElementException("No such printer");

        val pdf = exportStockInLineQrcode(
            ExportQrCodeRequest(
                stockInLineIds = listOf(request.stockInLineId)
            )
        )

        val jasperPrint = pdf["report"] as JasperPrint

        // 1. Create a temporary file to save the PDF.
        val tempPdfFile = File.createTempFile("print_job_", ".pdf")

        try {
            // 2. Export the JasperPrint to the temporary PDF file.
            JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath)

            // 3. Call the utility function with the temporary file.
            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 {
            // 4. Ensure the temporary file is deleted after the print job is sent.
            tempPdfFile.delete()
        }
    }
}