package com.ffii.fpsms.modules.master.service

import com.ffii.core.support.AbstractBaseEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service
import com.google.gson.Gson
import org.springframework.transaction.annotation.Transactional
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import java.sql.DriverManager.println
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.regex.Pattern


@Service
open class UomConversionService(
    private val jdbcDao: JdbcDao,
    private val uomConversionRepository: UomConversionRepository,
): AbstractBaseEntityService<UomConversion, Long, UomConversionRepository>(jdbcDao, uomConversionRepository)  {
    // do mapping with projection

    private val JSON_VAL_PATH = "dataExport/MTMS_bom_raw.json"

    @kotlin.Throws(IOException::class)
    open fun importFromM18() : ArrayList<UomConversion>{
        val resource = ClassPathResource(JSON_VAL_PATH)
        val templateInputStream = resource.inputStream

        // Read the JSON file into a String
        val jsonStringBuilder = StringBuilder()
        InputStreamReader(templateInputStream, StandardCharsets.UTF_8).use { reader ->
            BufferedReader(reader).forEachLine { line ->
                jsonStringBuilder.append(line)
            }
        }
        val gson = Gson()
        val bomData: BomData = gson.fromJson(jsonStringBuilder.toString(), BomData::class.java)
        val bomObjects = bomData.values
        val output = ArrayList<UomConversion>()
        for (i in bomObjects.indices) {
            //kotlin.io.println("${i + 1}. ${bomObjects[i]}");
            val transformedItem = transformItem(bomObjects[i])
            output.add(transformedItem)
        }
        calculateSizeInGram(output)
        return output;
    }

    open fun parseDate(dateString: String?): LocalDateTime {
        val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
        return LocalDateTime.parse(dateString, formatter)
    }

    //temp object for storing data from Gson conversion
    class BomData {
        var stSearch: String? = null
        var size = 0
        lateinit var values: Array<BomObject>
    }

    //json structure for m18 data which we would like to save in MTMS
    class BomObject {
        var code: String? = null
        var udfudesc: String? = null
        var udfShortDesc: String? = null
        var lastModifyDate: String? = null
        var id: Long? = null
    }


    open fun transformItem(item: BomObject): UomConversion {
        //Convert m18 bom reocrd to MTMS db structure record
        val code = if (item.code == null) "N/A" else item.code
        val udfudesc = item.udfudesc
        val udfShortDesc = item.udfShortDesc
        val id = item.id
        val lastModifyDate = parseDate(item.lastModifyDate)
        // Initialize quantities and units
        var unit1Qty = 1.0
        var unit2Qty = 0.0
        var unit3Qty = 0.0
        var unit4Qty = 0.0
        var unit1: String? = null
        var unit2: String? = null
        var unit3: String? = null
        var unit4: String? = null
        val sizeInGram = 0.0
        val gramPerSmallestUnit = 0.0

        // Regex to extract numbers (including decimals) and units
        val pattern =
            Pattern.compile("(?:(\\d*\\.?\\d+)?([A-Za-z]+))?(?:(\\d*\\.?\\d+)([A-Za-z]+))?(?:(\\d*\\.?\\d+)([A-Za-z]+))?(?:(\\d*\\.?\\d+)([A-Za-z]+))?")
        val matcher = pattern.matcher(code)
        if (matcher.find()) {
            // Capture groups
            val group1 = matcher.group(1)
            unit1Qty = if (group1 == null || group1.isEmpty()) 1.0 else group1.toDouble()
            unit1 = if (matcher.group(2) != null) matcher.group(2) else ""
            val group3 = matcher.group(3)
            unit2Qty = if (group3 == null || group3.isEmpty()) 0.0 else group3.toDouble()
            unit2 = if (matcher.group(4) != null) matcher.group(4) else ""
            val group5 = matcher.group(5)
            unit3Qty = if (group5 == null || group5.isEmpty()) 0.0 else group5.toDouble()
            unit3 = if (matcher.group(6) != null) matcher.group(6) else ""
            val group7 = matcher.group(7)
            unit4Qty = if (group7 == null || group7.isEmpty()) 0.0 else group7.toDouble()
            unit4 = if (matcher.group(8) != null) matcher.group(8) else ""
        }

        // Create the transformed JSON object
        val transformedItem = UomConversion()
        transformedItem.code = code
        transformedItem.udfudesc = udfudesc
        transformedItem.udfShortDesc = udfShortDesc
        transformedItem.unit1 = unit1
        transformedItem.unit1Qty = unit1Qty
        transformedItem.unit2 = unit2
        transformedItem.unit2Qty = unit2Qty
        transformedItem.unit3 = unit3
        transformedItem.unit3Qty = unit3Qty
        transformedItem.unit4 = unit4
        transformedItem.unit4Qty = unit4Qty
        transformedItem.sizeInGram = sizeInGram
        transformedItem.gramPerSmallestUnit = gramPerSmallestUnit
        if (id != null) {
            transformedItem.m18Id = id
        }
        transformedItem.lastModifyDate = lastModifyDate
        return transformedItem
    }

    open fun calculateSizeInGram(output: ArrayList<UomConversion>) {
        // Define the conversion factors
        val units: MutableMap<String, Double> = HashMap()
        units.put("KG", 1000.0)
        units.put("L", 1000.0) //should be dynamic
        units.put("ML", 1.0) //should be dynamic
        units.put("GAL", 3785.4117)//should be dynamic
        units.put("G", 1.0)
        units.put("LB", 453.59)
        units.put("POUNDS", 453.59)
        units.put("CATTY", 604.78982)
        units.put("OZ", 28.3495)
        units.put("TAEL", 37.5)
        
        for (item in output) {
            var totalGram = 0.0
            var smallestUnitGram = 0.0

            // Calculate based on unit1
            if (item.unit1 != null && item.unit1Qty != null) {
                smallestUnitGram += item.unit1Qty!! // Assuming unit1 is a direct quantity
            }

            // Calculate based on unit2
            if (item.unit2 != null && item.unit2Qty != null) {
                if (units.containsKey(item.unit2!!.uppercase(Locale.getDefault()))) {
                    totalGram += item.unit1Qty!! * item.unit2Qty!! * units[item.unit2!!.uppercase(Locale.getDefault())]!!
                    smallestUnitGram = item.unit2Qty!! * units[item.unit2!!.uppercase(Locale.getDefault())]!!
                }
            }

            // Calculate based on unit3
            if (item.unit3 != null && item.unit3Qty != null) {
                if (units.containsKey(item.unit3!!.uppercase(Locale.getDefault()))) {
                    totalGram += item.unit1Qty!! * item.unit2Qty!! * item.unit3Qty!! * units[item.unit3!!.uppercase(Locale.getDefault())]!!
                    smallestUnitGram = item.unit3Qty!! * units[item.unit3!!.uppercase(Locale.getDefault())]!!
                }
            }

            // Calculate based on unit4
            if (item.unit4 != null && item.unit4Qty != null) {
                if (units.containsKey(item.unit4!!.uppercase(Locale.getDefault()))) {
                    totalGram += item.unit1Qty!! * item.unit2Qty!! * item.unit3Qty!! * item.unit4Qty!! * units[item.unit4!!.uppercase(Locale.getDefault())]!!
                    smallestUnitGram = item.unit4Qty!! * units[item.unit4!!.uppercase(Locale.getDefault())]!!
                }
            }

            if (units.containsKey(item.unit1!!.uppercase(Locale.getDefault())) ||
                units.containsKey(item.unit2!!.uppercase(Locale.getDefault())) ||
                units.containsKey(item.unit3!!.uppercase(Locale.getDefault())) ||
                units.containsKey(item.unit4!!.uppercase(Locale.getDefault()))
            ) {
                // If any valid conversion was made, set sizeInGram and gramPerSmallestUnit
                item.sizeInGram = totalGram
                item.gramPerSmallestUnit = smallestUnitGram
            } else {
                // Set to 0 if no valid units found
                item.sizeInGram = 0.0
                item.gramPerSmallestUnit = 0.0
            }
        }
    }
    open fun findAll(): List<UomConversion> {
        return uomConversionRepository.findAllByDeletedFalse();
    }

    open fun findById(id: Long): UomConversion? {
        return uomConversionRepository.findByIdAndDeletedFalse(id)
    }

    open fun findByM18Id(m18Id: Long) : UomConversion? {
        return uomConversionRepository.findByM18IdAndDeletedFalse(m18Id)
    }
    @Throws(IOException::class)
    @Transactional
    open fun saveUomConversion(newUomConversion: UomConversion): MessageResponse {
//        val duplicatedItem = uomConversionRepository.findByLastModifyDateAndM18IdAndDeletedFalse(newUomConversion.lastModifyDate, newUomConversion.m18Id)
//        if (duplicatedItem != null && duplicatedItem.id != newUomConversion.id) {
//            return MessageResponse(
//                id = newUomConversion.id,
//                code = newUomConversion.code,
//                name = newUomConversion.udfudesc,
//                type = "UOM update to date",
//                message = "BOM with M18Id = ${duplicatedItem.m18Id} is already up to date",
//                errorPosition = "code"
//            )
//        }

        val uomConversion = findByM18Id(newUomConversion.m18Id) ?:
        if (newUomConversion.id != null && newUomConversion.id > 0)
            uomConversionRepository.findByIdAndDeletedFalse(newUomConversion.id)
        else UomConversion()

        if (uomConversion == null) {
            // Handle the case where the item does not exist
            return MessageResponse(
                id = null,
                code = newUomConversion.code,
                name = newUomConversion.udfudesc,
                type = "Error",
                message = "UOM conversion not found for ID: ${newUomConversion.id}",
                errorPosition = "database"
            )
        }

        uomConversion.apply {
            m18Id = newUomConversion.m18Id
            code = newUomConversion.code
            udfudesc = newUomConversion.udfudesc
            udfShortDesc = newUomConversion.udfShortDesc
            unit1 = newUomConversion.unit1
            unit1Qty = newUomConversion.unit1Qty
            unit2 = newUomConversion.unit2
            unit2Qty = newUomConversion.unit2Qty
            unit3 = newUomConversion.unit3
            unit3Qty = newUomConversion.unit3Qty
            unit4 = newUomConversion.unit4
            unit4Qty = newUomConversion.unit4Qty
            sizeInGram = newUomConversion.sizeInGram
            gramPerSmallestUnit = newUomConversion.gramPerSmallestUnit
            lastModifyDate = newUomConversion.lastModifyDate
        }

        return try {
            val savedItem = uomConversionRepository.saveAndFlush(uomConversion)
            MessageResponse(
                id = savedItem.id,
                name = newUomConversion.code,
                code = newUomConversion.code,
                type = "M18BOM",
                message = "UOM Save Success",
                errorPosition = null
            )
        } catch (e: Exception) {
            // Log the exception
            throw RuntimeException("Error saving UOM conversion: ${e.message}", e)
        }
    }
}