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

import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus
import com.ffii.fpsms.modules.stock.entity.*
import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo
import com.ffii.fpsms.modules.stock.web.model.*
import org.springframework.data.repository.query.Param
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

@Service
open class StockOutLineService(
    private val jdbcDao: JdbcDao,
    private val pickOrderLineRepository: PickOrderLineRepository,
    private val stockOutRepository: StockOutRepository,
    private val stockOutLineRepository: StockOutLIneRepository,
    private val itemRepository: ItemsRepository,
    private val inventoryRepository: InventoryRepository,
    private val inventoryLotLineRepository: InventoryLotLineRepository
    ): AbstractBaseEntityService<StockOutLine, Long, StockOutLIneRepository>(jdbcDao, stockOutLineRepository)  {
    @Throws(IOException::class)
    @Transactional
    open fun findAllByStockOutId(stockOutId: Long): List<StockOutLine> {
        return stockOutLineRepository.findAllByStockOutIdAndDeletedFalse(stockOutId)
    }
    @Throws(IOException::class)
    @Transactional
    open fun deleteById(id: Long): String {
        val deleteStockOutline = stockOutLineRepository.findById(id).orElseThrow().apply {
            deleted = true
        }
        val updateItem = stockOutLineRepository.save(deleteStockOutline)
        val response = "mark deleted item: ${updateItem.id}"
        return response
    }
    @Throws(IOException::class)
    @Transactional
    open fun deleteByStockOutId(id: Long): String {
        val deleteStockOutline = stockOutLineRepository.findAllByStockOutIdAndDeletedFalse(id).map {sol ->
            sol.apply {
                deleted = true
            }
        }
        val updateItemIds = stockOutLineRepository.saveAllAndFlush(deleteStockOutline).map { it.id }
        val response = "mark deleted items: $updateItemIds"
        return response
    }

    @Transactional
    open fun getAllStockOutLineByPickOrderLineId(pickOrderLineId: Long): List<StockOutLineInfo> {
        return stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
    }

    @Transactional
    open fun create(request: CreateStockOutLineRequest): MessageResponse {
        // pick flow step 1
//        println(request.pickOrderLineId)
        val stockOut = stockOutRepository.findByConsoPickOrderCode(request.consoCode).orElseThrow()
        val pickOrderLine = pickOrderLineRepository.saveAndFlush(
            pickOrderLineRepository.findById(request.pickOrderLineId).orElseThrow()
                .apply {
                    this.status = PickOrderLineStatus.PICKING
                }
        )
        val item = itemRepository.findById(pickOrderLine.item!!.id!!).orElseThrow()
        val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()
        val stockOutLine = StockOutLine()
            .apply {
                this.item = item
                this.qty = request.qty
                this.stockOut = stockOut
                this.inventoryLotLine = inventoryLotLine
                this.pickOrderLine = pickOrderLine
                this.status = StockOutLineStatus.PENDING.status
            }
        val savedStockOutLine = saveAndFlush(stockOutLine)
        val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!)
//        println("triggering")
        return MessageResponse(
            id = savedStockOutLine.id,
            name = savedStockOutLine.inventoryLotLine!!.inventoryLot!!.lotNo,
            code = savedStockOutLine.stockOut!!.consoPickOrderCode,
            type = savedStockOutLine.status,
            message = "success",
            errorPosition = null,
            entity = mappedSavedStockOutLine,
        )
    }

    @Transactional
    fun handleQc(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): List<StockOutLine?> {
        var newStockOutLine: StockOutLine? = null
        if (request.qty < stockOutLine.qty!!) {
            newStockOutLine = StockOutLine().apply {
                this.pickOrderLine = stockOutLine.pickOrderLine
                this.stockOut = stockOutLine.stockOut
                this.item = stockOutLine.item
                this.qty = stockOutLine.qty!! - request.qty
                this.status = StockOutLineStatus.DETERMINE1.status // escalated
            }
        }
        val inventoryLotLine = if (request.inventoryLotLineId != null)
            inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()
        else null
        stockOutLine.apply {
            this.inventoryLotLine = inventoryLotLine ?: stockOutLine.inventoryLotLine
            this.qty = request.qty
            this.status = StockOutLineStatus.COMPLETE.status // complete
        }
        // update inventory lot line
        val targetLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId!!).orElseThrow()
        val targetLotLineEntry = targetLotLine.apply {
            this.outQty = (this.outQty?: BigDecimal.ZERO) + request.qty.toBigDecimal()
            this.holdQty = this.holdQty!!.minus(request.qty.toBigDecimal())
        }
        inventoryLotLineRepository.save(targetLotLineEntry)
        // update inventory
        val inventory = inventoryRepository.findByItemId(request.itemId).orElseThrow()
        val inventoryEntry = inventory.apply {
            this.onHandQty = this.onHandQty!!.minus(request.qty.toBigDecimal())
            this.onHoldQty = this.onHoldQty!!.minus(request.qty.toBigDecimal())
        }

        return listOf(stockOutLine, newStockOutLine)
    }
    @Transactional
    fun handleLotChangeApprovalOrReject(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): List<StockOutLine?> {
        /**
         * @param request.qty
         *  qty that requires lot change
         */
        var newStockOutLine: StockOutLine? = null

        if (request.qty < stockOutLine.qty!!) {
            newStockOutLine = StockOutLine().apply {
                this.pickOrderLine = stockOutLine.pickOrderLine
                this.stockOut = stockOutLine.stockOut
                this.item = stockOutLine.item
                this.qty = stockOutLine.qty!! - request.qty
                this.status = StockOutLineStatus.REJECTED.status // original status
            }
        }
        stockOutLine.apply {
            this.qty = request.qty
            this.status = StockOutLineStatus.LOT_CHANGE_APPROVAL.status // require new lot no
        }
        return listOf(stockOutLine, newStockOutLine)
    }

//    @Transactional
//    fun assignNewLotLine(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): List<StockOutLine?> {
//        val newInventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow()
//        return listOf(stockOutLine.apply {
//            this.inventoryLotLine = newInventoryLotLine
//        })
//    }
    @Transactional
    fun checkIsStockOutLineCompleted(pickOrderLineId: Long) {
        val unfinishedLine = stockOutLineRepository
            .findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId)
            .filter {
                it.status != StockOutLineStatus.COMPLETE.status
                        &&  it.status != StockOutLineStatus.REJECTED.status
            }
        if (unfinishedLine.isEmpty()) {
            // set pick order line status to complete
            val pol = pickOrderLineRepository.findById(pickOrderLineId).orElseThrow()
            pickOrderLineRepository.save(
                pol.apply {
                    this.status = PickOrderLineStatus.COMPLETED
                }
            )
        } else {
            // return unfinished ids to frontend
            println(unfinishedLine.map {
                Pair(
                    it.id,
                    it.status
                )
            })
        }
    }
    @Transactional
    open fun update(request: UpdateStockOutLineRequest): MessageResponse {
        val stockOutLine = stockOutLineRepository.findById(request.id).orElseThrow()
        var stockOutLineEntries: List<StockOutLine?> = listOf()
        // pick flow step 2
        if (request.status == StockOutLineStatus.COMPLETE.status) { // doing qc
            println("doing sth")
            if (request.qty <= 0) {
                return MessageResponse(
                    id = null,
                    code = null,
                    name = null,
                    type = "qty == 0",
                    message = "qty cannot be 0",
                    errorPosition = "request.qty",
                )
            }
            stockOutLineEntries = handleQc(stockOutLine, request)
        }
        if (request.status == StockOutLineStatus.LOT_CHANGE_APPROVAL.status) {
            if (request.qty < 0) {
                return MessageResponse(
                    id = null,
                    code = null,
                    name = null,
                    type = "qty < 0",
                    message = "qty cannot be smaller than 0",
                    errorPosition = "request.qty",
                )
            }
            stockOutLineEntries = handleLotChangeApprovalOrReject(stockOutLine, request)
        }

        val savedEntries = stockOutLineRepository.saveAllAndFlush(stockOutLineEntries.filterNotNull())
        val lineInfoList = stockOutLineRepository.findAllByIdIn(savedEntries.map { it.id!! })

        checkIsStockOutLineCompleted(pickOrderLineId = stockOutLine.pickOrderLine!!.id!!)

        return MessageResponse(
            id = 0,
            name = "id: ${lineInfoList.map { it.id }.joinToString(separator = ",")}",
            code = stockOutLine.stockOut!!.consoPickOrderCode,
            type = lineInfoList.joinToString(separator = ",") { it.status },
            message = "success",
            errorPosition = null,
            entity = lineInfoList,
        )
    }
}