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

import com.ffii.core.response.RecordsRes
import com.ffii.core.utils.PdfUtils
import com.ffii.core.utils.QrCodeUtil
import com.ffii.fpsms.modules.master.entity.ItemUomRespository
import com.ffii.fpsms.modules.master.entity.WarehouseRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo
import com.ffii.fpsms.modules.stock.entity.projection.LotLineToQrcode
import com.ffii.fpsms.modules.stock.web.InventoryLotLineController
import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineStatusRequest

import com.ffii.fpsms.modules.stock.web.model.ExportQrCodeRequest
import com.ffii.fpsms.modules.stock.web.model.SaveInventoryLotLineRequest
import com.ffii.fpsms.modules.stock.web.model.SearchInventoryLotLineInfoRequest
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.sf.jasperreports.engine.JasperCompileManager
import org.springframework.core.io.ClassPathResource
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.FileNotFoundException
import java.io.IOException
import java.math.BigDecimal
import java.time.format.DateTimeFormatter
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineQuantitiesRequest
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisRequest
import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisResponse
import com.ffii.fpsms.modules.stock.web.model.ScannedLotInfo
import com.ffii.fpsms.modules.stock.web.model.SameItemLotInfo

@Service
open class InventoryLotLineService(
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val warehouseRepository: WarehouseRepository,
    private val itemUomRespository: ItemUomRespository,
    private val stockInLineRepository: StockInLineRepository,
    private val inventoryRepository: InventoryRepository
) {
    open fun findById(id: Long): Optional<InventoryLotLine> {
        return inventoryLotLineRepository.findById(id)
    }

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

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

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

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

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

        println("status: ${request.status}")
        println("status123: ${status?.value}")

        inventoryLotLine.apply {
            this.inventoryLot = inventoryLot
            this.warehouse = warehouse
            inQty = request.inQty
            outQty = request.outQty
            holdQty = request.holdQty
            this.stockUom = stockUom
            this.status =
                when (status == InventoryLotLineStatus.AVAILABLE && qtyStatus == InventoryLotLineStatus.AVAILABLE) {
                    true -> InventoryLotLineStatus.AVAILABLE
                    else -> InventoryLotLineStatus.UNAVAILABLE
                }
            remarks = request.remarks
        }

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

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

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

        val updatedLotLine = saveInventoryLotLine(updateRequest)

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

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

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

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

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

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

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

        val qrCodeInfo = listOf(stockInLineInfo)
//        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(itemId = info.itemId, stockInLineId = 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!!
            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")
        )
        return mapOf(
            "report" to PdfUtils.fillReport(poLabel,fields, params),
            "fileName" to (qrCodeInfo[0].poCode ?: qrCodeInfo[0].lotNo ?: "N/A")
        );
    }

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

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

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

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

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

        val updatedInventoryLotLine = saveInventoryLotLine(updateRequest)

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

open fun analyzeQrCode(request: QrCodeAnalysisRequest): QrCodeAnalysisResponse {
    val stockInLine = stockInLineRepository.findById(request.stockInLineId).orElseThrow()

    // Try direct link first; fall back to first lot line in the linked inventoryLot
    val scannedInventoryLotLine =
        stockInLine.inventoryLotLine
            ?: stockInLine.inventoryLot?.inventoryLotLines?.firstOrNull()
            ?: throw IllegalStateException("No inventory lot line found for stockInLineId=${request.stockInLineId}")

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

    // Collect same-item available lots; skip the scanned one; only remainingQty > 0
    val sameItemLots = inventoryLotLineRepository
        .findAllByInventoryLotItemIdAndStatus(request.itemId, InventoryLotLineStatus.AVAILABLE)
        .asSequence()
        .filter { it.id != scannedInventoryLotLine.id }
        .mapNotNull { lotLine ->
            val lot = lotLine.inventoryLot ?: return@mapNotNull null
            val lotNo = lot.stockInLine?.lotNo ?: return@mapNotNull null
            val uomDesc = lotLine.stockUom?.uom?.udfudesc ?: return@mapNotNull null

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

            if (remainingQty > BigDecimal.ZERO)
                SameItemLotInfo(
                    lotNo = lotNo,
                    inventoryLotLineId = lotLine.id!!,
                    availableQty = remainingQty,
                    uom = uomDesc
                )
            else null
        }
        .toList()

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

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