package com.ffii.fpsms.modules.jobOrder.service

import com.ffii.core.exception.BadRequestException
import com.ffii.core.response.RecordsRes
import com.ffii.core.utils.GsonUtils
import com.ffii.core.utils.PdfUtils
import com.ffii.fpsms.modules.common.SecurityUtils
import com.ffii.fpsms.modules.jobOrder.entity.JobOrder
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository
import com.ffii.fpsms.modules.master.service.BomService
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderLineRequest
import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderRequest
import com.ffii.fpsms.modules.user.service.UserService
import com.google.gson.reflect.TypeToken
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.jvm.optionals.getOrNull

import com.ffii.fpsms.modules.jobOrder.web.model.*
import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest
import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest
import com.ffii.fpsms.modules.master.service.PrinterService
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService
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 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 java.io.IOException


@Service
open class JobOrderService(
    val jobOrderRepository: JobOrderRepository,
    val bomService: BomService,
    val userService: UserService,
    val productionScheduleLineRepository: ProductionScheduleLineRepository,
    val pickOrderService: PickOrderService,
    val joPickOrderService: JoPickOrderService,
    val pickOrderRepository: PickOrderRepository,
    val pickOrderLineRepository: PickOrderLineRepository,
    val suggestedPickLotService: SuggestedPickLotService,
    val inventoryLotLineRepository: InventoryLotLineRepository,
    val stockOutRepository: StockOutRepository,
    val stockOutLineRepository: StockOutLIneRepository,
    private val printerService: PrinterService,

) {

    open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> {
        val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);

        val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc(
            code = request.code ?: "",
            bomName = request.itemName ?: "",
            pageable = pageable
        )

        val planStartFrom = request.planStart
        val planStartTo = request.planStartTo
        val records = response.content
            .filter {
//                println("from ($planStartFrom):" +(planStartFrom == null || (it.planStart != null && (planStartFrom.isEqual(it.planStart) || planStartFrom.isBefore(it.planStart)))))
//                println("to: ($planStartTo)" +(planStartTo == null || (it.planStart != null && (planStartTo.isEqual(it.planStart) || planStartTo.isAfter(it.planStart)))))
               (planStartFrom == null || (it.planStart != null && (planStartFrom.isEqual(it.planStart) || planStartFrom.isBefore(it.planStart)))) &&
                (planStartTo == null || (it.planStart != null && (planStartTo.isEqual(it.planStart) || planStartTo.isAfter(it.planStart))))
            }

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

    open fun jobOrderDetail(id: Long): JobOrderDetail {
        val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException();

        val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
        val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
        return JobOrderDetail(
            id = sqlResult.id,
            code = sqlResult.code,
            itemCode = sqlResult.itemCode,
            name = sqlResult.name,
            reqQty = sqlResult.reqQty,
            uom = sqlResult.uom,
            pickLines = jsonResult,
            status = sqlResult.status,
            shortUom = sqlResult.shortUom
        )
    }

    open fun jobOrderDetailByCode(code: String): JobOrderDetail {
        val sqlResult = jobOrderRepository.findJobOrderDetailByCode(code) ?: throw NoSuchElementException("Job Order not found: $code");

        val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
        val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
        return JobOrderDetail(
            id = sqlResult.id,
            code = sqlResult.code,
            itemCode = sqlResult.itemCode,
            name = sqlResult.name,
            reqQty = sqlResult.reqQty,
            uom = sqlResult.uom,
            pickLines = jsonResult,
            status = sqlResult.status,
            shortUom = sqlResult.shortUom
        )
    }

    open fun assignJobNo(): String {
        val suffixFormat = "%03d"
        val pattern = "yyyyMMdd"
        val formatter = DateTimeFormatter.ofPattern(pattern)

        val prefix = "JO"
        val midfix = LocalDate.now().format(formatter)
        val suffix = String.format(suffixFormat, 1)

        val latestCode = jobOrderRepository.findLatestCodeByPrefix("${prefix}-${midfix}")
        if (latestCode != null) {
            val splitLatestCode = latestCode.split("-")
            if (splitLatestCode.size > 2) {
                val latestNo = splitLatestCode[2].toInt()
                return listOf<String>(prefix, midfix, String.format(suffixFormat, latestNo + 1)).joinToString("-")
            }
        }

        return listOf<String>(prefix, midfix, suffix).joinToString("-")
    }

    open fun createJobOrder(request: CreateJobOrderRequest): MessageResponse {
        val jo = JobOrder()
        val bom = request.bomId?.let { bomService.findById(it) }
        val approver = request.approverId?.let { userService.find(it).getOrNull() } ?: SecurityUtils.getUser().getOrNull()
        val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() }
        val code = assignJobNo()
        val status = JobOrderStatus.entries.find { it.value == request.status }

        jo.apply {
            this.code = code
            this.bom = bom
            //TODO: planStart & planEnd
            planStart = request.planStart ?: LocalDateTime.now()
            planEnd = request.planEnd ?: LocalDateTime.now()
            reqQty = request.reqQty
            this.status = status
            type = request.type
            this.approver = approver
            this.prodScheduleLine = prodScheduleLine
        }

        val savedJo = jobOrderRepository.saveAndFlush(jo);

        return MessageResponse(
            id = savedJo.id,
            name = null,
            code = savedJo.code,
            type = savedJo.type,
            message = "Success",
            errorPosition = null
        )
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun updateJobOrder(request: JobOrderUpdateRequest): MessageResponse {
        val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        val newStatus: JobOrderStatus = when (request.status) { //TODO: improve if can
            "pending" -> { JobOrderStatus.PENDING }
            "processing" -> { JobOrderStatus.PROCESSING }
            "planning" -> { JobOrderStatus.PLANNING }
            "storing" -> { JobOrderStatus.STORING }
            "completed" -> { JobOrderStatus.COMPLETED }
            "packaging" -> { JobOrderStatus.PACKAGING }
            else -> { throw BadRequestException() }
        }

        jo.apply {
            status = newStatus
        }
        val savedJo = jobOrderRepository.save(jo)

        return MessageResponse(
            id = savedJo.id,
            code = savedJo.code,
            name = savedJo.bom?.name,
            type = null,
            message = "Job Order status updated to " + savedJo.status,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse {
        val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        jo.apply {
            status = JobOrderStatus.PENDING
        }
        jobOrderRepository.save(jo)

        val pols = jo.jobms.map {
            SavePickOrderLineRequest(
                itemId = it.item?.id,
                qty = it.reqQty ?: BigDecimal.ZERO,
                uomId = it.uom?.id,
            )
        }
        val po = SavePickOrderRequest(
            joId = jo.id,
            type = PickOrderType.JOB_ORDER,
            targetDate = jo.planStart?.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)

            // ✅ 添加 suggested pick lots 创建逻辑
            val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!)
            if (lines.isNotEmpty()) {
                val suggestions = suggestedPickLotService.suggestionForPickOrderLines(
                    SuggestedPickLotForPolRequest(pickOrderLines = lines)
                )

                val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)

                // ✅ 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 = SecurityUtils.getUser().getOrNull()?.id
                }
                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)
                            }
                        }
                    }
                }
            }
        }
        val itemIds = pols.mapNotNull { it.itemId }
        if (itemIds.isNotEmpty()) {
            joPickOrderService.createJoPickOrdersForPickOrder(createdPickOrder.id!!, itemIds)
            joPickOrderService.createJoPickOrderRecordsForPickOrder(createdPickOrder.id!!, itemIds)
        }


        return MessageResponse(
            id = jo.id,
            code = jo.code,
            name = jo.bom?.name,
            type = null,
            message = null,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }


    @Transactional(rollbackFor = [Exception::class])
    open fun startJobOrder(request: JobOrderCommonActionRequest): MessageResponse {
        val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
        jo.apply {
            status = JobOrderStatus.PROCESSING
        }
        jobOrderRepository.save(jo)

        return MessageResponse(
            id = jo.id,
            code = jo.code,
            name = jo.bom?.name,
            type = null,
            message = null,
            errorPosition = null,
            entity = mapOf("status" to jo.status?.value)
        )
    }

    //Pick Record
    @Throws(IOException::class)
    @Transactional
    open fun exportPickRecord(request: ExportPickRecordRequest): Map<String, Any> {
        val PICKRECORD_PDF = "PickRecord/PickRecordPDF.jrxml"
        val resource = ClassPathResource(PICKRECORD_PDF)
        if(!resource.exists()){
            throw FileNotFoundException("Report file not found: $PICKRECORD_PDF")
        }
        val inputStream = resource.inputStream
        val pickRecord = JasperCompileManager.compileReport(inputStream)
        val pickRecordInfo = joPickOrderService.getCompletedJobOrderPickOrderLotDetails(request.pickOrderIds).toMutableList()


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

        val sortedPickRecordInfo = pickRecordInfo.sortedBy { info ->
            (info["routerIndex"] as? Int) ?: Int.MAX_VALUE
        }

        sortedPickRecordInfo.forEachIndexed { index, info ->
            val field = mutableMapOf<String, Any>()

            val itemName = info["itemName"] as? String?: "N/A"
            val itemCode = info["itemCode"] as? String?: "N/A"
            val lotNo = info["lotNo"] as? String?: "N/A"
            val uomShortDesc = info["uomShortDesc"] as? String?: "N/A"
            val uomDesc = info["uomDesc"] as? String?: "N/A"
            val route = info["routerRoute"] as? String?: "N/A"
            val actualPickQty = info["actualPickQty"] as? BigDecimal?: "N/A"

            field["sequenceNumber"] = (index + 1).toString()
            field["itemName"] = itemName
            field["itemNo"] = itemCode
            field["lotNo"] = lotNo
            field["shortName"] = uomShortDesc
            field["uom"] = uomDesc
            field["route"] = route
            field["qty"] = actualPickQty.toString()

            fields.add(field)
        }

        params["JobOrderCode"] = pickRecordInfo.firstOrNull()?.get("jobOrderCode") as? String ?: "N/A"
        params["ProductionDate"] = pickRecordInfo.firstOrNull()?.get("jobOrderPlanStart") as? String ?: "N/A"
        val reqQty = pickRecordInfo.firstOrNull()?.get("jobOrderReqQty") as? BigDecimal
        params["RequiredQuantity"] = reqQty?.toString() ?: "N/A"
        params["FGCode"] = pickRecordInfo.firstOrNull()?.get("fgCode") as? String ?: "N/A"
        params["FGName"] = pickRecordInfo.firstOrNull()?.get("fgName") as? String ?: "N/A"

        /*// Debug UOM information
        val bomItemUomIdRaw = pickRecordInfo.firstOrNull()?.get("bomItemUomId")
        val bomItemId = pickRecordInfo.firstOrNull()?.get("bomItemId")
        val uomCode = pickRecordInfo.firstOrNull()?.get("uomCode") as? String
        val uomDesc = pickRecordInfo.firstOrNull()?.get("uomDesc") as? String
        val uomShortDesc = pickRecordInfo.firstOrNull()?.get("uomShortDesc") as? String
        val unit = pickRecordInfo.firstOrNull()?.get("unit") as? String

        println("Debug - UOM Info:")
        println("  bomItemId: $bomItemId")
        println("  bomItemUomId: $bomItemUomIdRaw")
        println("  uomCode: $uomCode")
        println("  uomDesc: $uomDesc")
        println("  uomShortDesc: $uomShortDesc")
        println("  unit (from BOM): $unit")*/

        params["unit"] = pickRecordInfo.firstOrNull()?.get("uomConversionDesc") as? String ?: "N/A"

        val pickOrderCode = pickRecordInfo.firstOrNull()?.get("pickOrderCode") as? String ?: "unknown"
        return mapOf(
            "report" to PdfUtils.fillReport(pickRecord, fields, params),
            "filename" to pickOrderCode
        )
    }

    @Transactional
    open fun printPickRecord(request: PrintPickRecordRequest){
        //val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer")

        val pdf = exportPickRecord(
            ExportPickRecordRequest(
                pickOrderIds = request.pickOrderId
            )
        )

        val jasperPrint = pdf["report"] as JasperPrint

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

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

        } finally {
            //tempPdfFile.delete*9
        }
    }
}