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.model.*
import com.ffii.fpsms.m18.utils.CommonUtils
import com.ffii.fpsms.modules.master.entity.UomConversion
import com.ffii.fpsms.modules.master.enums.ShopType
import com.ffii.fpsms.modules.master.service.*
import com.ffii.fpsms.modules.master.web.models.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Service
open class M18MasterDataService(
    val m18Config: M18Config,
    val apiCallerService: ApiCallerService,
    val itemsService: ItemsService,
    val shopService: ShopService,
    val uomConversionService: UomConversionService,
    val currencyService: CurrencyService,
    val itemUomService: ItemUomService,
    val bomService: BomService,
    val bomMaterialService: BomMaterialService,
) {

    val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
    val commonUtils = CommonUtils()
    val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

    // M18 Conditions
    val lastModifyDate = LocalDate.now().minusDays(1)
    val lastModifyDateConds = "lastModifyDate=largerThan=$lastModifyDate"
    val seriesIdList =
        listOf(m18Config.SERIESID_SC, m18Config.SERIESID_SE, m18Config.SERIESID_SF, m18Config.SERIESID_SR)
    val seriesIdConds =
        "(" + commonUtils.listToString(seriesIdList.filterNotNull(), "seriesId=unequal=", "=or=") + ")"
    val beIdList = listOf(m18Config.BEID_PF, m18Config.BEID_PP, m18Config.BEID_TOA)
    val beIdConds = "(" + commonUtils.listToString(beIdList.filterNotNull(), "beId=equal=", "=or=") + ")"
//    val beIdConds = commonUtils.BEID_CONDS

    // M18 API
    val M18_COMMON_FETCH_LIST_API = "/search/search"
    val M18_COMMON_LOAD_LINE_API = "/root/api/read"

    val M18_LOAD_PRODUCT_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.PRODUCT.value}"
    val M18_LOAD_VENDOR_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.VENDOR.value}"
    val M18_LOAD_UNIT_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.UNIT.value}"
    val M18_LOAD_CURRENCY_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.CURRENCY.value}"
    val M18_LOAD_BOM_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.BOM.value}"
    val M18_LOAD_BUSINESS_UNIT_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.BUSINESS_UNIT.value}" // for shop po?

    // --------------------------------------------- Common Function --------------------------------------------- ///
    private inline fun <reified T : Any> getList(
        stSearch: String?,
        params: String? = null,
        conds: String? = null,
    ): T? {
        val request = M18CommonListRequest(
            stSearch = stSearch,
            params = params,
            conds = conds
        )

        val response = apiCallerService.get<T, M18CommonListRequest>(
            M18_COMMON_FETCH_LIST_API,
            request
        ).block()

        return response
    }

    private inline fun <reified T : Any> getLine(
        id: Long,
        params: String?,
        api: String,
    ): T? {
        val request = M18CommonLineRequest(
            id = id,
            params = params,
        )

        val response = apiCallerService.get<T, M18CommonLineRequest>(
            api,
            request
        ).block()

        return response
    }

    // --------------------------------------------- Product --------------------------------------------- ///
    open fun getProducts(): M18ProductListResponse? {
        // seems no beId
        return getList<M18ProductListResponse>(
            stSearch = StSearchType.PRODUCT.value,
            params = null,
            conds = seriesIdConds
        )
//        val itemsParams = M18CommonListRequest(
//            stSearch = StSearchType.PRODUCT.value,
//            params = null,
//            conds = seriesIdConds
//        )
//
//        val items = apiCallerService.get<M18ProductListResponse, M18CommonListRequest>(
//            M18_COMMON_FETCH_LIST_API,
//            itemsParams
//        ).block()
//
//        return items
    }

    open fun getProduct(id: Long): M18ProductResponse? {
        logger.info("M18 Product ID: $id")
        return getLine<M18ProductResponse>(
            id = id,
            params = null,
            api = M18_LOAD_PRODUCT_API
        )
    }

    open fun saveProducts() {
        logger.info("--------------------------------------------Start - Saving M18 Products / Materials--------------------------------------------")
        val items = getProducts()
        val exampleProducts = listOf<Long>(10946L, 3825L)

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

        val values = items?.values?.sortedBy { it.id }
        if (values != null) {
            values.forEach { item ->
//                if (item.id in exampleProducts) {
                try {
                    val itemDetail = getProduct(item.id)
                    val pro = itemDetail?.data?.pro?.get(0)
                    val price = itemDetail?.data?.price

                    if (itemDetail != null && pro != null) {
                        val existingItem = itemsService.findByM18Id(item.id)
                        val saveItemRequest = NewItemRequest(
                            code = pro.code,
                            name = pro.desc,
//                            type = if (pro.seriesId == m18Config.SERIESID_PF) ProductType.MATERIAL
//                            else ItemType.PRODUCT,
                            type = ItemType.MATERIAL,
                            id = existingItem?.id,
                            description = pro.desc,
                            remarks = null,
                            shelfLife = null,
                            countryOfOrigin = null,
                            maxQty = null,
                            m18Id = item.id,
                            m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate)
                        )

                        val savedItem = itemsService.saveItem(saveItemRequest)
                        logger.info("Processing item uom...")
                        // Find the item uom that ready to delete (not in m18)
                        val existingItemUoms = savedItem.id?.let { itemUomService.findAllByItemsId(it) }
                        val m18ItemUomIds = price?.map { it.id } ?: listOf()

                        // Delete the item uom
                        logger.info("Deleting item uom...")
//                        logger.info("Item Uom: ${existingItemUoms?.map { it.m18Id }}")
//                        logger.info("M18: ${m18ItemUomIds}")
                        existingItemUoms?.filter { it.m18Id !in m18ItemUomIds }?.mapNotNull { it.id }
                            ?.let { itemUomService.deleteItemUoms(it) }

                        // Update the item uom
                        logger.info("Updating item uom...")
                        price?.forEach {
                            val itemUomRequest = ItemUomRequest(
                                m18UomId = it.unitId,
                                itemId = savedItem.id,
                                baseUnit = it.basicUnit,
                                stockUnit = it.stkUnit,
                                pickingUnit = it.pickUnit,
                                salesUnit = it.saleUnit,
                                purchaseUnit = it.purUnit,
                                price = null,
                                currencyId = null,
                                m18Id = it.id,
                                m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate)
                            )

//                            logger.info("saved item id: ${savedItem.id}")
                            itemUomService.saveItemUom(itemUomRequest)
                        }

                        successList.add(item.id)
                        logger.info("Success Count ${successList.size}: ${item.id} | ${pro.code} | ${pro.desc}")
                    } else {
                        failList.add(item.id)
                        logger.error("Fail Message: ${itemDetail?.messages?.get(0)?.msgDetail}")
                        logger.error("Fail Count ${failList.size}: Item ID - ${item.id} Not Found")
                    }
                } catch (e: Exception) {
                    failList.add(item.id)
                    logger.error("Exception")
                    logger.error("Fail Message: ${e.message}")
                    logger.error("Fail Count ${failList.size}: Item ID - ${item.id}")
                }
            }
        } else {
            logger.error("Items List is null. May occur errors.")
        }

        logger.info("Total Success (${successList.size})")

        if (failList.size > 0) {
            logger.error("Total Fail (${failList.size}): $failList")
        }

        logger.info("--------------------------------------------End - Saving M18 Products / Materials--------------------------------------------")
    }

    // --------------------------------------------- Vendor --------------------------------------------- ///
    open fun getVendors(): M18VendorListResponse? {
        return getList<M18VendorListResponse>(
            stSearch = StSearchType.VENDOR.value,
            params = null,
            conds = beIdConds
        )
    }

    open fun getVendor(id: Long): M18VendorResponse? {
        logger.info("M18 Vendor ID: $id")
        return getLine<M18VendorResponse>(
            id = id,
            params = null,
            api = M18_LOAD_VENDOR_API
        )
    }

    open fun saveVendors() {
        logger.info("--------------------------------------------Start - Saving M18 Vendors--------------------------------------------")
        val vendors = getVendors()
        val exampleVendors = listOf<Long>(191L)

        val successList = mutableListOf<Long>()
        val failList = mutableListOf<Long>()
        val values = vendors?.values?.sortedBy { it.id }

        if (values != null) {
            values.forEach { vendor ->
//                if (vendor.id in exampleVendors) {
                try {
                    val vendorDetail = getVendor(vendor.id)

                    if (vendorDetail != null && vendorDetail.data?.ven != null) {
                        val ven = vendorDetail.data.ven[0]

                        val saveShopRequest = SaveShopRequest(
                            id = null,
                            code = ven.code,
                            name = ven.desc.ifEmpty { ven.`desc_zh-TW` },
                            brNo = null,
                            contactNo = ven.tel,
                            contactEmail = ven.email,
                            contactName = null,
                            addr1 = ven.ad1,
                            addr2 = ven.ad2,
                            addr3 = ven.ad3,
                            addr4 = ven.ad4,
                            district = null,
                            type = ShopType.SUPPLIER.value,
                            m18Id = vendor.id,
                            m18LastModifyDate = commonUtils.timestampToLocalDateTime(ven.lastModifyDate)
                        )

                        shopService.saveShop(saveShopRequest)
                        successList.add(vendor.id)
                        logger.info("Success Count ${successList.size}: ${vendor.id} | ${ven.code} | ${ven.desc}")
                    } else {
                        failList.add(vendor.id)
                        logger.error("Fail Message: ${vendorDetail?.messages?.get(0)?.msgDetail}")
                        logger.error("Fail Count ${failList.size}: Vendor ID - ${vendor.id} Not Found")
                    }
                } catch (e: Exception) {
                    failList.add(vendor.id)
                    logger.error("Exception")
                    logger.error("Fail Message: ${e.message}")
                    logger.error("Fail Count ${failList.size}: Vendor ID - ${vendor.id}")
                }
//                }
            }
        } else {
            logger.error("Vendor List is null. May occur errors.")
        }

        logger.info("Total Success (${successList.size})")

        if (failList.size > 0) {
            logger.error("Total Fail (${failList.size}): $failList")
        }
        logger.info("--------------------------------------------End - Saving M18 Vendors--------------------------------------------")
    }

    // --------------------------------------------- Unit (UoM) --------------------------------------------- ///
    open fun getUnits(): M18UnitListResponse? {
        // seems no beId
        return getList<M18UnitListResponse>(
            stSearch = StSearchType.UNIT.value,
            params = null,
            conds = null
        )
    }

    open fun getUnit(id: Long): M18UnitResponse? {
        logger.info("M18 Unit ID: $id")
        return getLine<M18UnitResponse>(
            id = id,
            params = null,
            api = M18_LOAD_UNIT_API
        )
    }

    open fun saveUnits() {
        logger.info("--------------------------------------------Start - Saving M18 Units--------------------------------------------")
        val units = getUnits()

        val successTransformList = mutableListOf<Long>()
        val successSaveList = mutableListOf<Long>()
        val failTransformList = mutableListOf<Long>()
        val failSaveList = mutableListOf<Long>()

        val values = units?.values?.sortedBy { it.id }
        if (values != null) {
            val finalUnitList = arrayListOf<UomConversion>()

            // transform unit
            values.forEach { unit ->
                try {
                    val tempObject = UomConversionService.BomObject().apply {
                        code = unit.code
                        udfudesc = unit.udfudesc
                        lastModifyDate = unit.lastModifyDate
                        id = unit.id
                    }
                    finalUnitList += uomConversionService.transformItem(tempObject)
                    successTransformList += unit.id
                    logger.info("Transform Success (M18): ${unit.id}")
                } catch (e: Exception) {
                    failTransformList.add(unit.id)
                    logger.error("Transform Exception")
                    logger.error("Transform Fail Message: ${e.message}")
                    logger.error("Transform Fail Count ${failTransformList.size}: Unit ID - ${unit.id}")
                }
            }

            uomConversionService.calculateSizeInGram(finalUnitList)

            finalUnitList.forEach {
                try {
                    uomConversionService.saveUomConversion(it)
                    successSaveList += it.m18Id
                    logger.info("Save Success (M18): ${it.m18Id}")
                } catch (e: Exception) {
                    failSaveList.add(it.m18Id)
                    logger.error("Save Exception")
                    logger.error("Save Fail Message: ${e.message}")
                    logger.error("Save Fail Count ${failTransformList.size}: Unit ID - ${it.m18Id}")
                }
            }
        } else {
            logger.error("Unit List is null. May occur errors.")
        }

        logger.info("Total Transform Success (${successTransformList.size})")
        logger.info("Total Save Success (${successSaveList.size})")

        if (failTransformList.size > 0) {
            logger.error("Total Transform Fail (${failTransformList.size}): $failTransformList")
        }

        if (failSaveList.size > 0) {
            logger.error("Total Save Fail (${failSaveList.size}): $failSaveList")
        }
        logger.info("--------------------------------------------End - Saving M18 Units--------------------------------------------")
    }

    // --------------------------------------------- Currency --------------------------------------------- ///
    open fun getCurrencies(): M18CurrencyListResponse? {
        return getList<M18CurrencyListResponse>(
            stSearch = StSearchType.CURRENCY.value,
            params = null,
            conds = null
        )
    }

    open fun getCurrency(id: Long): M18CurrencyResponse? {
        logger.info("M18 Currency ID: $id")
        return getLine<M18CurrencyResponse>(
            id = id,
            params = null,
            api = M18_LOAD_CURRENCY_API
        )
    }

    open fun saveCurrencies() {
        logger.info("--------------------------------------------Start - Saving M18 Currencies--------------------------------------------")
        val currencies = getCurrencies()

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

        val values = currencies?.values?.sortedBy { it.id }
        if (values != null) {
            // save currency
            values.forEach { currency ->
                try {
                    val currencyRequest = SaveCurrencyRequest(
                        id = null,
                        code = currency.code,
                        name = currency.sym,
                        description = currency.curDesc,
                        m18Id = currency.id,
                        m18LastModifyDate = LocalDateTime.parse(currency.lastModifyDate, formatter)
                    )

                    currencyService.saveCurrency(currencyRequest)
                    successList += currency.id
                    logger.info("Save Success (M18): ${currency.id}")
                } catch (e: Exception) {
                    failList += currency.id
                    logger.error("Exception")
                    logger.error("Fail Message: ${e.message}")
                    logger.error("Fail Count ${failList.size}: Unit ID - ${currency.id}")
                }
            }
        } else {
            logger.error("Currency List is null. May occur errors.")
        }

        logger.info("Total Save Success (${successList.size})")

        if (failList.size > 0) {
            logger.error("Total Fail (${failList.size}): $failList")
        }

        logger.info("--------------------------------------------End - Saving Currencies--------------------------------------------")
    }

    // --------------------------------------------- Bom --------------------------------------------- ///
    open fun getBoms(): M18BomListResponse? {
        return getList<M18BomListResponse>(
            stSearch = StSearchType.BOM.value,
            params = null,
            conds = beIdConds
        )
    }

    open fun getBom(id: Long): M18BomResponse? {
        logger.info("M18 Bom ID: $id")
        return getLine<M18BomResponse>(
            id = id,
            params = null,
            api = M18_LOAD_BOM_API
        )
    }

    open fun saveBoms() {
        logger.info("--------------------------------------------Start - Saving M18 Boms--------------------------------------------")
        val boms = getBoms()

        val successList = mutableListOf<Long>()
        val successDetailList = mutableListOf<Long>()
        val failList = mutableListOf<Long>()
        val failDetailList = mutableListOf<Pair<Long, MutableList<Long>>>()
        var failDetailCount = 0

        val values = boms?.values?.sortedBy { it.id }
        if (values != null) {
            values.forEach { bom ->
                try {
                    val bomDetail = getBom(bom.id)
                    val bomUdfBomForShop = bomDetail?.data?.udfbomforshop?.get(0)
                    val bomUdfProduct = bomDetail?.data?.udfproduct

                    logger.info(bomUdfBomForShop.toString())
                    logger.info(bomUdfProduct.toString())
                    if (bomUdfBomForShop != null && bomUdfProduct != null) {
                        // Save Bom
                        val saveBomRequest = SaveBomRequest(
//                            itemId = itemsService.findByNameAndM18UomId(bomUdfBomForShop.desc, bomUdfBomForShop.udfUnit)?.id,
                            code = bomUdfBomForShop.code,
                            name = bomUdfBomForShop.desc,
                            description = bomUdfBomForShop.desc,
                            outputQty = if (bomUdfBomForShop.udfHarvest.trim().toBigDecimalOrNull() != null) bomUdfBomForShop.udfHarvest.trim().toBigDecimal() else BigDecimal(0),
                            outputQtyUom = bomUdfBomForShop.udfHarvestUnit,
                            yield = bomUdfBomForShop.udfYieldratePP,
                            m18UomId = bomUdfBomForShop.udfUnit,
                            m18Id = bomUdfBomForShop.id,
                            m18LastModifyDate = commonUtils.timestampToLocalDateTime(bomUdfBomForShop.lastModifyDate)
                        )
                        val bomId = bomService.saveBom(saveBomRequest).id
                        successList += bom.id

                        // Save Bom Material
                        logger.info("Start saving bom material...")
                        val tempFailList = mutableListOf<Long>()
                        bomUdfProduct.forEach { bomMaterial ->
                            try {
                                val saveBomMaterialRequest = SaveBomMaterialRequest(
                                    m18ItemId = bomMaterial.udfProduct,
                                    itemName = bomMaterial.udfIngredients,
                                    qty = bomMaterial.udfqty,
                                    m18UomId = bomMaterial.udfpurchaseUnit,
                                    uomName = bomMaterial.udfBaseUnit,
                                    bomId = bomId,
                                    m18Id = bomMaterial.id,
                                    m18LastModifyDate = commonUtils.timestampToLocalDateTime(bomUdfBomForShop.lastModifyDate)
                                )
                                bomMaterialService.saveBomMaterial(saveBomMaterialRequest)

                                successDetailList += bomMaterial.id
                            } catch (e: Exception) {
                                tempFailList += bomMaterial.id
                                logger.error("(Bom Material) Exception")
                                logger.error("(Bom Material) Fail Message: ${e.message}")
                                logger.error("(Bom Material) Fail Count ${++failDetailCount}: Bom Material ID - ${bomMaterial.id} | Bom ID - ${bom.id}")
                            }
                        }
                        failDetailList += Pair(bom.id, tempFailList)
                        logger.info("Save Success (M18): ${bom.id}")
                    } else {
                        failList.add(bom.id)
                        logger.error("(Bom) Fail Message: ${bomDetail?.messages?.get(0)?.msgDetail}")
                        logger.error("(Bom) Fail Count ${failList.size}: Bom ID - ${bom.id} Not Found")
                    }
                } catch (e: Exception) {
                    failList += bom.id
                    logger.error("(Bom) Exception")
                    logger.error("(Bom) Fail Message: ${e.message}")
                    logger.error("(Bom) Fail Count ${failList.size}: Bom ID - ${bom.id}")
                }
            }
        } else {
            logger.error("Currency List is null. May occur errors.")
        }

        logger.info("Total Bom Save Success (${successList.size})")
        logger.info("Total Bom Save Detail Success (${successDetailList.size})")

        if (failList.size > 0) {
            logger.error("Total Bom Fail (${failList.size}): $failList")
        }

        if (failDetailCount > 0) {
            logger.error("Total Bom Detail Fail (${failDetailCount}): $failDetailList")
        }

        logger.info("--------------------------------------------End - Saving Boms--------------------------------------------")
    }

    // --------------------------------------------- Business Unit (Shop) --------------------------------------------- ///
    open fun getBusinessUnits(): M18BusinessUnitListResponse? {
        // seems no beId
        return getList<M18BusinessUnitListResponse>(
            stSearch = StSearchType.BUSINESS_UNIT.value,
            params = null,
//            conds = beIdConds
        )
    }

    open fun getBusinessUnit(id: Long): M18BusinessUnitResponse? {
        logger.info("M18 Business Unit ID: $id")
        return getLine<M18BusinessUnitResponse>(
            id = id,
            params = null,
            api = M18_LOAD_BUSINESS_UNIT_API
        )
    }

    open fun saveBusinessUnits() {
        logger.info("--------------------------------------------Start - Saving M18 Business Units (Shops)--------------------------------------------")
        val businessUnits = getBusinessUnits()

        val successList = mutableListOf<Long>()
        val failList = mutableListOf<Long>()
        val values = businessUnits?.values?.sortedBy { it.id }
        val busMessages = if(businessUnits?.messages?.isNotEmpty() == true) businessUnits.messages[0] else null

        if (values != null) {
            values.forEach { businessUnit ->
//                if (vendor.id in exampleVendors) {
                try {
                    val businessUnitDetail = getBusinessUnit(businessUnit.id)
                    val virdept = businessUnitDetail?.data?.virdept?.get(0)
                    val buMessages = if(businessUnitDetail?.messages?.isNotEmpty() == true) businessUnitDetail?.messages[0] else null

                    if (virdept != null) {

                        val saveShopRequest = SaveShopRequest(
                            id = null,
                            code = virdept.code,
                            name = virdept.desc.ifEmpty { virdept.`desc_zh-TW` },
                            brNo = null,
                            contactNo = virdept.tel,
                            contactEmail = virdept.email,
                            contactName = null,
                            addr1 = virdept.addr.ifEmpty { virdept.addr_en },
                            addr2 = virdept.addr2.ifEmpty { virdept.addr2_en },
                            addr3 = virdept.addr3.ifEmpty { virdept.addr3_en },
                            addr4 = null,
                            district = null,
                            type = ShopType.SHOP.value,
                            m18Id = businessUnit.id,
                            m18LastModifyDate = commonUtils.timestampToLocalDateTime(virdept.lastModifyDate)
                        )

                        shopService.saveShop(saveShopRequest)
                        successList.add(businessUnit.id)
                        logger.info("Success Count ${successList.size}: ${businessUnit.id} | ${virdept.code} | ${virdept.desc}")
                    } else {
                        failList.add(businessUnit.id)
                        logger.error("(Business Unit) Fail Message: ${buMessages?.msgDetail}")
                        logger.error("(Business Unit) Fail Count ${failList.size}: Business Unit ID - ${businessUnit.id} Not Found")
                    }
                } catch (e: Exception) {
                    failList.add(businessUnit.id)
                    logger.error("(Business Unit) Exception")
                    logger.error("(Business Unit) Fail Message: ${e.message}")
                    logger.error("(Business Unit) Fail Count ${failList.size}: Business Unit ID - ${businessUnit.id}")
                }
//                }
            }
        } else {
            logger.error("(Business Unit) Business Unit List is null. May occur errors.")
            logger.error("(Business Unit)) Fail Message: ${busMessages?.msgDetail}")
            logger.error("(Business Unit) Business Unit List is null. May occur errors.")
        }

        logger.info("Total Success (${successList.size})")

        if (failList.size > 0) {
            logger.error("Total Fail (${failList.size}): $failList")
        }
        logger.info("--------------------------------------------End - Saving M18 Business Units--------------------------------------------")
    }
}