package com.ffii.fpsms.modules.purchaseOrder.service

import com.ffii.core.response.RecordsRes
import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.m18.entity.GrnSendLog
import com.ffii.fpsms.m18.entity.GrnSendLogRepository
import com.ffii.fpsms.m18.entity.M18DataLogRepository
import com.ffii.fpsms.m18.utils.CommonUtils
import com.ffii.fpsms.modules.master.entity.QcItem
import com.ffii.fpsms.modules.master.entity.ShopRepository
import com.ffii.fpsms.modules.master.service.CurrencyService
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.service.ShopService
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.purchaseOrder.entity.projections.*
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus
import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderType
import com.ffii.fpsms.modules.purchaseOrder.web.model.*
import com.ffii.fpsms.modules.stock.entity.StockInLine
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.entity.StockInRepository
import com.ffii.fpsms.modules.stock.web.model.StockInLineStatus
import com.google.gson.Gson
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.math.BigDecimal
import java.time.LocalDateTime
import java.util.HashMap
import java.util.Objects
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.purchaseOrder.web.model.PurchaseOrderSummary

@Service
open class PurchaseOrderService(
    private val jdbcDao: JdbcDao,
    private val purchaseOrderRepository: PurchaseOrderRepository,
    private val polRepository: PurchaseOrderLineRepository,
    private val shopRepository: ShopRepository,
    private val m18DataLogRepository: M18DataLogRepository,
    private val stockInRepository: StockInRepository,
    private val stockInLineRepository: StockInLineRepository,
    private val currencyService: CurrencyService,
    private val shopService: ShopService,
    private val grnSendLogRepository: GrnSendLogRepository,
    val itemUomService: ItemUomService,
) : AbstractBaseEntityService<PurchaseOrder, Long, PurchaseOrderRepository>(jdbcDao, purchaseOrderRepository) {
//    open fun getPurchaseOrderInfo(args: Map<String, Any>): List<Map<String, Any>> {
//        val sql = StringBuilder(
//            "SELECT " +
//                    "po.* " +
//                    "FROM purchase_order po " +
//                    "WHERE po.deleted = FALSE"
//        );
//        if (args.containsKey("name")){
//            sql.append(" AND i.name like :name ");
//        }
//    }
open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
    if (ids.isEmpty()) return emptyList()

    val sql = """
        select 
            po.id,
            po.code,
            po.status,
            po.orderDate,
            po.estimatedArrivalDate,
            s.name as supplierName,
            exists(
              select 1 from stock_in_line sil 
              where sil.purchaseOrderId = po.id 
                and sil.status like 'determine%' 
                and sil.deleted = false
            ) as escalated
        from purchase_order po
        left join shop s on s.id = po.supplierId
        where po.deleted = false
          and po.id in (:ids)
        order by po.orderDate desc
    """.trimIndent()

    val args = mutableMapOf<String, Any>(
        "ids" to ids
    )

    val list = jdbcDao.queryForList(sql, args)

    return list.map {
        PurchaseOrderSummary(
            id = (it["id"] as Number).toLong(),
            code = it["code"] as String,
            status = it["status"] as String?,
            orderDate = it["orderDate"] as LocalDateTime?,
            estimatedArrivalDate = it["estimatedArrivalDate"] as LocalDateTime?,
            supplierName = it["supplierName"] as String?,
            escalated = (it["escalated"] as Boolean?) ?: false,
        )
    }
}
    /**
     * `select * from ( ... one row per PO ... ) r [where r.itemDetail ...]` — same as the legacy list query, without LIMIT.
     */
    private fun buildPoListUnpagedSelectSql(args: MutableMap<String, Any>): String {
        val sql = StringBuilder(
            "select * from ( " +
                    "select " +
                    "     po.*," +
                    "     group_concat(" +
                    "       coalesce(i.code, \"N/A\"), " +
                    "       '-', " +
                    "       coalesce(i.name, \"N/A\")," +
                    "       ' ('," +
                    "       coalesce(uc.udfudesc, \"N/A\")," +
                    "       ')'," +
                    "       ': ', " +
                    "       coalesce(pol.qty, 0), " +
                    "       ' (', " +
                    "       coalesce(sil2.sumAcceptedQty, 0)," +
                    "       ')' " +
                    "       SEPARATOR ','" +
                    "     ) as itemDetail," +
                    "     group_concat(" +
                    "       coalesce(i.code, \"N/A\") " +
                    "       SEPARATOR ','" +
                    "     ) as itemCode," +
                    "     group_concat(" +
                    "       coalesce(i.name, \"N/A\") " +
                    "       SEPARATOR ','" +
                    "     ) as itemName," +
                    "     group_concat(" +
                    "       coalesce(ucm18.udfudesc, uc.udfudesc, \"N/A\") " +
                    "       SEPARATOR ','" +
                    "     ) as itemUom," +
                    "     group_concat(" +
                    "       coalesce(pol.qtyM18, pol.qty, 0) " +
                    "       SEPARATOR ','" +
                    "     ) as itemQty," +
                    "     group_concat(" +
                    "       coalesce(sil2.sumAcceptedQty, 0) " +
                    "       SEPARATOR ','" +
                    "     ) as itemSumAcceptedQty," +
                    "     s.name as supplier, " +
                    "     CASE " +
                    "         WHEN sil.purchaseOrderId IS NOT NULL THEN 1 " +
                    "         ELSE 0 " +
                    "     END AS escalated " +
                    " from purchase_order po" +
                    " left join purchase_order_line pol on pol.purchaseOrderId = po.id" +
                    " left join items i on i.id = pol.itemId" +
                    " left join shop s on s.id = po.supplierId " +
                    " left join ( " +
                    "     select " +
                    "         sil.purchaseOrderId " +
                    "         from stock_in_line sil " +
                    "         where sil.status like 'determine%'" +
                    "         and sil.deleted = false" +
                    "     ) sil on sil.purchaseOrderId = po.id" +
                    " left join ( " +
                    "     select " +
                    "         sil.purchaseOrderLineId, " +
                    "         sum(coalesce(sil.acceptedQty, 0)) as sumAcceptedQty " +
                    "         from stock_in_line sil " +
                    "         where sil.deleted = false " +
                    "         and sil.status = 'completed' " +
                    "         group by sil.purchaseOrderLineId " +
                    "     ) sil2 on sil2.purchaseOrderLineId = pol.id" +
                    " left join item_uom iu on iu.itemId = pol.itemId and iu.purchaseUnit = true" +
                    " left join uom_conversion uc on uc.id = iu.uomId" +
                    " left join uom_conversion ucm18 on ucm18.id = pol.uomIdM18" +
                    " where po.deleted = false " +
                    " and pol.deleted = false "
        )
        if (args.containsKey("code")){
            sql.append(" AND po.code like :code ");
        }
        if (args.containsKey("supplier")){
            sql.append(" AND s.name like :supplier ");
        }
        if (args.containsKey("status")){
            sql.append(" AND po.status = :status ");
        }
        if (args.containsKey("escalated")) {
            if (args["escalated"] == true) {
                sql.append(" and sil.purchaseOrderId IS NOT NULL ");
            } else {
                sql.append(" and sil.purchaseOrderId IS NULL ");
            }
        }
        if (args.containsKey("orderDate")){
            sql.append(" AND po.orderDate >= :orderDate ");
        }
        if (args.containsKey("orderDateTo")){
            sql.append(" AND po.orderDate <= :orderDateTo ");
        }
        if (args.containsKey("estimatedArrivalDate")){
            sql.append(" AND po.estimatedArrivalDate >= :estimatedArrivalDate ");
        }
        if (args.containsKey("estimatedArrivalDateTo")){
            sql.append(" AND po.estimatedArrivalDate <= :estimatedArrivalDateTo ");
        }
        sql.append(" group by po.id");
        sql.append(" order by po.orderDate desc")
        sql.append(" ) r")
        if (args.containsKey("itemDetail")){
            sql.append(" where r.itemDetail like :itemDetail ");
        }
        return sql.toString()
    }

    private fun mapRowsToPoListDataClass(list: List<Map<String, Any>>): List<PurchaseOrderDataClass> {
        return list.map {
            PurchaseOrderDataClass(
                id = (it["id"] as Int).toLong(),
                code = it["code"] as String,
                orderDate = it["orderDate"] as LocalDateTime?,
                estimatedArrivalDate = it["estimatedArrivalDate"] as LocalDateTime?,
                itemDetail = it["itemDetail"] as String,
                itemCode = it["itemCode"] as String,
                itemName = it["itemName"] as String,
                itemQty = it["itemQty"] as String,
                itemSumAcceptedQty = it["itemSumAcceptedQty"] as String,
                itemUom = it["itemUom"] as String,
                completeDate = it["completeDate"] as LocalDateTime?,
                status = it["status"] as String,
                supplier = it["supplier"] as String?,
                escalated = it["escalated"] == 1L,
            )
        }
    }

    open fun getPoListTotalCount(args: MutableMap<String, Any>): Int {
        val base = buildPoListUnpagedSelectSql(args)
        val countSql = "SELECT COUNT(1) AS cnt FROM ( $base ) po_list_count_wrap"
        val list = jdbcDao.queryForList(countSql, args)
        if (list.isEmpty()) {
            return 0
        }
        return (list.first()["cnt"] as Number).toInt()
    }

    open fun getPoListPage(
        args: MutableMap<String, Any>,
        pageSize: Int,
        pageNum: Int,
    ): List<PurchaseOrderDataClass> {
        val size = pageSize.coerceAtLeast(1)
        val page = pageNum.coerceAtLeast(1)
        val pagedArgs: MutableMap<String, Any> = HashMap(args)
        pagedArgs["limit"] = size
        pagedArgs["offset"] = (page - 1) * size
        val dataSql = buildPoListUnpagedSelectSql(args) +
            " ORDER BY r.orderDate DESC LIMIT :limit OFFSET :offset"
        val list = jdbcDao.queryForList(dataSql, pagedArgs)
        return mapRowsToPoListDataClass(list)
    }

    open fun allPurchaseOrder(): List<PurchaseOrder> {
        return purchaseOrderRepository.findAll()
    }

    open fun getDetailedPo(id: Long): Map<String, Any> {
        // REMINDER: po code have duplication
        val po = purchaseOrderRepository.findPurchaseOrderInfoByIdAndDeletedIsFalse(id)
        val pol = polRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(id)
//        val pol = polRepository.findAllPurchaseOrderLineInfoByPurchaseOrderIdAndDeletedIsFalse(id)
        val stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(id)
        val stockInLine = if (stockIn == null) listOf() else {
            stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!)
        }
        val mappedPoLine = pol.map { thisPol ->
            val inLine = stockInLine.filter { it.purchaseOrderLineId == thisPol.id }
                .filter { it.receiptDate != null }
            val categoryCode = thisPol.item?.qcCategory?.code
            val qcItems = thisPol.item?.qcCategory?.qcItemCategory?.map {
                QcForPoLine(
                    id = it.qcItem?.id,
                    code = categoryCode,
                    name = it.qcItem?.name,
                    description = it.description
                )
            } ?: mutableListOf();
            val purchaseUnit = thisPol.item?.id?.let { itemUomService.findPurchaseUnitByItemId(it) }
            val stockUnit = thisPol.item?.id?.let { itemUomService.findStockUnitByItemId(it).let { iu ->
                val itemId = thisPol.item?.id
                val qtyM18 = thisPol.qtyM18
                val uomM18Id = thisPol.uomM18?.id
                val stockQtyFromM18 = if (itemId != null && qtyM18 != null && uomM18Id != null) {
                    itemUomService.convertQtyToStockQtyPrecise(itemId, uomM18Id, qtyM18)
                } else {
                    BigDecimal.ZERO
                }
                StockUomForPoLine(
                    id = iu?.id,
                    stockUomCode = iu?.uom?.code,
                    stockUomDesc = iu?.uom?.udfudesc,
                    stockQty = if (stockQtyFromM18 > BigDecimal.ZERO) stockQtyFromM18 else {
                        // fallback to legacy behavior when M18 fields are missing
                        iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQtyPrecise(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO
                    },
                    stockRatioN = iu?.ratioN,
                    stockRatioD = iu?.ratioD,
                    purchaseRatioN = purchaseUnit?.ratioN,
                    purchaseRatioD = purchaseUnit?.ratioD,
                )
            } } ?: StockUomForPoLine()
            PoLineWithStockInLine(
                id = thisPol.id!!,
                purchaseOrderId = thisPol.purchaseOrder!!.id!!,
                itemId = thisPol.item!!.id!!,
                itemNo = thisPol.itemNo!!,
                itemName = thisPol.item!!.name,
                qty = thisPol.qtyM18 ?: thisPol.qty!!,
//                processed = inLine.filter{ it.status == StockInLineStatus.COMPLETE.status}.sumOf { it.acceptedQty },
                processed = inLine
                    .filter { line -> line.putAwayLines?.any { it.qty?.let { qty -> qty > BigDecimal.ZERO } == true } ?: false }
                    .sumOf { line -> line.putAwayLines?.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } ?: BigDecimal.ZERO},
                receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO },
                uom = thisPol.uomM18 ?: thisPol.uom!!,
                price = thisPol.price!!,
                status = thisPol.status!!.toString(),
                stockInLine = inLine,
                qcItems = qcItems,
                stockUom = stockUnit
            )
        }
        val result = mapOf(
            "id" to po.id,
            "code" to po.code,
            "supplier" to (po.supplier ?: "N/A"),
            "orderDate" to po.orderDate,
            "estimatedArrivalDate" to po.estimatedArrivalDate,
            "completeDate" to po.completeDate,
            "status" to po.status,
            "pol" to mappedPoLine
        )
        return result
    }

    open fun findByM18DataLogId(m18DataLogId: Long): PurchaseOrder? {
        return purchaseOrderRepository.findTopByM18DataLogIdAndDeletedIsFalseOrderByModifiedDesc(m18DataLogId)
    }

    @Throws(IOException::class)
    @Transactional
    open fun savePurchaseOrder(request: SavePurchaseOrderRequest): SavePurchaseOrderResponse {
        val purchaseOrder =
            request.id?.let { purchaseOrderRepository.findById(it).getOrDefault(PurchaseOrder()) } ?: PurchaseOrder()
        val supplier = request.m18SupplierId?.let { shopService.findVendorByM18Id(it) }
            ?: request.supplierId?.let { shopService.findById(it) }
        val shop = request.m18ShopId?.let { shopService.findShopByM18Id(it) }
            ?: request.shopId?.let { shopService.findById(it) }
        val currency = request.m18CurrencyId?.let { currencyService.findByM18Id(it) }
            ?: request.currencyId?.let { currencyService.findById(it) }
        val status = request.status?.let { status -> PurchaseOrderStatus.entries.find { it.value == status } }
        val type = request.type?.let { type -> PurchaseOrderType.entries.find { it.value == type } }
        val m18DataLog = request.m18DataLogId?.let { m18DataLogRepository.findById(it).getOrNull() }

        //Need check duplicate?
        purchaseOrder.apply {
            code = request.code
            this.supplier = supplier
            this.shop = shop
            this.currency = currency
            orderDate = request.orderDate
            estimatedArrivalDate = request.estimatedArrivalDate
            completeDate = request.completeDate
            this.status = status
            this.type = type
            this.m18DataLog = m18DataLog
            m18BeId = request.m18BeId
            m18CreatedUId = request.m18CreatedUId
        }

        val savedPurchaseOrder = purchaseOrderRepository.saveAndFlush(purchaseOrder).let {
            SavePurchaseOrderResponse(
                id = it.id,
                code = it.code,
                supplierCode = it.supplier?.code,
                shopCode = it.shop?.code,
                currencyCode = it.currency?.code,
                orderDate = it.orderDate,
                estimatedArrivalDate = it.estimatedArrivalDate,
                completeDate = it.completeDate,
                status = it.status?.value
            )
        }

        return savedPurchaseOrder
    }
    @Throws(IOException::class)
    @Transactional
    open fun startPo(id: Long): MessageResponse {
        val po = purchaseOrderRepository.findById(id).orElseThrow()
        po.apply {
            status = PurchaseOrderStatus.RECEIVING
        }
        val savedPo = purchaseOrderRepository.saveAndFlush(po)
        return MessageResponse(
            id = savedPo.id,
            name = savedPo.code,
            code = savedPo.code,
            type = PurchaseOrderStatus.RECEIVING.value,
            message = "Update Success",
            errorPosition = null,
            entity = savedPo,
        )
    }
    @Throws(IOException::class)
    @Transactional
    open fun checkPolAndCompletePo(id: Long): MessageResponse {
        val unfinishedLines = polRepository.findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = id, status = PurchaseOrderLineStatus.COMPLETED)
//        val purchaseOrderLine = polRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(purchaseOrderId = id)
//        val unfinishedLines = purchaseOrderLine.filter {
//            it.status != PurchaseOrderLineStatus.COMPLETED
//        }
//        val unfinishedLines = polRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(purchaseOrderId = id)
//            .filter {
//                it.status != PurchaseOrderLineStatus.COMPLETED |
//                it.status != PurchaseOrderLineStatus.
//            }
        println("unfinishedLines")
        println(unfinishedLines)

        val po = purchaseOrderRepository.findById(id).orElseThrow()
        if (unfinishedLines.isEmpty()) {
            val stockInLines = stockInLineRepository.findAllByPurchaseOrderIdAndDeletedFalse(po.id!!).orElseThrow().filter {
                it.status == StockInLineStatus.COMPLETE.status
            }
            po.apply {
                status = PurchaseOrderStatus.COMPLETED
            }
            val savedPo = purchaseOrderRepository.saveAndFlush(po)
            // map request
            val antRequest = stockInLines.map {
                Ant(
                    proCode = it.itemNo!!,
                    unitCode = it.purchaseOrderLine!!.uom!!.code!!,
                    locCode = it.inventoryLotLine!!.warehouse!!.code!!,
                    qty = it.acceptedQty!!.toDouble(),
                    up = (it.purchaseOrderLine!!.up ?: 0.0).toDouble(),
                    amt = CommonUtils.getAmt(
                        up = (it.purchaseOrderLine!!.up ?: BigDecimal(0)),
                        discount = it.purchaseOrderLine!!.m18Discount ?: BigDecimal(0),
                        qty = it.acceptedQty!!
                    )
                )
            }
            val postGrnRequest = PostGrnRequest(
                beId = po!!.m18BeId!!,
                venCode = po.supplier!!.code!!,
                ant = antRequest
            )
            // post grn api
            // log in grn send log
            val grnSendLog = GrnSendLog().apply {
                this.refType = "purchase order"
                this.data = mutableMapOf(
                    "beId" to postGrnRequest.beId,
                    "venCode" to postGrnRequest.venCode,
                    "ant" to postGrnRequest.ant,
                )
                this.status = true
            }
            grnSendLogRepository.save(grnSendLog)

            return MessageResponse(
                id = savedPo.id,
                name = savedPo.code,
                code = savedPo.code,
                type = PurchaseOrderStatus.COMPLETED.value,
                message = "Update Success",
                errorPosition = null,
                entity = savedPo,
            )
        }
        val unfinishedIds = unfinishedLines.map { it.id }
        return MessageResponse(
            id = po.id,
            name = po.code,
            code = null,
            type = PurchaseOrderStatus.RECEIVING.value,
            message = "Still have unfinished po lines",
            errorPosition = "Unfinished",
            entity = unfinishedIds,
        )
    }

}