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.m18.entity.SchedulerSyncLog
import com.ffii.fpsms.m18.entity.SchedulerSyncLogRepository
import com.ffii.fpsms.m18.model.SyncResult
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,
    val schedulerSyncLogRepository: SchedulerSyncLogRepository,
) {
    var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
    val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
    val dateTimeStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
    val defaultCronExpression = "0 0 2 31 2 *";

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

    var scheduledM18Do1: ScheduledFuture<*>? = null
    var scheduledM18Do2: 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): ScheduledFuture<*>? {
        scheduled?.cancel(false)

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

        if (!isValidCronExpression(cron)) {
            cron = defaultCronExpression
        }

        // Now Kotlin is happy because both the method and the function allow nulls
        return taskScheduler.schedule(
            { scheduleFunc() },
            CronTrigger(cron)
        )
    }

    // Init Scheduler
    @PostConstruct
    fun init() {
        //scheduleM18PoTask();
        scheduleM18Po();
        scheduleM18Do1();
        scheduleM18Do2();
        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() {
        //this is going to delete
        commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Pos)
    }

    fun scheduleM18Po() {
        commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Po)
    }

    fun scheduleM18Do1() {
        commonSchedule(scheduledM18Do1, SettingNames.SCHEDULE_M18_DO1, ::getM18Dos1)
    }

    fun scheduleM18Do2() {
        commonSchedule(scheduledM18Do2, SettingNames.SCHEDULE_M18_DO2, ::getM18Dos2)
    }

    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, 7)
            }
        } 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)
        )*/
        val tmr = today.plusDays(1L)
        var request = M18CommonRequest(
            modifiedDateTo = tmr.format(dataStringFormat),
            modifiedDateFrom = tmr.format(dataStringFormat)
        )
        m18PurchaseOrderService.savePurchaseOrders(request);

        //dDate from tmr to tmr
        var requestDO = M18CommonRequest(
            modifiedDateTo = tmr.format(dataStringFormat),
            modifiedDateFrom = tmr.format(dataStringFormat)
        )
        m18DeliveryOrderService.saveDeliveryOrders(requestDO);
//        logger.info("today: ${today.format(dataStringFormat)}")
//        logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
    }

    open fun getM18Po() {
        logger.info("PO Scheduler")
        val currentTime = LocalDateTime.now()
        val today = currentTime.toLocalDate().atStartOfDay()
        val TenDaysLater = today.plusDays(10L)

        var requestDO = M18CommonRequest(
            dDateTo = TenDaysLater.format(dateTimeStringFormat),
            dDateFrom = today.format(dateTimeStringFormat)
        )
     
        val result = m18PurchaseOrderService.savePurchaseOrders(requestDO);

        saveSyncLog(
            type = "PO", 
            status = "SUCCESS", 
            result = result, 
            start = currentTime
        )
    }
    
    open fun getM18Dos1() {
        logger.info("DO Scheduler 1 - DO")
        val currentTime = LocalDateTime.now()
        val today = currentTime.toLocalDate().atStartOfDay()
        val twoDaysLater = today.plusDays(2L)

        var requestDO = M18CommonRequest(
            dDateTo = twoDaysLater.format(dateTimeStringFormat),
            dDateFrom = twoDaysLater.format(dateTimeStringFormat)
        )
     
        val result = m18DeliveryOrderService.saveDeliveryOrders(requestDO);

        saveSyncLog(
            type = "DO1", 
            status = "SUCCESS", 
            result = result, 
            start = currentTime
        )
        
    }

    private fun saveSyncLog(type: String, status: String, result: SyncResult? = null, error: String? = null, start: LocalDateTime) {
        val log = SchedulerSyncLog(
            syncType = type,
            status = status,
            recordsProcessed = result?.totalProcessed ?: 0,
            recordsSaved = result?.totalSuccess ?: 0,
            recordsFailed = result?.totalFail ?: 0,
            errorMessage = error,
            query = result?.query ?: "",
            startTime = start,
            endTime = LocalDateTime.now()
        )
        schedulerSyncLogRepository.save(log)
    }

    open fun getM18Dos2() {
        logger.info("DO Scheduler 2 - DO")
        val currentTime = LocalDateTime.now()

        // .atStartOfDay() results in 00:00:00
        val today = currentTime.toLocalDate().atStartOfDay()
        val ysd = today.minusDays(1L)
        val tmr = today.plusDays(1L)

        // Set to 19:00:00 of yesterday
        val ysdNight = ysd.withHour(19).withMinute(0).withSecond(0)

        // Set to 11:00:00 of today
        val todayEleven = today.withHour(11).withMinute(0).withSecond(0)        

        val requestDO = M18CommonRequest(
            // These will now produce "yyyy-MM-dd HH:mm:ss"
            dDateTo = tmr.format(dateTimeStringFormat),           // e.g. 2026-01-19 00:00:00
            dDateFrom = tmr.format(dateTimeStringFormat),         // e.g. 2026-01-19 00:00:00
            modifiedDateTo = todayEleven.format(dateTimeStringFormat),   // 2026-01-18 11:00:00
            modifiedDateFrom = ysdNight.format(dateTimeStringFormat)    // 2026-01-17 19:00:00
        )
    
        val result = m18DeliveryOrderService.saveDeliveryOrders(requestDO)

        saveSyncLog(
            type = "DO2", 
            status = "SUCCESS", 
            result = result, 
            start = currentTime
        )
    }

    open fun getM18MasterData() {
        logger.info("Daily Scheduler - Master Data")
        var 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)

        val resultProducts = m18MasterDataService.saveProducts(request)
        saveSyncLog(
            type = "Products", 
            status = "SUCCESS", 
            result = resultProducts, 
            start = currentTime
        )
//        m18MasterDataService.saveBoms(request)
        currentTime = LocalDateTime.now()
        val resultVendors = m18MasterDataService.saveVendors(request)
        saveSyncLog(
            type = "Vendors", 
            status = "SUCCESS", 
            result = resultVendors, 
            start = currentTime
        )

        val resultBusinessUnits = m18MasterDataService.saveBusinessUnits(request)
        saveSyncLog(
            type = "BusinessUnits", 
            status = "SUCCESS", 
            result = resultBusinessUnits, 
            start = currentTime
        )

        val resultCurrencies = m18MasterDataService.saveCurrencies(request)
        saveSyncLog(
            type = "Currencies", 
            status = "SUCCESS", 
            result = resultCurrencies, 
            start = currentTime
        )
//        logger.info("today: ${today.format(dataStringFormat)}")
//        logger.info("yesterday: ${yesterday.format(dataStringFormat)}")

        
    }
}