package com.ffii.fpsms.modules.common.scheduler.service

import com.ffii.core.utils.JwtTokenUtil
import com.ffii.fpsms.m18.service.M18DeliveryOrderService
import com.ffii.fpsms.m18.service.M18MasterDataService
import com.ffii.fpsms.m18.service.M18PurchaseOrderService
import com.ffii.fpsms.m18.web.models.M18CommonRequest
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.master.service.ProductionScheduleService
import com.ffii.fpsms.modules.settings.service.SettingsService
import jakarta.annotation.PostConstruct
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.support.CronTrigger
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.HashMap
import java.util.concurrent.ScheduledFuture
import kotlin.jvm.optionals.getOrNull

@Service
open class SchedulerService(
    val settingsService: SettingsService,
    val taskScheduler: TaskScheduler,
    val productionScheduleService: ProductionScheduleService,
    val m18PurchaseOrderService: M18PurchaseOrderService,
    val m18DeliveryOrderService: M18DeliveryOrderService,
    val m18MasterDataService: M18MasterDataService,
) {
    var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
    val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
    val defaultCronExpression = "0 0 2 * * *";

    @Volatile
    var scheduledM18Po: ScheduledFuture<*>? = null

    @Volatile
    var scheduledM18Master: ScheduledFuture<*>? = null

    @Volatile
    var scheduledRoughProd: ScheduledFuture<*>? = null

    @Volatile
    var scheduledDetailedProd: ScheduledFuture<*>? = null

    // Common Function
    fun isValidCronExpression(cronExpression: String): Boolean {
        return try {
            CronTrigger(cronExpression)
            true
        } catch (e: IllegalArgumentException) {
            false
        }
    }

    fun commonSchedule(scheduled: ScheduledFuture<*>?, settingName: String, scheduleFunc: () -> Unit) {
        scheduled?.cancel(false)

        var cron = settingsService.findByName(settingName).getOrNull()?.value ?: defaultCronExpression;

        if (!isValidCronExpression(cron)) {
            cron = defaultCronExpression
        }
        scheduledM18Po = taskScheduler.schedule(
            {
                scheduleFunc()
            },
            CronTrigger(cron)
        )
    }

    // Init Scheduler
//    @PostConstruct
    fun init() {
//        scheduleM18PoTask();
        scheduleM18MasterData();
        scheduleRoughProd();
        scheduleDetailedProd();
    }

    // Scheduler
    // --------------------------- FP-MTMS --------------------------- //
    fun scheduleRoughProd() {
        commonSchedule(scheduledRoughProd, SettingNames.SCHEDULE_PROD_ROUGH, ::getRoughProdSchedule)
    }

    fun scheduleDetailedProd() {
        commonSchedule(scheduledDetailedProd, SettingNames.SCHEDULE_PROD_DETAILED, ::getDetailedProdSchedule)
    }

    // --------------------------- M18 --------------------------- //
    fun scheduleM18PoTask() {
        commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Pos)
    }

    fun scheduleM18MasterData() {
        commonSchedule(scheduledM18Master, SettingNames.SCHEDULE_M18_MASTER, ::getM18MasterData)
    }

    // Function for schedule
    // --------------------------- FP-MTMS --------------------------- //
    open fun getRoughProdSchedule() {
        try {
            logger.info("Daily Scheduler - Rough Prod")
            val demoFGList = productionScheduleService.convertToFinishedGoodList();

            val result: HashMap<ProductionScheduleService.RoughScheduleObj, Double> = productionScheduleService.generateRoughScheduleByWeek(demoFGList)
            val sortedEntries = result.entries.sortedBy { it.value }

            var accProdCount = 0.0;
            var fgCount = 0L;
            for ((roughScheduleRecord, totalDifference) in sortedEntries) {
                accProdCount += roughScheduleRecord.totalProductionCount;
                fgCount ++;
                println("[totalDifference:" + totalDifference + "] - " + roughScheduleRecord.toString())
            }

            // Convert to List<HashMap<RoughScheduleObj, Double>>
            productionScheduleService.saveRoughScheduleOutput(sortedEntries, accProdCount, fgCount);
        } catch (e: Exception) {
            throw RuntimeException("Error generate schedule: ${e.message}", e)
        }
    }

    open fun getDetailedProdSchedule() {
        try {
            logger.info("Daily Scheduler - Detailed Prod")
            // For test
            val today = LocalDateTime.now()

            // T, T+1, T+2
            for (day in longArrayOf(0L, 1L, 2L)) {
                val produceAt = today.plusDays(day)
                val latestRoughScheduleAt = productionScheduleService.getScheduledAtByDate(produceAt.toLocalDate());
                // assume schedule period is monday to sunday
                val assignDate = produceAt.dayOfWeek.value

                //TODO: update this to receive selectedDate and assignDate from schedule
                println("produceAt: $produceAt | today： $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ")
                productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt.atStartOfDay(), produceAt)
            }
        } catch (e: Exception) {
            throw RuntimeException("Error generate schedule: ${e.message}", e)
        }
    }

    // --------------------------- M18 --------------------------- //
//    @Async
//    @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY)
    open fun getM18Pos() {
        logger.info("Daily Scheduler - PO")
        val currentTime = LocalDateTime.now()
        val today = currentTime.toLocalDate().atStartOfDay()
        val yesterday = today.minusDays(1L)
        val request = M18CommonRequest(
            modifiedDateTo = today.format(dataStringFormat),
            modifiedDateFrom = yesterday.format(dataStringFormat)
        )
        m18PurchaseOrderService.savePurchaseOrders(request);
        m18DeliveryOrderService.saveDeliveryOrders(request);
//        logger.info("today: ${today.format(dataStringFormat)}")
//        logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
    }

    open fun getM18MasterData() {
        logger.info("Daily Scheduler - Master Data")
        val currentTime = LocalDateTime.now()
        val today = currentTime.toLocalDate().atStartOfDay()
        val yesterday = today.minusDays(1L)
        val request = M18CommonRequest(
            modifiedDateTo = today.format(dataStringFormat),
            modifiedDateFrom = yesterday.format(dataStringFormat)
        )
        m18MasterDataService.saveUnits(request)
        m18MasterDataService.saveProducts(request)
//        m18MasterDataService.saveBoms(request)
        m18MasterDataService.saveVendors(request)
        m18MasterDataService.saveBusinessUnits(request)
        m18MasterDataService.saveCurrencies(request)
//        logger.info("today: ${today.format(dataStringFormat)}")
//        logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
    }
}