package com.arms.config;

import com.arms.api.schedule.model.vo.ScheduleInfoVO;
import com.arms.api.schedule.model.vo.ScheduleMapVO;
import com.arms.api.schedule.service.ScheduleService;
import com.arms.api.schedule.util.ScheduleMapProvider;
import com.arms.api.schedule.util.ScheduleTaskDispatcher;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableScheduling
@Slf4j
public class DynamicSchedulerConfig implements SchedulingConfigurer {

    @Autowired
    private ScheduleService scheduleService;

    @Autowired
    private ScheduleTaskDispatcher dispatcher;

    @Autowired
    @Qualifier("dynamicTaskScheduler")
    private TaskScheduler taskScheduler;

    private ScheduledTaskRegistrar taskRegistrar;

    // 현재 실행 중인 스케줄 작업들을 추적
    private final List<ScheduledTask> scheduledTasks = new ArrayList<>();

    private final ScheduleMapProvider scheduleMapProvider;

    public DynamicSchedulerConfig(ScheduleMapProvider scheduleMapProvider) { // 기본 생성자 추가
        log.info("DynamicSchedulerConfig initialized.");
        this.scheduleMapProvider = scheduleMapProvider;
    }


    @Bean(name = "dynamicTaskScheduler")
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("Dynamic-scheduler-");
        scheduler.initialize();
        return scheduler;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
        log.info("DynamicSchedulerConfig :: configureTasks");
        taskRegistrar.setTaskScheduler(taskScheduler);
        scheduleTasks(); // 초기 등록
    }

    private void scheduleTasks() {

        // 기존 스케줄된 작업이 있다면 모두 취소 -> 동작하는 schedule 에 대한 방법 고민
        for (ScheduledTask task : scheduledTasks) {
            task.cancel();
        }
        scheduledTasks.clear();

        ScheduleMapVO scheduleMapVO = scheduleMapProvider.getScheduleMapVO();
        if (scheduleMapVO == null) {
            log.warn("[DynamicSchedulerConfig :: scheduleTasks] :: scheduleMapVO is null.");
            return;
        }

        Map<String, List<ScheduleInfoVO>> scheduleInfoMap = scheduleMapVO.getScheduleInfoMap();

        if (scheduleInfoMap == null || scheduleInfoMap.isEmpty()) {
            log.warn("[DynamicSchedulerConfig :: scheduleTasks] :: No schedule info found in scheduleInfoMap.");
            return;
        }

        scheduleInfoMap.forEach((fileName, scheduleList) -> {
            for (ScheduleInfoVO scheduleInfo : scheduleList) {
                if (Boolean.TRUE.equals(scheduleInfo.getEnabled())) {
                    String name = scheduleInfo.getName();
                    String methodName = scheduleInfo.getName(); // 임시처리
                    String cron = scheduleInfo.getCron();
                    String notes = scheduleInfo.getNotes();

                    Runnable businessLogic = null;

                    if (dispatcher.contains(name)) {
                        log.info("businessLogic is fetched by scheduleName [{}]", name);
                        businessLogic = dispatcher.getTask(name);
                    } else if (dispatcher.contains(methodName)) {
                        log.info("businessLogic is fetched by methodName [{}]", methodName);
                        businessLogic = dispatcher.getTask(methodName);
                    }

                    Runnable task;
                    if (businessLogic != null) {
                        Runnable fetchedLogic = businessLogic;
                        task = () -> {
                            log.info("Executing [{}] from [{}] - {}", name, fileName, notes);
                            long start = System.nanoTime();
                            try {
                                fetchedLogic.run();
                            } catch (Exception e) {
                                log.error("Error during execution of [{}]: {}", name, e.getMessage(), e);
                            } finally {
                                long end = System.nanoTime();
                                long elapsedMs = TimeUnit.NANOSECONDS.toMillis(end - start);
                                log.info("Finished [{}] from [{}] in {} ms", name, fileName, elapsedMs);
                            }
                        };
                    }
                    else {
                        task = () -> log.info("Executing [{}] from [{}] - {} (No business logic mapped)", name, fileName, notes);
                    }

                    CronTask cronTask = new CronTask(task, cron);
                    ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
                    scheduledTasks.add(scheduledTask);
                    log.info("Registered [{}] from [{}] with cron [{}]", name, fileName, cron);
                }
            }
        });
    }

    @EventListener(RefreshScopeRefreshedEvent.class)
    public void onRefresh() throws Exception {
        log.info("[DynamicSchedulerConfig :: onRefresh] :: Refreshing dynamic schedules...");

        ScheduleMapVO refreshedMap = scheduleService.getScheduleList();
        scheduleMapProvider.setScheduleMapVO(refreshedMap);

        scheduleTasks();

        log.info("[DynamicSchedulerConfig :: onRefresh] :: Schedules refreshed successfully.");
    }


    public void refreshSchedules() throws Exception {
        log.info("[DynamicSchedulerConfig :: refreshSchedules] :: Manually triggering schedule refresh...");

        ScheduleMapVO refreshedMap = scheduleService.getScheduleList();
        scheduleMapProvider.setScheduleMapVO(refreshedMap);

        scheduleTasks();

        log.info("[DynamicSchedulerConfig :: refreshSchedules] :: Manual refresh complete.");
    }
}
