package com.eactive.eai.rms.common.scheduler;

import com.eactive.eai.rms.common.util.ContainerUtil;
import com.eactive.eai.rms.data.entity.man.scheduler.JobData;
import com.eactive.eai.rms.data.entity.man.scheduler.JobInfo;
import com.eactive.eai.rms.data.entity.man.scheduler.JobInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.EverythingMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

@Slf4j
@Component
@Transactional(transactionManager = "transactionManagerForEMS")
public class EMSSchedulerImpl implements EMSScheduler {

   public static final String CLUSTERED = "CLUSTERED";

	@Autowired
	@Qualifier("scheduler")
	private Scheduler scheduler;

   @Autowired
   @Qualifier("clusteredScheduler")
   private Scheduler clusteredScheduler;

	@Autowired
	private JobLoggingListener loggingListener;

	@Autowired
	private JobInfoService jobInfoService;

	@EventListener
	public void handleContextRefresh(ContextRefreshedEvent event) {

		if (event.getApplicationContext().getParent() != null) {
			// root context가 아니면 동작 하지 않음
			return;
		}
		jobInfoService.findAll().stream().forEach(jobInfo -> {
			try {
            if( CLUSTERED.equals(jobInfo.getInstanceName()) ){
               if( !clusteredScheduler.checkExists(new JobKey(jobInfo.getId(), Scheduler.DEFAULT_GROUP)) ){
                  addJob(clusteredScheduler, jobInfo);
               }
            } else {
               addJob(scheduler, jobInfo);
            }

			} catch (Exception e) {
				log.error("initialize error job : {}", jobInfo, e);
			}
		});

		try {
			scheduler.getListenerManager().addJobListener(loggingListener, EverythingMatcher.allJobs());
         clusteredScheduler.getListenerManager().addJobListener(loggingListener, EverythingMatcher.allJobs());
		} catch (SchedulerException e1) {
			log.error("Error during add job listener", e1);
		}
	}

	@EventListener
	public void handleContextStopped(ContextClosedEvent event) {

		if (event.getApplicationContext().getParent() != null) {
			// root context가 아니면 동작 하지 않음
			return;
		}

		jobInfoService.findAll().stream().forEach(jobInfo -> {
			try {
            if(CLUSTERED.equals(jobInfo.getInstanceName())){

            } else {
               deleteJob(scheduler, jobInfo.getId());
            }

			} catch (Exception e) {
				log.error("deleteJob,addJob error", e);
			}
		});

	}

	private String getTriggerName(String jobName) {
		return jobName + "-trigger";
	}

	@Override
   public void deleteAndAddJob(Scheduler inScheduler, JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
      deleteJob(inScheduler, jobInfo.getId());
      addJob(inScheduler, jobInfo);
	}

	@Override
   public void deleteAndAddJob(Scheduler inScheduler, String jobName) throws SchedulerException, ClassNotFoundException {
      deleteJob(inScheduler, jobName);
		JobInfo jobInfo = jobInfoService
				.findById(jobName)
				.orElseThrow(() -> new SchedulerException("jobInfo를 찾을 수 없습니다 : " + jobName));

		try {
         addJob(inScheduler, jobInfo);
		} catch (Exception e) {
			throw new SchedulerException(e);
		}
	}

   @Override
   public void reloadJob(String jobName) throws SchedulerException, ClassNotFoundException {
      JobInfo jobInfo = jobInfoService.getById(jobName);
      if (CLUSTERED.equals(jobInfo.getInstanceName())) {
         deleteJob(scheduler, jobName);
         reloadClusteredJob(jobInfo);
      } else {
         deleteAndAddJob(scheduler, jobInfo);
         deleteJob(clusteredScheduler, jobName);
      }
   }

   private void reloadClusteredJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
      String jobId = jobInfo.getId();
      TriggerKey triggerKey = new TriggerKey(getTriggerName(jobId), Scheduler.DEFAULT_GROUP);
      JobKey jobKey = new JobKey(jobId, Scheduler.DEFAULT_GROUP);

      Trigger existing = clusteredScheduler.getTrigger(triggerKey);
      JobDetail existingJob = clusteredScheduler.getJobDetail(jobKey);

      if (existing != null && existingJob != null) {
         // JobDetail(JobData 포함) 갱신
         @SuppressWarnings("unchecked")
         Class<Job> jobClass = (Class<Job>) Class.forName(jobInfo.getJobClassName());
         JobBuilder jobBuilder = newJob(jobClass).withIdentity(jobKey);
         for (JobData jobData : jobInfo.getJobDatas()) {
            jobBuilder.usingJobData(jobData.getId().getDataKey(), jobData.getDataValue());
         }
         clusteredScheduler.addJob(jobBuilder.build(), true);

         // 트리거(cron) 갱신
         CronTrigger newTrigger = newTrigger()
               .withIdentity(triggerKey)
               .withSchedule(cronSchedule(jobInfo.getCronExp()))
               .build();
         Date ft = clusteredScheduler.rescheduleJob(triggerKey, newTrigger);
         if (ft != null) {
            return;
         }
         log.warn("[reloadClusteredJob] {} rescheduleJob returned null, falling back to deleteAndAddJob", jobId);
      } else if (existing != null) {
         // orphaned trigger (job missing) — clean up before re-adding
         log.warn("[reloadClusteredJob] {} has orphaned trigger without job, unscheduling", jobId);
         clusteredScheduler.unscheduleJob(triggerKey);
      }
      deleteAndAddJob(clusteredScheduler, jobInfo);
   }

   public void deleteJob(Scheduler inScheduler, String jobNm) throws SchedulerException {
      String schedulerType = (inScheduler == clusteredScheduler) ? "clusteredScheduler" : "scheduler";
		JobKey key = new JobKey(jobNm, Scheduler.DEFAULT_GROUP);
      JobDetail jobDetail = inScheduler.getJobDetail(key);
		if (jobDetail != null) {
         inScheduler.deleteJob(key);
		}
	}

   public void addJob(Scheduler inScheduler, JobInfo info) throws ClassNotFoundException, SchedulerException {

		if (StringUtils.equalsIgnoreCase(info.getUseYn(), "Y")
            && StringUtils.equalsAnyIgnoreCase(info.getInstanceName(), "ALL", "CLUSTERED", ContainerUtil.getInstanceName())) {

			@SuppressWarnings("unchecked")
			Class<Job> jobClass = (Class<Job>) Class.forName(info.getJobClassName());

			JobBuilder jobBuilder = newJob(jobClass);
			jobBuilder = jobBuilder.withIdentity(info.getId(), Scheduler.DEFAULT_GROUP);

			for (JobData jobData : info.getJobDatas()) {
				jobBuilder.usingJobData(jobData.getId().getDataKey(), jobData.getDataValue());
			}

			JobDetail job = jobBuilder.build();

			CronTrigger trigger = newTrigger()
					.withIdentity(getTriggerName(info.getId()), Scheduler.DEFAULT_GROUP)
					.withSchedule(cronSchedule(info.getCronExp()))
					.build();

         inScheduler.scheduleJob(job, trigger);
		}
	}

}
