package com.ffii.fpsms.m18.service

import com.ffii.core.utils.JwtTokenUtil
import com.ffii.fpsms.api.service.ApiCallerService
import com.ffii.fpsms.m18.M18Config
import com.ffii.fpsms.m18.enums.M18DataLogStatus
import com.ffii.fpsms.m18.model.*
import com.ffii.fpsms.m18.utils.CommonUtils
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.service.ItemsService
import com.ffii.fpsms.modules.master.service.ShopService
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.service.PurchaseOrderLineService
import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderService
import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderLineRequest
import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderRequest
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import kotlin.reflect.full.memberProperties

@Service
open class M18PurchaseOrderService(
    val m18Config: M18Config,
    val apiCallerService: ApiCallerService,
    val m18DataLogService: M18DataLogService,
    val purchaseOrderService: PurchaseOrderService,
    val purchaseOrderLineService: PurchaseOrderLineService,
    val itemsService: ItemsService,
    val shopService: ShopService,
    val itemUomService: ItemUomService,
) {
    val commonUtils = CommonUtils()
    val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)

    val lastModifyDateStart = "2025-05-14 14:00:00"
    val lastModifyDateEnd = "2025-05-14 14:30:00"
    val lastModifyDateConds =
        "lastModifyDate=largerOrEqual=${lastModifyDateStart}=and=lastModifyDate=lessOrEqual=${lastModifyDateEnd}"
//    val lastModifyDate = LocalDateTime.now().minusMinutes(30)
//    val commonConds =
//        "(beId=equal=${m18Config.BEID_PF}=or=beId=equal=${m18Config.BEID_PP}=or=beId=equal=${m18Config.BEID_TOA})=and=lastModifyDate=largerOrEqual=${lastModifyDate}"

    // M18 API
    val M18_LOAD_PURCHASE_ORDER_API = "/root/api/read/po"
    val M18_FETCH_PURCHASE_ORDER_LIST_API = "/search/search"

    // Include material po, shop po, oem po
    open fun getPurchaseOrdersWithType(): M18PurchaseOrderListResponseWithType? {
        val purchaseOrders = M18PurchaseOrderListResponseWithType(mutableListOf())

        // Material PO
        val materialPoBuyers =
            commonUtils.listToString(listOf(m18Config.BEID_PP, m18Config.BEID_PF), "beId=equal=", "=or=")
        val materialPoSupplierNot = commonUtils.listToString(
            shopService.findM18VendorIdsByCodeRegexp(m18Config.MATERIAL_PO_SUPPLIER_NOT),
            "venId=unequal=",
            "=or="
        )
        val materialPoConds = "(${materialPoBuyers})=and=(${materialPoSupplierNot})=and=(${lastModifyDateConds})"
        println("materialPoConds: ${materialPoConds}")
        val materialPoParams = M18PurchaseOrderListRequest(
            params = null,
            conds = materialPoConds
        )

        try {
            purchaseOrders.valuesWithType += Pair(
                PurchaseOrderType.MATERIAL,
                apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
                    M18_FETCH_PURCHASE_ORDER_LIST_API,
                    materialPoParams
                ).block()
            )
        } catch (e: Exception) {
            logger.error("(Getting Material Po list) Error on Function - ${e.stackTrace}")
            logger.error(e.message)
        }

        // Shop PO
        val shopPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_TOA), "beId=equal=", "=or=")
        val shopPoSupplier = commonUtils.listToString(
            shopService.findM18VendorIdsByCodeRegexp(m18Config.SHOP_PO_SUPPLIER),
            "venId=equal=",
            "=or="
        )
        val shopPoConds = "(${shopPoBuyers})=and=(${shopPoSupplier})=and=(${lastModifyDateConds})"
        println("shopPoConds: ${shopPoConds}")
        val shopPoParams = M18PurchaseOrderListRequest(
            params = null,
            conds = shopPoConds
        )

        try {
            purchaseOrders.valuesWithType += Pair(
                PurchaseOrderType.SHOP, apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
                    M18_FETCH_PURCHASE_ORDER_LIST_API,
                    shopPoParams
                ).block()
            )
        } catch (e: Exception) {
            logger.error("(Getting Shop Po list) Error on Function - ${e.stackTrace}")
            logger.error(e.message)
        }

        // OEM PO
        /* val oemPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_PF, m18Config.BEID_PP), "beId=equal=", "=or=")
        val oemPoSupplier = commonUtils.listToString(
            shopService.findM18VendorIdsByCodeRegexp(m18Config.OEM_PO_SUPPLIER),
            "venId=equal=",
            "=or="
        )
        val oemPoConds = "(${oemPoBuyers})=and=(${oemPoSupplier})=and=(${lastModifyDateConds})"
        println("oemPoConds: ${oemPoConds}")
        val oemPoParams = M18PurchaseOrderListRequest(
            params = null,
            conds = oemPoConds
        )

        try {
            purchaseOrders.valuesWithType += Pair(
                PurchaseOrderType.OEM, apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
                    M18_FETCH_PURCHASE_ORDER_LIST_API,
                    oemPoParams
                ).block()
            )
        } catch (e: Exception) {
            logger.error("(Getting OEM Po list) Error on Function - ${e.stackTrace}")
            logger.error(e.message)
        } */

        return purchaseOrders
    }

    open fun getPurchaseOrder(id: Long): M18PurchaseOrderResponse? {
        val purchaseOrderParams = M18PurchaseOrderRequest(
            id = id
        )

        var purchaseOrder: M18PurchaseOrderResponse? = null

        try {
            purchaseOrder = apiCallerService.get<M18PurchaseOrderResponse, M18PurchaseOrderRequest>(
                M18_LOAD_PURCHASE_ORDER_API,
                purchaseOrderParams
            ).block()
        } catch (e: Exception) {
            logger.error("(Getting Po Detail) Error on Function - ${e.stackTrace}")
            logger.error(e.message)
        }

        return purchaseOrder
    }

    open fun savePurchaseOrders() {
        logger.info("--------------------------------------------Start - Saving M18 Purchase Order--------------------------------------------")
        val purchaseOrdersWithType = getPurchaseOrdersWithType()
        val examplePurchaseOrders = listOf<Long>(4764034L)

        val successList = mutableListOf<Long>()
        val successDetailList = mutableListOf<Long>()
        val failList = mutableListOf<Long>()
        val failDetailList = mutableListOf<Long>()

        val poRefType = "Purchase Order"
        val poLineRefType = "Purchase Order Line"

        if (purchaseOrdersWithType != null) {
            // Loop for Purchase Orders (values)
            purchaseOrdersWithType.valuesWithType.forEach { purchaseOrderWithType ->
                val type = purchaseOrderWithType.first
                // if success
                val purchaseOrdersValues = purchaseOrderWithType.second?.values
                // if fail
                val purchaseOrdersMessages = purchaseOrderWithType.second?.messages

                if (purchaseOrdersValues != null) {
                    purchaseOrdersValues.forEach { purchaseOrder ->
                        val purchaseOrderDetail = getPurchaseOrder(purchaseOrder.id)

                        var purchaseOrderId: Long? = null //FP-MTMS

                        // Process for Purchase Order (mainpo)
                        // Assume only one PO in the PO (search by PO ID)
                        val mainpo = purchaseOrderDetail?.data?.mainpo?.get(0)
                        val pot = purchaseOrderDetail?.data?.pot
                        val purchaseOrderLineMessage = purchaseOrderDetail?.messages

                        // purchase_order + m18_data_log table
                        if (mainpo != null) {
                            // Find the latest m18 data log by m18 id & type
                            logger.info("${poRefType}: Finding For Latest M18 Data Log...")
                            val latestPurchaseOrderLog =
                                m18DataLogService.findLatestM18DataLogWithSuccess(purchaseOrder.id, poRefType)

                            logger.info(latestPurchaseOrderLog.toString())
                            // Save to m18_data_log table
                            logger.info("${poRefType}: Saving for M18 Data Log...")
                            val mainpoJson =
                                mainpo::class.memberProperties.associate { prop -> prop.name to prop.getter.call(mainpo) }
                                    .toMutableMap()

                            val saveM18PurchaseOrderLogRequest = SaveM18DataLogRequest(
                                id = null,
                                refType = poRefType,
                                m18Id = purchaseOrder.id,
                                m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
//                                dataLog = mainpoJson,
                                statusEnum = M18DataLogStatus.NOT_PROCESS
                            )

                            val saveM18PurchaseOrderLog =
                                m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLogRequest)
//                    logger.info("${poRefType}: Saved M18 Data Log. ID: ${saveM18PurchaseOrderLog.id}")

                            try {
                                // Find the purchase_order if exist
                                logger.info("${poRefType}: Finding exising purchase order...")
                                val existingPurchaseOrder =
                                    latestPurchaseOrderLog?.id?.let { purchaseOrderService.findByM18DataLogId(it) }
                                logger.info("${poRefType}: Exising purchase order ID: ${existingPurchaseOrder?.id}")

                                // Save to purchase_order table
                                logger.info("${poRefType}: Saving purchase order...")
                                val savePurchaseOrderRequest = SavePurchaseOrderRequest(
                                    id = existingPurchaseOrder?.id,
                                    code = mainpo.code,
                                    m18SupplierId = mainpo.venId,
                                    m18ShopId = mainpo.virDeptId,
                                    m18CurrencyId = mainpo.curId,
                                    orderDate = commonUtils.timestampToLocalDateTime(mainpo.tDate),
                                    estimatedArrivalDate = commonUtils.timestampToLocalDateTime(mainpo.dDate),
                                    completeDate = null,
                                    status = PurchaseOrderStatus.PENDING.value,
                                    type = type.value,
                                    m18DataLogId = saveM18PurchaseOrderLog.id,
                                )

                                val savePurchaseOrderResponse =
                                    purchaseOrderService.savePurchaseOrder(savePurchaseOrderRequest)
                                purchaseOrderId = savePurchaseOrderResponse.id

                                // Update m18_data_log with success
                                val successSaveM18PurchaseOrderLogRequest = SaveM18DataLogRequest(
                                    id = saveM18PurchaseOrderLog.id,
                                    dataLog = mainpoJson,
                                    statusEnum = M18DataLogStatus.SUCCESS
                                )

                                m18DataLogService.saveM18DataLog(successSaveM18PurchaseOrderLogRequest)

                                // log success info
                                successList.add(purchaseOrder.id)
                                logger.info("${poRefType}: Saved purchase order. ID: ${savePurchaseOrderResponse.id} | M18 ${poRefType} ID: ${purchaseOrder.id}")

                            } catch (e: Exception) {
                                failList.add(purchaseOrder.id)
                                logger.error("${poRefType}: Saving Failure!")
                                logger.error("Error on Function - ${e.stackTrace} | Type: ${poRefType} | M18 ID: ${purchaseOrder.id} | Different? ${mainpo.id}")
                                logger.error(e.message)

                                val errorSaveM18PurchaseOrderLogRequest = SaveM18DataLogRequest(
                                    id = saveM18PurchaseOrderLog.id,
                                    dataLog = mutableMapOf(Pair("Exception Message", e.message)),
                                    statusEnum = M18DataLogStatus.FAIL
                                )

                                m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLogRequest)
                                logger.error("${poRefType}: M18 Data Log Updated! Please see the error. ID: ${saveM18PurchaseOrderLogRequest.id}")
                            }

                            // purchase_order_line + m18_data_log
                            // TODO: check deleted po line?
                            if (pot != null) {
                                // Loop for Purchase Order Lines (pot)
                                pot.forEach { line ->

                                    // Find the latest m18 data log by m18 id & type
                                    logger.info("${poLineRefType}: Finding For Latest M18 Data Log...")
                                    val latestPurchaseOrderLineLog =
                                        m18DataLogService.findLatestM18DataLogWithSuccess(line.id, poLineRefType)
                                    // logger.info("${poLineRefType}: Latest M18 Data Log ID: ${latestPurchaseOrderLineLog?.id}")

                                    // Save to m18_data_log table
                                    logger.info("${poLineRefType}: Saving for M18 Data Log...")
                                    val lineJson =
                                        line::class.memberProperties.associate { prop ->
                                            prop.name to prop.getter.call(
                                                line
                                            )
                                        }
                                            .toMutableMap()
                                    val saveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
                                        id = null,
                                        refType = poLineRefType,
                                        m18Id = line.id,
                                        m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
//                                        dataLog = lineJson,
                                        statusEnum = M18DataLogStatus.NOT_PROCESS
                                    )

                                    val saveM18PurchaseOrderLineLog =
                                        m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLineLogRequest)

                                    // logger.info("${poLineRefType}: Saved M18 Data Log. ID: ${saveM18PurchaseOrderLineLog.id}")
                                    logger.info("${poLineRefType}: Finding item...")
                                    val item = itemsService.findByM18Id(line.proId)
                                    logger.info("${poLineRefType}: Item ID: ${item?.id} | M18 Item ID: ${line.proId}")

                                    try {
                                        // Find the purchase_order_line if exist
                                        logger.info("${poLineRefType}: Finding exising purchase order line...")
                                        val existingPurchaseOrderLine = latestPurchaseOrderLineLog?.id?.let {
                                            purchaseOrderLineService.findPurchaseOrderLineByM18Id(it)
                                        }
                                        logger.info("${poLineRefType}: Exising purchase order line ID: ${existingPurchaseOrderLine?.id}")

                                        // Save to purchase_order_line table
                                        logger.info("${poLineRefType}: Saving purchase order line...")
                                        val itemUom = item?.id?.let { itemUomService.findPurchaseUnitByItemId(it) }
                                        val savePurchaseOrderLineRequest = SavePurchaseOrderLineRequest(
                                            id = existingPurchaseOrderLine?.id,
                                            itemId = item?.id,
                                            uomId = itemUom?.uom?.id,
                                            purchaseOrderId = purchaseOrderId,
                                            qty = line.qty,
                                            price = line.amt,
//                                            m18CurrencyId = mainpo.curId,
                                            status = existingPurchaseOrderLine?.status?.value
                                                ?: PurchaseOrderLineStatus.PENDING.value,
                                            m18DataLogId = saveM18PurchaseOrderLineLog.id,
                                        )

                                        val savePurchaseOrderLineResponse =
                                            purchaseOrderLineService.savePurchaseOrderLine(savePurchaseOrderLineRequest)

                                        // Update m18_data_log with success
                                        val successSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
                                            id = saveM18PurchaseOrderLineLog.id,
                                            dataLog = lineJson,
                                            statusEnum = M18DataLogStatus.NOT_PROCESS
                                        )

                                        m18DataLogService.saveM18DataLog(successSaveM18PurchaseOrderLineLogRequest)

                                        // log success info
                                        successDetailList.add(line.id)
                                        logger.info("${poLineRefType}: Purchase order ID: ${purchaseOrderId} | M18 ID: ${purchaseOrder.id}")
                                        logger.info("${poLineRefType}: Saved purchase order line. ID: ${savePurchaseOrderLineResponse.id} | M18 Line ID: ${line.id}")
                                    } catch (e: Exception) {
                                        failDetailList.add(line.id)
                                        logger.error("${poLineRefType}: Saving Failure!")
                                        logger.error("Error on Function - ${e.stackTrace} | Type: ${poLineRefType} | M18 ID: ${line.id}")
                                        logger.error(e.message)

                                        val errorSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
                                            id = saveM18PurchaseOrderLineLog.id,
                                            dataLog = mutableMapOf(Pair("Exception Message", e.message)),
                                            statusEnum = M18DataLogStatus.FAIL
                                        )

                                        m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLineLogRequest)
                                        logger.error("${poLineRefType}: M18 Data Log Updated! Please see the error. ID: ${saveM18PurchaseOrderLineLog.id}")
                                    }
                                }
                            } else {
                                // pot
                                logger.error("${poLineRefType}: Saving Failure!")
                                val saveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
                                    id = null,
                                    refType = "${poLineRefType}",
                                    m18Id = purchaseOrder.id,
                                    m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
//                                    dataLog = mutableMapOf(Pair("Error Message", "${poLineRefType} is null")),
                                    dataLog = mutableMapOf(
                                        Pair(
                                            "${poLineRefType} Error Message",
                                            "pot is null"
                                        ),
                                        Pair(
                                            "${poLineRefType} Error Code",
                                            purchaseOrderLineMessage?.get(0)?.msgCode ?: "No Msg Code from M18"
                                        ),
                                        Pair(
                                            "${poLineRefType} Error Detail",
                                            purchaseOrderLineMessage?.get(0)?.msgDetail ?: "No Msg Detail from M18"
                                        ),
                                    ),
                                    statusEnum = M18DataLogStatus.FAIL
                                )

                                val errorLog = m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLineLogRequest)
                                logger.error("${poLineRefType}: M18 Data Log Updated! Please see the error. ID: ${errorLog.id}")
                            }
                        } else {
                            // mainpo
                            failList.add(purchaseOrder.id)
                            logger.error("${poRefType}: Saving Failure!")
                            val saveM18DataLogRequest = SaveM18DataLogRequest(
                                id = null,
                                refType = "${poRefType}",
                                m18Id = purchaseOrder.id,
                                // m18LastModifyDate = if(mainpo?.lastModifyDate != null) commonUtils.instantToLocalDateTime(mainpo.lastModifyDate) else LocalDateTime.now(),
                                m18LastModifyDate = LocalDateTime.now(),
                                dataLog = mutableMapOf(
                                    Pair(
                                        "${poRefType} Error",
                                        "mainpo is null"
                                    ),
                                    Pair(
                                        "${poRefType} Error Code",
                                        purchaseOrdersMessages?.get(0)?.msgCode ?: "No Msg Code from M18"
                                    ),
                                    Pair(
                                        "${poRefType} Error Detail",
                                        purchaseOrdersMessages?.get(0)?.msgDetail ?: "No Msg Detail from M18"
                                    ),
                                ),
                                statusEnum = M18DataLogStatus.FAIL
                            )

                            val errorLog = m18DataLogService.saveM18DataLog(saveM18DataLogRequest)
                            logger.error("${poLineRefType}: M18 Data Log Updated! Please see the error. ID: ${errorLog.id}")
                        }
                    }
                } else {
                    logger.error("${poRefType} List is null. May occur errors.")
                }
            }
        } else {
            logger.error("${poRefType} List is null. May occur errors.")
        }

        // End of save. Check result
        logger.info("Total Success (${poRefType}) (${successList.size}): $successList")
//        if (failList.size > 0) {
        logger.error("Total Fail (${poRefType}) (${failList.size}): $failList")
//        }

        logger.info("Total Success (${poLineRefType}) (${successDetailList.size}): $successDetailList")
//        if (failDetailList.size > 0) {
        logger.error("Total Fail (${poLineRefType}) (${failDetailList.size}): $failDetailList")
//        }
        logger.info("--------------------------------------------End - Saving M18 Purchase Order--------------------------------------------")
    }
}