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

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 com.ffii.fpsms.modules.stock.web.model.ExportQrCodeRequest
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest
import com.ffii.fpsms.modules.stock.web.model.SaveStockInRequest
import com.ffii.fpsms.modules.stock.web.model.StockInLineStatus
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.fpsms.modules.master.entity.WarehouseRepository
import java.io.FileNotFoundException
import java.time.format.DateTimeFormatter


@Service
open class StockInLineService(
    private val jdbcDao: JdbcDao,
    private val polRepository: PurchaseOrderLineRepository,
    private val qcItemsRepository: QcItemRepository,
    private val qcResultRepository: QcResultRepository,
    private val stockInService: StockInService,
    private val stockInRepository: StockInRepository,
    private val stockInLineRepository: StockInLineRepository,
    private val inventoryLotRepository: InventoryLotRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository,
    private val itemRepository: ItemsRepository,
    private val warehouseRepository: WarehouseRepository,
    ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) {

    @Throws(IOException::class)
    @Transactional
    open fun create(request: SaveStockInLineRequest): MessageResponse {
        val stockInLine = StockInLine()
        val item = itemRepository.findById(request.itemId).orElseThrow()
        val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow()
        val stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId)
            ?: stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn
        stockInLine.apply {
            this.item = item
            itemNo = item.code
            this.purchaseOrderLine = purchaseOrderLine
            this.stockIn = stockIn
            acceptedQty = request.acceptedQty
            status = StockInLineStatus.PENDING.status
        }
        val savedInLine = saveAndFlush(stockInLine)
        val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedInLine.id!!)
        return MessageResponse(
            id = savedInLine.id,
            code = savedInLine.itemNo,
            name = savedInLine.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 = "POLOT", itemId = stockInLine.item!!.id!!, count = inventoryCount)
        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): InventoryLotLine {
        val inventoryLotLine = InventoryLotLine()
        val warehouse = warehouseRepository.findById(request.warehouseId!!).orElseThrow()
        inventoryLotLine.apply {
            this.inventoryLot = stockInLine.inventoryLot
            this.warehouse = warehouse
            this.inQty = request.acceptedQty
            this.status = "available"
        }
        val savedInventoryLotLine = inventoryLotLineRepository.saveAndFlush(inventoryLotLine)
        return savedInventoryLotLine
    }

    @Throws(IOException::class)
    @Transactional
    fun saveQcResultWhenStockIn(request: SaveStockInLineRequest, stockInLine: StockInLine): 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()
                QcResult().apply {
                    this.qcItem = qcItem
                    this.item = item
                    this.stockInLine = stockInLine
                    this.failQty = it.failQty
                    this.type = "qc" // default as qc for now
                    this.remarks = it.remarks
                }
            }
            return qcResultRepository.saveAllAndFlush(qcResultEntries)
        }
        return null
    }
    @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,
            )
        if (stockInLine.expiryDate != null && request.expiryDate == null) {
            request.apply {
                expiryDate = stockInLine.expiryDate
            }
        }
        // return list of stock in line, update data grid with the list
        if (request.acceptedQty.compareTo(stockInLine.acceptedQty) == 0) {
            var savedInventoryLot: InventoryLot? = null
            var savedInventoryLotLine: InventoryLotLine? = null // maybe remove this later
            saveQcResultWhenStockIn(request, stockInLine)
            if (request.status == StockInLineStatus.RECEIVED.status) {
                savedInventoryLot = saveInventoryLotWhenStockIn(request = request, stockInLine = stockInLine)
            }
            if (request.status == StockInLineStatus.COMPLETE.status) {
                savedInventoryLotLine = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
            }
            stockInLine.apply {
//                user = null
                this.productionDate = request.productionDate?.atStartOfDay() // maybe need to change the request to LocalDateTime
                this.productLotNo = request.productLotNo
                this.receiptDate = request.receiptDate?.atStartOfDay()
                this.status = request.status
                this.expiryDate = stockInLine.expiryDate ?: request.expiryDate
                this.inventoryLot = stockInLine.inventoryLot ?: savedInventoryLot
                this.lotNo = stockInLine.lotNo ?: savedInventoryLot?.lotNo
                this.lotNo = savedInventoryLot?.lotNo
            }
            val savedStockInLine = saveAndFlush(stockInLine)
            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)
            )
        } else {
            if (request.acceptedQty <= BigDecimal(0) ) {
                return MessageResponse(
                    id = null,
                    code = null,
                    name = null,
                    type = "acceptedQty == 0",
                    message = "acceptedQty cannot be 0",
                    errorPosition = "request.acceptedQty",
                )
            }
            val newStockInLine = StockInLine()
            newStockInLine.apply {
                this.item = stockInLine.item
                this.itemNo = stockInLine.itemNo
                this.purchaseOrderLine = stockInLine.purchaseOrderLine
                this.stockIn = stockInLine.stockIn
                this.demandQty = stockInLine.demandQty
                this.acceptedQty = stockInLine.acceptedQty!!.minus(request.acceptedQty)
                this.price = stockInLine.price
                this.priceUnit = stockInLine.priceUnit
                this.inventoryLot = stockInLine.inventoryLot
                this.lotNo = stockInLine.lotNo
                this.productionDate = stockInLine.productionDate
                this.expiryDate = stockInLine.expiryDate
                this.status = stockInLine.status // this does update status
                this.user = stockInLine.user
            }
            saveQcResultWhenStockIn(request, stockInLine)
            var savedInventoryLot: InventoryLot? = null
            var savedInventoryLotLine: InventoryLotLine? = null // maybe remove this later
            if (request.status == StockInLineStatus.RECEIVED.status) {
                savedInventoryLot = saveInventoryLotWhenStockIn(request = request, stockInLine = stockInLine)
            }
            if (request.status == StockInLineStatus.COMPLETE.status) {
                savedInventoryLotLine = saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine)
            }
            stockInLine.apply {
                this.receiptDate = request.receiptDate?.atStartOfDay()
                this.productionDate = request.productionDate?.atStartOfDay()
                this.acceptedQty = request.acceptedQty
                this.status = request.status
                this.inventoryLot = savedInventoryLot ?: stockInLine.inventoryLot
                this.lotNo = savedInventoryLot?.lotNo ?: stockInLine.lotNo
                this.expiryDate = stockInLine.expiryDate ?: request.expiryDate
                this.productLotNo = request.productLotNo
            }

            val stockInLineEntries = listOf(stockInLine, newStockInLine)
            val savedEntries = stockInLineRepository.saveAllAndFlush(stockInLineEntries)
            val ids = savedEntries.map { it.id!! }
            val lineInfoList = stockInLineRepository.findStockInLineInfoByIdInAndDeletedFalse(ids)

            return MessageResponse(
                id = stockInLine.id,
                code = null,
                name = null,
                type = "Save success",
                message = "created 2 stock in line",
                errorPosition = null,
                entity = lineInfoList
            )
        }
    }

    @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 qrCodeContent = (
                "itemId:${info.itemId}" +
                ",stockInLineId:${info.id}"
            )
//            field["itemId"] = info.itemId
            field["itemName"] = info.itemName!!
            field["itemNo"] = info.itemNo
            field["poCode"] = info.poCode
            field["itemType"] = info.itemType
            field["acceptedQty"] = info.acceptedQty.toString()
            field["uom"] = info.uom!!
            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!!
            val image = QrCodeUtil.generateQRCodeImage(qrCodeContent)
            field["qrCode"] = image
            fields.add(field)
        }
        val params: MutableMap<String, Any> = mutableMapOf(
            "poCode" to qrCodeInfo[0].poCode
        )
        return mapOf(
            "report" to PdfUtils.fillReport(poLabel,fields, params),
            "fileName" to qrCodeInfo[0].poCode
        );
    }

}