package com.ffii.fpsms.modules.master.service

import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.web.models.*
import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.Files;
import java.nio.file.StandardCopyOption
import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository
import com.ffii.fpsms.modules.settings.entity.BomWeightingScoreRepository
import java.math.BigDecimal
import java.math.RoundingMode
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.web.models.ConvertUomByItemRequest
import com.ffii.fpsms.modules.master.web.models.ConvertUomByItemResponse


@Service
open class BomService(
    private val bomRepository: BomRepository,
    private val bomMaterialRepository: BomMaterialRepository,
    private val bomProcessRepository: BomProcessRepository,
    private val bomProcessMaterialRepository: BomProcessMaterialRepository,
    private val itemsService: ItemsService,
    private val itemsRepository: ItemsRepository,
    private val uomConversionService: UomConversionService,
    private val uomConversionRepository: UomConversionRepository,
    private val equipmentRepository: EquipmentRepository,
    private val processRepository: ProcessRepository,
    private val equipmentDetailRepository: EquipmentDetailRepository,
    private val bomWeightingScoreRepository: BomWeightingScoreRepository,
    private val itemUomService: ItemUomService,
) {
    open fun findAll(): List<Bom> {
        return bomRepository.findAll()
    }

    open fun findById(id: Long): Bom? {
        return bomRepository.findByIdAndDeletedIsFalse(id)
    }

    open fun findByM18Id(m18Id: Long): Bom? {
        return bomRepository.findByM18IdAndDeletedIsFalse(m18Id)
    }

    open fun findByItemId(itemId: Long): Bom? {
        return bomRepository.findByItemIdAndDeletedIsFalse(itemId)
    }

    open fun saveBom(request: SaveBomRequest): SaveBomResponse {

        val item = request.code.let { itemsService.findByM18BomCode(it) } ?: request.itemId?.let { itemsService.findById(it) }

        val uom = request.m18UomId?.let { uomConversionService.findByM18Id(it) }
            ?: request.uomId?.let { uomConversionService.findById(it) }

        val bom = request.m18Id?.let { findByM18Id(it) }
            ?: request.id?.let { findById(it) }
            ?: Bom()

        bom.apply {
            this.item = item
            code = request.code
            name = request.name
            description = request.description
            outputQty = request.outputQty
            outputQtyUom = request.outputQtyUom
            yield = request.yield
            this.uom = uom
            m18Id = request.m18Id ?: m18Id
            m18LastModifyDate = request.m18LastModifyDate ?: m18LastModifyDate
        }

        val response = bomRepository.saveAndFlush(bom).let {
            SaveBomResponse(
                id = it.id,
                code = it.code,
                name = it.name,
                description = it.description,
                outputQty = it.outputQty,
                outputQtyUom = it.outputQtyUom,
                yield = it.yield,
                uomName = uom?.udfudesc,
            )
        }

        return response
    }

    //////// -------------------------------- for excel import ------------------------------- /////////
    private fun saveBomEntity(req: ImportBomRequest): Bom {
        val item =  itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name)
        val uom = if (req.uomId != null) uomConversionRepository.findById(req.uomId!!).orElseThrow() else null
        val bom = bomRepository.findByCodeAndDeletedIsFalse(req.code) ?: Bom()
        bom.apply {
            this.isDark = req.isDark
            this.isFloat = req.isFloat
            this.isDense = req.isDense
            this.code = req.code
            this.scrapRate = req.scrapRate
            this.timeSequence = req.timeSequence
            this.complexity = req.complexity
            this.allergicSubstances = req.allergicSubstances
            this.name = req.name
            this.description = req.description
            this.item = item
            this.outputQty = req.outputQty
            this.outputQtyUom = req.outputQtyUom
            this.yield = req.yield
            this.uom = uom
//            this.excelUom = req.excelUom
        }
        bom.baseScore = calculateBaseScore(bom)
        val savedBom = bomRepository.saveAndFlush(bom)
//        println("saved: ${savedBom.id}")
        return savedBom
    }

    private fun saveBomMaterial(req: ImportBomMatRequest): BomMaterial {
        val bomMaterial = req.bom?.id?.let { bId -> 
            req.item?.id?.let { iId -> 
                bomMaterialRepository.findByBomIdAndItemId(bId, iId) 
            } 
        } ?: BomMaterial()

        val item = req.item
        val bomId = req.bom?.id
        val bomCode = req.bom?.code

        // ===== 新逻辑：使用 Excel column 6 的 saleQty 和 column 7 的 sales unit =====
        val excelSaleQty = req.saleQty  // Excel 第6栏的 saleQty
        val excelSalesUnitCode = req.salesUnitCode  // Excel 第7栏的 sales unit code（字符串）
        val excelSalesUnit = req.salesUnit  // Excel 第7栏查找的 uom_conversion（可能为 null）

        var saleQty: BigDecimal? = excelSaleQty
        var saleUnitId: Long? = null
        var saleUnitCode: String? = null

        var baseQty: BigDecimal? = null
        var baseUnit: Integer? = null
        var baseUnitName: String? = null

        var stockQty: BigDecimal? = null
        var stockUnit: Integer? = null
        var stockUnitName: String? = null

        if (item?.id != null) {
            val itemId = item.id!!
            try {
                // ---- 1) 获取 item 的真实 sale unit ----
                val saleItemUom = itemUomService.findSalesUnitByItemId(itemId)
                val itemSaleUnit = saleItemUom?.uom
                
                // ---- 2) 确定 sale unit（来自 Excel column 7）----
                if (excelSalesUnit != null) {
                    // Excel 找到了 uom_conversion
                    saleUnitId = excelSalesUnit.id
                    saleUnitCode = excelSalesUnit.udfudesc
                    
                    // 检查 Excel sales unit 与 item sale unit 是否匹配
                    if (itemSaleUnit != null && excelSalesUnit.id != itemSaleUnit.id) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Excel sales unit (${excelSalesUnit.code}/${excelSalesUnit.udfudesc}) does not match item sale unit (${itemSaleUnit.code}/${itemSaleUnit.udfudesc})",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnit.code
                            )
                        )
                    }
                } else {
                    // Excel 没有找到 uom_conversion，使用 Excel 数据本身
                    saleUnitCode = excelSalesUnitCode
                    
                    if (excelSalesUnitCode != null) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Excel sales unit code '$excelSalesUnitCode' not found in uom_conversion, using Excel data as-is",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnitCode
                            )
                        )
                    }
                }

                // ---- 3) 从 saleQty（sale unit）转换为 baseQty（base unit）----
                if (excelSaleQty != null && excelSalesUnit != null && excelSalesUnit.id != null) {
                    try {
                        // 先检查 item 是否有 base unit
                        val baseItemUom = itemUomService.findBaseUnitByItemId(itemId)
                        if (baseItemUom == null) {
                            bomMaterialImportIssues.add(
                                BomMaterialImportIssue(
                                    bomId = bomId,
                                    bomCode = bomCode,
                                    itemId = item.id,
                                    itemCode = item.code,
                                    itemName = item.name,
                                    reason = "Cannot convert saleQty to baseQty: item base unit not found",
                                    srcQty = excelSaleQty,
                                    srcUomCode = excelSalesUnit.code
                                )
                            )
                        } else {
                            val baseResult = itemUomService.convertUomByItem(
                                ConvertUomByItemRequest(
                                    itemId = itemId,
                                    qty = excelSaleQty,
                                    uomId = excelSalesUnit.id!!,
                                    targetUnit = "baseUnit"
                                )
                            )
                            baseQty = baseResult.newQty
                            
                            // 获取 base unit 信息
                            baseUnit = baseItemUom.uom?.id?.toInt()?.let { Integer.valueOf(it) } as? Integer
                            baseUnitName = baseItemUom.uom?.udfudesc
                            
                            // 验证转换结果
                            if (baseQty == null) {
                                bomMaterialImportIssues.add(
                                    BomMaterialImportIssue(
                                        bomId = bomId,
                                        bomCode = bomCode,
                                        itemId = item.id,
                                        itemCode = item.code,
                                        itemName = item.name,
                                        reason = "Cannot convert saleQty to baseQty: conversion returned null",
                                        srcQty = excelSaleQty,
                                        srcUomCode = excelSalesUnit.code
                                    )
                                )
                            }
                        }
                    } catch (e: IllegalArgumentException) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Cannot convert saleQty to baseQty: ${e.message ?: "IllegalArgumentException"}",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnit.code
                            )
                        )
                        println("【BOM Import Warning】bomCode=$bomCode, item=${item.code} 转 baseQty 失败: ${e.message}")
                    } catch (e: Exception) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Cannot convert saleQty to baseQty: ${e.javaClass.simpleName} - ${e.message ?: "Unknown error"}",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnit.code
                            )
                        )
                        println("【BOM Import Error】bomCode=$bomCode, item=${item.code} 转 baseQty 失败: ${e.message}")
                    }
                } else if (excelSaleQty != null && excelSalesUnit == null) {
                    bomMaterialImportIssues.add(
                        BomMaterialImportIssue(
                            bomId = bomId,
                            bomCode = bomCode,
                            itemId = item.id,
                            itemCode = item.code,
                            itemName = item.name,
                            reason = "Cannot convert saleQty to baseQty: Excel sales unit not found",
                            srcQty = excelSaleQty,
                            srcUomCode = excelSalesUnitCode
                        )
                    )
                }

                // ---- 4) 从 saleQty（sale unit）转换为 stockQty（stock unit）----
                if (excelSaleQty != null && excelSalesUnit != null && excelSalesUnit.id != null) {
                    try {
                        // 先检查 item 是否有 stock unit
                        val stockItemUom = itemUomService.findStockUnitByItemId(itemId)
                        if (stockItemUom == null) {
                            bomMaterialImportIssues.add(
                                BomMaterialImportIssue(
                                    bomId = bomId,
                                    bomCode = bomCode,
                                    itemId = item.id,
                                    itemCode = item.code,
                                    itemName = item.name,
                                    reason = "Cannot convert saleQty to stockQty: item stock unit not found",
                                    srcQty = excelSaleQty,
                                    srcUomCode = excelSalesUnit.code
                                )
                            )
                        } else {
                            val stockResult = itemUomService.convertUomByItem(
                                ConvertUomByItemRequest(
                                    itemId = itemId,
                                    qty = excelSaleQty,
                                    uomId = excelSalesUnit.id!!,
                                    targetUnit = "stockUnit"
                                )
                            )
                            stockQty = stockResult.newQty
                            
                            // 获取 stock unit 信息
                            stockUnit = stockItemUom.uom?.id?.toInt()?.let { Integer.valueOf(it) } as? Integer
                            stockUnitName = stockItemUom.uom?.udfudesc
                            
                            // 验证转换结果
                            if (stockQty == null) {
                                bomMaterialImportIssues.add(
                                    BomMaterialImportIssue(
                                        bomId = bomId,
                                        bomCode = bomCode,
                                        itemId = item.id,
                                        itemCode = item.code,
                                        itemName = item.name,
                                        reason = "Cannot convert saleQty to stockQty: conversion returned null",
                                        srcQty = excelSaleQty,
                                        srcUomCode = excelSalesUnit.code
                                    )
                                )
                            }
                        }
                    } catch (e: IllegalArgumentException) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Cannot convert saleQty to stockQty: ${e.message ?: "IllegalArgumentException"}",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnit.code
                            )
                        )
                        println("【BOM Import Warning】bomCode=$bomCode, item=${item.code} 转 stockQty 失败: ${e.message}")
                    } catch (e: Exception) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Cannot convert saleQty to stockQty: ${e.javaClass.simpleName} - ${e.message ?: "Unknown error"}",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnit.code
                            )
                        )
                        println("【BOM Import Error】bomCode=$bomCode, item=${item.code} 转 stockQty 失败: ${e.message}")
                    }
                } else if (excelSaleQty != null && excelSalesUnit == null) {
                    bomMaterialImportIssues.add(
                        BomMaterialImportIssue(
                            bomId = bomId,
                            bomCode = bomCode,
                            itemId = item.id,
                            itemCode = item.code,
                            itemName = item.name,
                            reason = "Cannot convert saleQty to stockQty: Excel sales unit not found",
                            srcQty = excelSaleQty,
                            srcUomCode = excelSalesUnitCode
                        )
                    )
                }

                // 最终检查：如果 stockQty 仍然为 null，记录问题
                if (stockQty == null && excelSaleQty != null && excelSalesUnit != null && excelSalesUnit.id != null) {
                    // 检查是否已经记录过这个问题（避免重复）
                    val alreadyReported = bomMaterialImportIssues.any { issue ->
                        issue.itemId == item.id && 
                        issue.reason.contains("stockQty", ignoreCase = true) &&
                        issue.srcQty == excelSaleQty
                    }
                    if (!alreadyReported) {
                        bomMaterialImportIssues.add(
                            BomMaterialImportIssue(
                                bomId = bomId,
                                bomCode = bomCode,
                                itemId = item.id,
                                itemCode = item.code,
                                itemName = item.name,
                                reason = "Cannot convert saleQty to stockQty: conversion failed (final check)",
                                srcQty = excelSaleQty,
                                srcUomCode = excelSalesUnit.code
                            )
                        )
                    }
                }

            } catch (e: IllegalArgumentException) {
                bomMaterialImportIssues.add(
                    BomMaterialImportIssue(
                        bomId = bomId,
                        bomCode = bomCode,
                        itemId = item.id,
                        itemCode = item.code,
                        itemName = item.name,
                        reason = e.message ?: "UOM conversion error",
                        srcQty = excelSaleQty,
                        srcUomCode = excelSalesUnitCode
                    )
                )
                println("【BOM Import Warning】bomCode=$bomCode, item=${item.code} 转 UOM 失败: ${e.message}")
            } catch (e: Exception) {
                bomMaterialImportIssues.add(
                    BomMaterialImportIssue(
                        bomId = bomId,
                        bomCode = bomCode,
                        itemId = item.id,
                        itemCode = item.code,
                        itemName = item.name,
                        reason = "Unknown error: ${e.message}",
                        srcQty = excelSaleQty,
                        srcUomCode = excelSalesUnitCode
                    )
                )
                println("【BOM Import Error】bomCode=$bomCode, item=${item.code}: ${e.message}")
            }
        } else {
            // item 缺失
            bomMaterialImportIssues.add(
                BomMaterialImportIssue(
                    bomId = bomId,
                    bomCode = bomCode,
                    itemId = item?.id,
                    itemCode = item?.code,
                    itemName = item?.name,
                    reason = "Missing item for conversion",
                    srcQty = excelSaleQty,
                    srcUomCode = excelSalesUnitCode
                )
            )
        }

        // ===== 写回 BomMaterial =====
        bomMaterial.apply {
            this.item = req.item
            this.itemName = req.item?.name
            this.isConsumable = req.isConsumable
            this.qty = req.qty                 // BOM 原始用量（column 2，保留）
            this.uom = req.uom                 // BOM 原始 UOM（column 3，保留）
            this.uomName = req.uomName

            // 新逻辑：使用 Excel column 6 的 saleQty 和 column 7 的 sales unit
            this.saleQty = saleQty
            this.salesUnit = excelSalesUnit  // 使用 Excel 的 sales unit，不是 item 的 stock unit
            this.salesUnitCode = saleUnitCode

            // 从 sale unit 转换为 base unit
            this.baseQty = baseQty
            this.baseUnit = baseUnit
            this.baseUnitName = baseUnitName

            // 从 sale unit 转换为 stock unit（新增）
            this.stockQty = stockQty
            this.stockUnit = stockUnit
            this.stockUnitName = stockUnitName

            this.bom = req.bom
        }
        return bomMaterialRepository.saveAndFlush(bomMaterial)
    }
    private fun saveBomProcess(req: ImportBomProcessRequest): BomProcess {
        val bomProcess = req.bom!!.id?.let { id -> req.seqNo?.let { seqNo -> bomProcessRepository.findBySeqNoAndBomIdAndDeletedIsFalse(seqNo.toInt(), id) } } ?: BomProcess()
        bomProcess.apply {
            this.process = req.process
            this.equipment = req.equipment
            this.description = req.description
            this.seqNo = req.seqNo
            this.durationInMinute = req.durationInMinute
            this.prepTimeInMinute = req.prepTimeInMinute
            this.postProdTimeInMinute = req.postProdTimeInMinute
            this.bom = req.bom
        }
        return bomProcessRepository.saveAndFlush(bomProcess)
    }
    fun saveBomProcessMaterial(req: ImportBomProcessMaterialRequest): BomProcessMaterial {
        val bomProcessMaterial = req.bomProcess?.id?.let { pid -> req.bomMaterial?.id?.let { mid -> bomProcessMaterialRepository.findByBomProcessIdAndBomMaterialId(pid, mid)} } ?: BomProcessMaterial()
        bomProcessMaterial.apply {
            this.bomProcess = req.bomProcess
            this.bomMaterial = req.bomMaterial
        }
        return bomProcessMaterialRepository.saveAndFlush(bomProcessMaterial)
    }
    private fun importExcelBomMaterial(bom: Bom, sheet: Sheet) {
        var bomProcessMatRequest = ImportBomProcessMaterialRequest()
        var startRowIndex = 10
        val endRowIndex = 30
        var startColumnIndex = 0
        val endColumnIndex = 10
        while (startRowIndex < endRowIndex) {
            val tempRow = sheet.getRow(startRowIndex)
            val tempCell = tempRow.getCell(startColumnIndex)
            if (tempCell != null && tempCell.cellType == CellType.STRING && tempCell.stringCellValue.trim() == "材料編號") {
                println("last: $startRowIndex")
                startRowIndex++
                break
            }
            startRowIndex++
        }
        var bomMatRequest = ImportBomMatRequest(
            bom = bom
        )
        println("starting new loop")
        while (startRowIndex != endRowIndex || startColumnIndex != endColumnIndex) {
            val tempRow = sheet.getRow(startRowIndex)
            val tempCell = tempRow.getCell(startColumnIndex)
            if (startColumnIndex == 0 && (tempCell == null || tempCell.cellType == CellType.BLANK)) {
                break
            } else {
                try {
                    when (startColumnIndex) {
                        0 -> {
//                            println("rowIndex: $startRowIndex")
                            val nameRow = sheet.getRow(startRowIndex)
                            val nameCell = nameRow.getCell(1)
                            println(tempCell.stringCellValue.trim())
                            val item = itemsRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim())
                                ?: itemsRepository.findByNameAndDeletedFalse(nameCell.stringCellValue.trim())
//                            println("getting item.....:")
//                            println(item)
                            bomMatRequest.item = item
                        }
                        2-> {
                            bomMatRequest.qty = tempCell.numericCellValue.toBigDecimal()
                        }
                        3 -> {
                            val uom = uomConversionRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim())
                            bomMatRequest.uom = uom
                            bomMatRequest.uomName = uom?.udfudesc
                            
                        }
                        
                        6 -> {
                            bomMatRequest.saleQty = tempCell.numericCellValue.toBigDecimal()
                        }
                        
                        7 -> {
                            val salesUnitCodeStr = tempCell.stringCellValue.trim()
                            val normalizedCode = if (salesUnitCodeStr.equals("Litter", ignoreCase = true)) "L" else salesUnitCodeStr
                            val salesUnit = uomConversionRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim())
                            
                            bomMatRequest.salesUnit = salesUnit
                           // bomMatRequest.salesUnitCode = salesUnit?.udfudesc
                            bomMatRequest.salesUnitCode = salesUnitCodeStr
                        }
                         
                      
                        /* 
                        2 -> {
                            val salesUnit = uomConversionRepository.findByCodeAndDeletedFalse(tempCell.stringCellValue.trim())
                            bomMatRequest.salesUnit = salesUnit
                        }*/

                        10 -> {
                            println("seqNo: ${tempCell.numericCellValue.toInt()}")
                            println("bomId: ${bom.id!!}")
                            val bomProcess = bomProcessRepository.findBySeqNoAndBomIdAndDeletedIsFalse(
                                seqNo = tempCell.numericCellValue.toInt(),
                                bomId = bom.id!!
                            )!! // if null = bugged
                            bomProcessMatRequest.bomProcess = bomProcess
                        }
                    }

                    println("startRowIndex: $startRowIndex")
                    println("endRowIndex: $endRowIndex")

//                    println("first condition: ${startColumnIndex < endColumnIndex}")
//                    println("second condition: ${startRowIndex < endRowIndex}")
                    if (startColumnIndex < endColumnIndex) {
                        startColumnIndex++
                    } else if (startRowIndex < endRowIndex) {
                        startRowIndex++
                        // do save
                        println("req:")
                        println(bomMatRequest)
                        val bomMaterial = saveBomMaterial(bomMatRequest)
                        bomProcessMatRequest.bomMaterial = bomMaterial
                        val bomProcessMaterial = saveBomProcessMaterial(bomProcessMatRequest)
                        // clean up
                        startColumnIndex = 0
                        bomMatRequest = ImportBomMatRequest(
                            bom = bom
                        )
                        println("saved: $bomMatRequest")
                    }
                } catch(e: Error) {
                    println("DEBUG ERROR:")
                    println(e)
                }
            }
        }
    }

    private fun importExcelBomBasicInfo(sheet: Sheet): Bom {
        var request = ImportBomRequest(
            code = "",
            name = "",
            description = "",
        )
        var startRowIndex = 0
        val endRowIndex = 10
        var startColumnIndex = 0
        val endColumnIndex = 9
        while (startRowIndex != endRowIndex || startColumnIndex != endColumnIndex) {
            val tempRow = sheet.getRow(startRowIndex)
            val tempCell = tempRow.getCell(startColumnIndex)
            if (tempCell != null && tempCell.cellType == CellType.STRING) {
                val tempCellVal = tempCell.stringCellValue.trim()
                // --------------------- assigning value that is under a header to request ------------------------ //
                val topTargetValueRow = sheet.getRow(startRowIndex + 1)
                val topTargetValueCell = topTargetValueRow.getCell(startColumnIndex)
                when (tempCellVal) {
                   "編號" -> {
                       request.code = topTargetValueCell.stringCellValue.trim()
                   }
                   "產品名稱" ->  request.name = topTargetValueCell.stringCellValue.trim()
                   "種類" ->  request.description = topTargetValueCell.stringCellValue.trim()
                   "份量 (Qty)" ->  request.outputQty = topTargetValueCell.numericCellValue.toBigDecimal()
                   "單位" ->  request.outputQtyUom = topTargetValueCell.stringCellValue.trim()
                }
                // ----------------------------------------------------------------------------------------------- //
                // --------------------- assigning value that is next to a header to request ------------------------ //
                fun calculateColourScore(value: String): Int {
                    var score = -1
                    when (value) {
                        "淺" -> score = 0
                        "深" -> score = 1
                    }
                    return score
                }
                fun calculateFloatScore(value: String): Int {
                    var score = -1
                    when (value) {
                        "沉" -> score = 0
                        "浮" -> score = 1
                    }
                    return score
                }

                fun calculateAllergicSubstancesScore(value: String): Int {
                    var score = -1
                    when (value) {
                        "N/A" -> score = 0
                        "0" -> score = 0
                        "Yes" -> score = 5
                        "1" -> score = 5
                        " " -> score = 0
                        "" -> score = 0
                    }
                    return score
                }
                val leftTargetValueRow = sheet.getRow(startRowIndex)
                val leftTargetValueCell = leftTargetValueRow.getCell(startColumnIndex + 1)

                fun getCellValueAsString(cell: org.apache.poi.ss.usermodel.Cell?): String {
                    return when {
                        cell == null -> ""
                        cell.cellType == CellType.STRING -> cell.stringCellValue.trim()
                        cell.cellType == CellType.NUMERIC -> cell.numericCellValue.toString().trim()
                        cell.cellType == CellType.BLANK -> ""
                        else -> ""
                    }
                }

                when {
                        //use fix row column by index not by search value contain later
                    tempCellVal.contains("深淺") ->  request.isDark = if (leftTargetValueCell?.cellType == CellType.NUMERIC)
                        leftTargetValueCell.numericCellValue.toInt()
                    else 0
                    tempCellVal.contains("浮沉") ->  request.isFloat = if (leftTargetValueCell?.cellType == CellType.NUMERIC)
                        leftTargetValueCell.numericCellValue.toInt()
                    else 0
                    tempCellVal.contains("過敏原 （如有）") ->  request.allergicSubstances = calculateAllergicSubstancesScore(getCellValueAsString(leftTargetValueCell))
                    tempCellVal.contains("濃淡") ->  request.isDense = if (leftTargetValueCell?.cellType == CellType.NUMERIC)
                        leftTargetValueCell.numericCellValue.toInt()
                    else 0
                    tempCellVal.contains("損耗率") ->  request.scrapRate = if (leftTargetValueCell?.cellType == CellType.NUMERIC)
                        leftTargetValueCell.numericCellValue.toInt()
                    else 0
                    tempCellVal.contains("生產先後次序") ->  request.timeSequence = if (leftTargetValueCell?.cellType == CellType.NUMERIC)
                        leftTargetValueCell.numericCellValue.toInt()
                    else 0
                    tempCellVal.contains("複雜度") ->  request.complexity = if (leftTargetValueCell?.cellType == CellType.NUMERIC)
                        leftTargetValueCell.numericCellValue.toInt()
                    else 0
                }
            }
        
            if (startRowIndex < endRowIndex) {
                startRowIndex++
            } else if (startColumnIndex < endColumnIndex) {
                startColumnIndex++
                startRowIndex = 0
            }
        }
        return saveBomEntity(request)
    }

    private fun generateNextTMPEquipmentCode(): String {
        // 查询所有未删除的 EquipmentDetail
        val allEquipmentDetails = equipmentDetailRepository.findAllByDeletedFalse()
        
        // 过滤出以 "TMP" 开头的 equipmentCode，并提取数字部分
        val tmpNumbers = allEquipmentDetails
            .mapNotNull { it.equipmentCode }
            .filter { it.startsWith("TMP") }
            .mapNotNull { code ->
                // 提取 TMP 后面的数字部分（例如：TMP01 -> 1, TMP02 -> 2）
                val numberPart = code.substring(3).trim()
                numberPart.toIntOrNull()
            }
        
        // 找到最大编号，如果没有则从 0 开始
        val maxNumber = tmpNumbers.maxOrNull() ?: 0
        
        // 递增并格式化为两位数（TMP01, TMP02, ...）
        val nextNumber = maxNumber + 1
        return "TMP${nextNumber.toString().padStart(2, '0')}"
    }
    private fun bomGetOrCreateEquipment(equipmentName: String): Equipment {
        var equipment = equipmentRepository.findByNameAndDeletedIsFalse(equipmentName)
            ?: equipmentRepository.findByCodeAndDeletedIsFalse(equipmentName)
    
        var equipment_detail = equipmentDetailRepository.findByNameAndDeletedIsFalse(equipmentName)
        if (equipment == null) {
            // 解析 XXX-YYY 格式
            val parts = equipmentName.split("-")
            val firstPart = if (parts.size > 0) parts[0] else equipmentName  // XXX
            val secondPart = if (parts.size > 1) parts[1] else ""   // YYY
            
            // 创建 equipment
            equipment = Equipment().apply {
                this.code = equipmentName  // 完整值 XXX-YYY
                this.name = secondPart
                this.description = firstPart  // XXX 写入 description
            }
            equipment = equipmentRepository.saveAndFlush(equipment)
            
            // 创建 equipment_detail
            if (equipment_detail == null) {
                val nextEquipmentCode = generateNextTMPEquipmentCode()
                equipment_detail = EquipmentDetail().apply {
                    this.name = "1號"  // 默认值
                    this.description = equipmentName  // equipmentName (完整值 XXX-YYY)
                    this.code = "${this.description}-$name" 
                    this.equipmentCode=nextEquipmentCode
                }
                equipment_detail = equipmentDetailRepository.saveAndFlush(equipment_detail)
            }
        }
        return equipment!!
    }

    private fun bomGetOrCreateProcess(name: String): Process {
        var process = processRepository.findByNameAndDeletedIsFalse(name)
            ?: processRepository.findByCodeAndDeletedIsFalse(name)
        if (process == null) {
            val truncatedName = name.take(30)
            process = Process().apply {
                this.name = truncatedName
                this.code = truncatedName
                this.description = name
            }
            process = processRepository.saveAndFlush(process)
        }
        return process!!
    }

    private fun extractDurationStringToMinutes(str: String): Int {
        val regex = """(\d+)(\D+)""".toRegex()
        val matchResult = regex.find(str)
//        fun returnMultiplier(unit: String): {
//
//        }
        val (number, unit) = matchResult?.let {
            val num1 = it.groupValues[1].toInt()
            val unit1 = it.groupValues[2]
//            val num2 = it.groupValues[3].toInt()
//            val unit2 = it.groupValues[4]
            Pair(num1, unit1)
        }!!
        var multiplier = 1
        when {
            unit.contains("min") -> {
                multiplier = 1
            }
            unit.contains("hr") -> {
                multiplier = 60
            }
        }
        return number * multiplier
    }
    private fun importExcelBomProcess(bom: Bom, sheet: Sheet) {
        var startRowIndex = 30
        var endRowIndex = 70
        var startColumnIndex = 0
        val endColumnIndex = 11
        while (startRowIndex < endRowIndex) {
            val tempRow = sheet.getRow(startRowIndex) ?: throw Exception()
            val tempCell = tempRow.getCell(startColumnIndex)
            if (tempCell != null && tempCell.cellType == CellType.STRING && tempCell.stringCellValue.trim() == "工序") {
                startRowIndex += 2 // skip column header
//                println("last: $startRowIndex")
                break
            }
            startRowIndex++
        }
        var searchRowIndex = startRowIndex
        val maxSearchRow = 50 
        while (searchRowIndex < maxSearchRow) {
            val tempRow = sheet.getRow(searchRowIndex)
            val tempCell = tempRow?.getCell(0)
            if (tempCell == null || tempCell.cellType == CellType.BLANK) {
                endRowIndex = searchRowIndex
                break
            }
            searchRowIndex++
        }
        var bomProcessRequest = ImportBomProcessRequest(
            bom = bom
        )
        while (startRowIndex != endRowIndex || startColumnIndex != endColumnIndex) {
            val tempRow = sheet.getRow(startRowIndex)
            val tempCell = tempRow.getCell(startColumnIndex)
//            val checkCell = tempRow.getCell(0)
            if (startColumnIndex == 0 && (tempCell == null || tempCell.cellType == CellType.BLANK)) {
//                println("hi")
                break
            } else {
//                println(startRowIndex)
//                println(startColumnIndex)
//                println(tempCell.cellType)
//                println(tempCell.toString())
                try {
                    when (startColumnIndex) {
                        0 -> {
//                            println("startRowIndex: $startRowIndex")
                            bomProcessRequest.seqNo = tempCell.numericCellValue.toLong()
//                            println("seqNo: ${tempCell.numericCellValue.toLong()}")
//                            println("bomProcessRequest: $bomProcessRequest")
                        }

                        1 -> {
                            val processName = tempCell.stringCellValue.trim()
                            val process = bomGetOrCreateProcess(processName)
                            bomProcessRequest.process = process
//                            println("process created")
                        }
                        2 -> {
                            val description = tempCell.stringCellValue.trim()
                            bomProcessRequest.description = description
                        }
                        3 -> {
                            val equipmentName = tempCell.stringCellValue.trim()
                            if (equipmentName != "不適用") {
                                val equipment = bomGetOrCreateEquipment(equipmentName)
//                                println("equipment created")
                                bomProcessRequest.equipment = equipment
                            }
                        }
                        5 -> { //duration
                            when (tempCell.cellType) {
                                CellType.NUMERIC -> {
                                    bomProcessRequest.durationInMinute = tempCell.numericCellValue.toInt()
                                }
                                CellType.STRING -> {
                                    val durationString = tempCell.stringCellValue.trim()
                                    bomProcessRequest.durationInMinute = extractDurationStringToMinutes(durationString)
                                }

                                else -> {}
                            }

                        }
                        /*
                        5 -> { //processOutputQty
                            val processOutputQty = tempCell.stringCellValue.trim()
                            bomProcessRequest.processOutputQty = processOutputQty

                        }
                        6 -> { //processOutputUnit
                            val processOutputUnit = tempCell.stringCellValue.trim()
                            bomProcessRequest.processOutputUnit = processOutputUnit

                        }
                        7 -> { //byProductName
                            val byProductQty = tempCell.stringCellValue.trim()
                            bomProcessRequest.byProductQty = byProductQty
                        }
                        8 -> { //byProductQty
                            val byProductUnit = tempCell.stringCellValue.trim()
                            bomProcessRequest.byProductUnit = byProductUnit
                        }

                        9 -> { //byProductUnit
                            val byProductUnit = tempCell.stringCellValue.trim()
                            bomProcessRequest.byProductUnit = byProductUnit
                        }
                    */
                        9 -> { //scrapRate
                           // val scrapRate = tempCell.stringCellValue.trim()
                           // bomProcessRequest.scrapRate = scrapRate
                        }
                        10 -> { //prepTimeInMinute
                            when (tempCell.cellType) {
                                CellType.NUMERIC -> {
                                    bomProcessRequest.prepTimeInMinute = tempCell.numericCellValue.toInt()
                                }
                                CellType.STRING -> {
                                    val prepTimeInMinuteString = tempCell.stringCellValue.trim()
                                    bomProcessRequest.prepTimeInMinute = extractDurationStringToMinutes(prepTimeInMinuteString)
                                }

                                else -> {}
                            }
                        }
                        11 -> { //postProdTimeInMinute
                            when (tempCell.cellType) {
                                CellType.NUMERIC -> {
                                    bomProcessRequest.postProdTimeInMinute = tempCell.numericCellValue.toInt()
                                }
                                CellType.STRING -> {
                                    val postProdTimeInMinuteString = tempCell.stringCellValue.trim()
                                    bomProcessRequest.postProdTimeInMinute = extractDurationStringToMinutes(postProdTimeInMinuteString)
                                }
                                else -> {}
                            }
                        }
                    }
//                    println("req:")
//                    println(bomProcessRequest)
                    // moving the loop
                    if (startColumnIndex < endColumnIndex) {
//                        println("case1")
                        startColumnIndex++
//                        println("next col startColumnIndex: $startColumnIndex")
                    } else if (startRowIndex < endRowIndex) {
//                        println("case2")
                        // when doesn't meet first condition, falls to this and go new row
                        startRowIndex++
//                        println("next row startRowIndex: $startRowIndex")
                        // do save
                        saveBomProcess(bomProcessRequest)
                        // clean up
                        startColumnIndex = 0
                        bomProcessRequest = ImportBomProcessRequest(
                            bom = bom
                        )
                    }
                } catch (e: Error) {
//                    throw e
                }
            }
        }
    }

    /**
     * Calculates baseScore from BOM basic info using bom_weighting_score (code = BOM column name).
     * For isDark, isFloat, isDense, allergicSubstances, timeSequence, complexity: (extractedScore / range) * weighting * 100.
     * For equipmentConflict: weighting * 100 only.
     */
    private fun calculateBaseScore(bom: Bom): BigDecimal {
        val scale = 2
        val roundingMode = RoundingMode.HALF_UP
        var sum = BigDecimal.ZERO.setScale(scale, roundingMode)

        // Score columns: contribution = (extractedScore / range) * weighting * 100
        val scoreColumns = listOf(
            "isDark" to (bom.isDark?.toBigDecimal() ?: BigDecimal.ZERO),
            "isFloat" to (bom.isFloat?.toBigDecimal() ?: BigDecimal.ZERO),
            "isDense" to (bom.isDense?.toBigDecimal() ?: BigDecimal.ZERO),
            "allergicSubstances" to (bom.allergicSubstances?.toBigDecimal() ?: BigDecimal.ZERO),
            "timeSequence" to (bom.timeSequence?.toBigDecimal() ?: BigDecimal.ZERO),
            "complexity" to (bom.complexity?.toBigDecimal() ?: BigDecimal.ZERO),
        )
        for ((code, extractedScore) in scoreColumns) {
            val row = bomWeightingScoreRepository.findByCodeAndDeletedFalse(code) ?: continue
            val range = (row.range ?: 1).toBigDecimal()
            val weighting = row.weighting ?: continue
            if (range.compareTo(BigDecimal.ZERO) == 0) continue
            val contribution = extractedScore
                .divide(range, scale, roundingMode)
                .multiply(weighting)
                .multiply(BigDecimal(100))
                .setScale(scale, roundingMode)
            sum = sum.add(contribution)
        }

        // equipmentConflict: contribution = weighting * 100 only
        val equipmentConflictRow = bomWeightingScoreRepository.findByCodeAndDeletedFalse("equipmentConflict")
        if (equipmentConflictRow?.weighting != null) {
            val contribution = equipmentConflictRow.weighting!!
                .multiply(BigDecimal(100))
                .setScale(scale, roundingMode)
            sum = sum.add(contribution)
        }

        return sum.setScale(scale, roundingMode)
    }
    open fun exportProblematicBOM() {
        val resolver = PathMatchingResourcePatternResolver()
        val successExcel = resolver.getResources("file:C:/Users/2Fi/Desktop/fail/*.xlsx")
        val failedExcels = resolver.getResources("file:C:/Users/2Fi/Desktop/fail/*.xlsx")



    }
    open fun importBOM(): ByteArray {
        bomMaterialImportIssues.clear()  // 清空问题列表
        
        val resolver = PathMatchingResourcePatternResolver()
        val excels = resolver.getResources("file:C:/Users/kw093/Downloads/bom/bom/*.xlsx")
        println("size: ${excels.size}")
        val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx")
        val templateInputStream = logExcel.inputStream
        val workbook: Workbook = XSSFWorkbook(templateInputStream)
        val logSheet: Sheet = workbook.getSheetAt(0)
        var rowIndex = 0
    
        for (resource in excels) {
            val filename = resource.filename ?: "unknown"
         
            
            try {
                //get sheet
                println("处理文件: $filename")
                val templateInputStream2 = resource.inputStream
                val workbook2: Workbook = XSSFWorkbook(templateInputStream2)
                val sheet: Sheet = workbook2.getSheetAt(0)
                val bom = importExcelBomBasicInfo(sheet) // updating bom table
                //  import bom process / create new process
                importExcelBomProcess(bom, sheet)
                // import bom material
                importExcelBomMaterial(bom, sheet)
                workbook2.close()
                println("成功处理文件: $filename")
            } catch (e: org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException) {
                println("错误：文件 '$filename' 不是有效的 Excel 文件，跳过")
                println("  错误信息: ${e.message}")
            } catch (e: java.util.zip.ZipException) {
                println("错误：文件 '$filename' 格式错误（可能是临时文件或损坏），跳过")
                println("  错误信息: ${e.message}")
            } catch (e: Exception) {
                println("错误：处理文件 '$filename' 时发生异常")
                println("  错误类型: ${e.javaClass.simpleName}")
                println("  错误信息: ${e.message}")
                e.printStackTrace()
            }
        }
        
        printBomMaterialImportIssues()  // 打印问题汇总
        
        val outputStream = ByteArrayOutputStream()
        workbook.write(outputStream)
        workbook.close()
        return outputStream.toByteArray()
    }
    private fun printBomMaterialImportIssues() {
        println("\n========== BOM Material UOM Conversion Issues ==========")
        println("Total issues: ${bomMaterialImportIssues.size}")
        if (bomMaterialImportIssues.isEmpty()) {
            println("No issues.")
            println("========================================================\n")
            return
        }

        // 按 BOM 分组统计
        val issuesByBom = bomMaterialImportIssues.groupBy { 
            it.bomCode?.takeIf { it.isNotEmpty() } ?: "BOM-id-${it.bomId}" 
        }
        
        println("\n按 BOM 分组统计:")
        issuesByBom.forEach { (bomKey, issues) ->
            println("  $bomKey: ${issues.size} 个问题")
        }
        
        println("\n详细问题列表:")
        bomMaterialImportIssues
            .sortedWith(compareBy({ it.bomCode ?: "BOM-id-${it.bomId}" }, { it.itemCode }))
            .forEach { issue ->
                val bomIdentifier = issue.bomCode?.takeIf { it.isNotEmpty() } 
                    ?: "BOM-id-${issue.bomId}"
                println(
                    "BOM=$bomIdentifier, " +
                            "Item=${issue.itemCode ?: "null"} (${issue.itemName ?: "null"}), " +
                            "src=${issue.srcQty} ${issue.srcUomCode} -> reason=${issue.reason}"
                )
            }

        println("========================================================\n")
    }
}