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

import com.ffii.fpsms.modules.pickOrder.entity.PickOrder
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.stock.entity.*
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo
import com.ffii.fpsms.modules.stock.enums.SuggestedPickLotType
import com.ffii.fpsms.modules.stock.web.model.SaveSuggestedPickLotRequest
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPoRequest
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPolRequest
import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotResponse
import org.springframework.stereotype.Service
import java.math.BigDecimal
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull

@Service
open class SuggestedPickLotService(
    val suggestedPickLotRepository: SuggestPickLotRepository,
    val stockOutLIneRepository: StockOutLIneRepository,
    val inventoryLotLineRepository: InventoryLotLineRepository,
    val pickOrderLineRepository: PickOrderLineRepository,
    val inventoryLotLineService: InventoryLotLineService,
) {
    // Calculation Available Qty / Remaining Qty
    open fun calculateRemainingQtyForInfo(inventoryLotLine: InventoryLotLineInfo?): BigDecimal {
        val zero = BigDecimal.ZERO
        return (inventoryLotLine?.inQty ?: zero)
            .minus(inventoryLotLine?.outQty ?: zero)
            .minus((inventoryLotLine?.holdQty ?: zero))
    }

    open fun calculateRemainingQty(inventoryLotLine: InventoryLotLine?): BigDecimal {
        val zero = BigDecimal.ZERO
        return (inventoryLotLine?.inQty ?: zero)
            .minus(inventoryLotLine?.outQty ?: zero)
            .minus((inventoryLotLine?.holdQty ?: zero))
    }

    // Suggestion
    open fun suggestionForPickOrders(request: SuggestedPickLotForPoRequest): SuggestedPickLotResponse {
        val pos = request.pickOrders
        val suggestedList = mutableListOf<SuggestedPickLot>()
        var holdQtyMap = request.holdQtyMap

        pos.forEach {
            val response = suggestionForPickOrderLines(SuggestedPickLotForPolRequest(
                holdQtyMap = holdQtyMap,
                pickOrderLines = it.pickOrderLines
            ))

            holdQtyMap = response.holdQtyMap
            suggestedList += response.suggestedList
        }

        return SuggestedPickLotResponse(holdQtyMap = holdQtyMap,  suggestedList = suggestedList)
    }

    open fun suggestionForPickOrderLines(request: SuggestedPickLotForPolRequest): SuggestedPickLotResponse {
        val pols = request.pickOrderLines
        val itemIds = pols.mapNotNull { it.item?.id }
        val zero = BigDecimal.ZERO

        val suggestedList: MutableList<SuggestedPickLot> = mutableListOf()
        val holdQtyMap: MutableMap<Long?, BigDecimal?> = request.holdQtyMap

        // get current inventory lot line qty & grouped by item Id
        val availableInventoryLotLines = inventoryLotLineService
            .allInventoryLotLinesByItemIdIn(itemIds)
            .filter { it.status == InventoryLotLineStatus.AVAILABLE.value }
            .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero }
            .sortedBy { it.expiryDate }
            .groupBy { it.item?.id }

        // loop for suggest pick lot line
        pols.forEach { line ->
            val lotLines = availableInventoryLotLines[line.item?.id].orEmpty()
            var remainingQty = line.qty ?: zero
            val updatedLotLines = mutableListOf<InventoryLotLineInfo>()

            lotLines.forEachIndexed { index, lotLine ->
                if (remainingQty <= zero) return@forEachIndexed

                val availableQty = calculateRemainingQtyForInfo(lotLine)
                    .minus(holdQtyMap[lotLine.id] ?: zero)


                if (availableQty <= zero) {
                    updatedLotLines += lotLine
                    return@forEachIndexed
                }
                val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() }
                val originalHoldQty = inventoryLotLine?.holdQty

                // Update Qty
                val assignQty = minOf(availableQty, remainingQty)
                remainingQty = remainingQty.minus(assignQty)
                holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQty)
//                lotLine.holdQty = lotLine.holdQty?.plus(assignQty)
                suggestedList += SuggestedPickLot().apply {
                    type = SuggestedPickLotType.PICK_ORDER
                    suggestedLotLine = inventoryLotLine
                    pickOrderLine = line
                    qty = assignQty
                }
            }

            // if still have remainingQty
            if (remainingQty > zero) {
                suggestedList += SuggestedPickLot().apply {
                    type = SuggestedPickLotType.PICK_ORDER
                    suggestedLotLine = null
                    pickOrderLine = line
                    qty = remainingQty
                }
            }
        }
        return SuggestedPickLotResponse(holdQtyMap = holdQtyMap,  suggestedList = suggestedList)
    }

    // Convertion
    open fun convertRequestsToEntities(request: List<SaveSuggestedPickLotRequest>): List<SuggestedPickLot> {
//        val zero = BigDecimal.ZERO
//        val entities = mutableListOf<SuggestedPickLot>()
//        val holdQtyCount = mutableMapOf<Long?, BigDecimal?>()
//        request.forEach {
//            val entity = convertRequestToEntity(it)
//            val suggestedLotLine = entity.suggestedLotLine
//            val remainQty = calculateRemainingQty(suggestedLotLine)
//            holdQtyCount[suggestedLotLine?.id] = (holdQtyCount[suggestedLotLine?.id] ?: zero).plus(suggestedLotLine?.holdQty ?: zero)
//            if (remainQty.minus(holdQtyCount[suggestedLotLine?.id] ?: zero) < zero) {
//                throw RuntimeException("The suggested pick qty is over. Please re-suggest the pick qty.");
//            }
//            entities += entity
//        }
//        return entities
        return request.map { convertRequestToEntity(it) }
    }

    open fun convertRequestToEntity(request: SaveSuggestedPickLotRequest): SuggestedPickLot{
        val suggestedPickLot =
            request.id?.let { id -> suggestedPickLotRepository.findById(id).getOrDefault(SuggestedPickLot()) }
                ?: SuggestedPickLot()
        val type = request.type?.let { _type -> SuggestedPickLotType.entries.find { it.value == _type } }
        val stockOutLine = request.stockOutLineId?.let { id -> stockOutLIneRepository.findById(id).getOrNull() }
        val suggestedLotLine =
            request.suggestedLotLineId?.let { id -> inventoryLotLineRepository.findById(id).getOrNull() }
        val pickOrderLine = request.pickOrderLineId?.let { id -> pickOrderLineRepository.findById(id).getOrNull() }

        suggestedPickLot.apply {
            this.type = type
            this.stockOutLine = stockOutLine
            this.suggestedLotLine = suggestedLotLine
            this.pickOrderLine = pickOrderLine
            qty = request.qty
        }

        return suggestedPickLot
    }

    // Save
    open fun saveSuggestedPickLot(request: SaveSuggestedPickLotRequest): SuggestedPickLot {
        val suggestedPickLot = convertRequestToEntity(request)

        return suggestedPickLotRepository.save(suggestedPickLot)
    }

    open fun saveAll(request: List<SuggestedPickLot>): List<SuggestedPickLot> {
        return suggestedPickLotRepository.saveAll(request)
    }
}