package com.spring.infra.quartz;

import java.util.List;
import java.util.Set;

import javax.annotation.PreDestroy;

import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
* Quartz 스케줄러의 안전한 종료를 관리하는 리스너 클래스
* 
* <p>이 클래스는 Spring 애플리케이션 컨텍스트가 종료될 때 Quartz 스케줄러의 모든 작업을
* 안전하게 종료하고 정리하는 역할을 합니다. 실행 중인 작업이 정상적으로 완료될 때까지
* 대기하며, 모든 트리거를 정리합니다.</p>
* 
* <p>주요 기능:</p>
* <ul>
*   <li>실행 중인 작업의 안전한 종료 처리</li>
*   <li>트리거 상태 모니터링 및 정리</li>
*   <li>스케줄러 리소스의 정상적인 해제</li>
* </ul>
* 
* @author [작성자 이름]
* @version 1.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class QuartzShutdownListener implements ApplicationListener<ContextClosedEvent> {
    
    /** 작업 완료 대기 최대 시간 (밀리초) */
    private static final long SHUTDOWN_WAIT_TIME = 30000L; // 30초
    
    /** Quartz 스케줄러 인스턴스 */
    private final Scheduler scheduler;
    

    /**
     * Spring 컨텍스트 종료 이벤트 발생 시 실행되는 메서드
     * 
     * @param event 컨텍스트 종료 이벤트
     */
    @Override
    public void onApplicationEvent(@NonNull ContextClosedEvent event) {
        log.info("Quartz 셧다운 프로세스 시작...");
        shutdownQuartzGracefully();
    }

    /**
     * Quartz 스케줄러의 안전한 종료를 처리하는 메서드
     * 
     * <p>다음의 단계를 순차적으로 실행합니다:</p>
     * <ol>
     *   <li>새로운 작업 스케줄링 중지</li>
     *   <li>현재 실행 중인 작업 목록 확인</li>
     *   <li>실행 중인 작업의 상태 로깅</li>
     *   <li>작업 완료 대기</li>
     *   <li>트리거 정리</li>
     *   <li>스케줄러 종료</li>
     * </ol>
     */
    private void shutdownQuartzGracefully() {
        try {
            // 1. 새로운 작업 스케줄링 중지
            scheduler.standby();
            log.info("Scheduler를 대기 모드로 전환했습니다.");

            // 2. 현재 실행 중인 작업 목록 확인
            List<JobExecutionContext> currentJobs = scheduler.getCurrentlyExecutingJobs();
            if (!currentJobs.isEmpty()) {
                log.info("현재 실행 중인 작업 수: {}", currentJobs.size());

                // 3. 실행 중인 작업의 정보 로깅
                logRunningJobs(currentJobs);

                // 4. 실행 중인 작업이 완료될 때까지 대기
                waitForJobCompletion(currentJobs);
            }

            // 5. 모든 트리거 상태 확인 및 정리
            cleanupTriggers();

            // 6. 스케줄러 종료
            performSchedulerShutdown();

        } catch (SchedulerException e) {
            log.error("Quartz 종료 중 오류 발생", e);
            Thread.currentThread().interrupt();
        } catch (InterruptedException e) {
            log.error("Quartz 종료 대기 중 인터럽트 발생", e);
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 실행 중인 작업들의 상세 정보를 로깅
     * 
     * @param currentJobs 현재 실행 중인 작업 목록
     */
    private void logRunningJobs(List<JobExecutionContext> currentJobs) {
        for (JobExecutionContext job : currentJobs) {
            log.info("실행 중인 작업: {}, 트리거: {}, 시작 시간: {}", 
                job.getJobDetail().getKey(),
                job.getTrigger().getKey(),
                job.getFireTime()
            );
        }
    }

    /**
     * 실행 중인 작업이 완료될 때까지 대기
     * 
     * @param currentJobs 현재 실행 중인 작업 목록
     * @throws InterruptedException 대기 중 인터럽트 발생 시
     * @throws SchedulerException 스케줄러 관련 오류 발생 시
     */
    private void waitForJobCompletion(List<JobExecutionContext> currentJobs) 
            throws InterruptedException, SchedulerException {
        long startTime = System.currentTimeMillis();
        while (!currentJobs.isEmpty() && 
                (System.currentTimeMillis() - startTime) < SHUTDOWN_WAIT_TIME) {
            log.info("실행 중인 작업이 완료될 때까지 대기 중...");
            Thread.sleep(1000);
            currentJobs = scheduler.getCurrentlyExecutingJobs();
        }
    }

    /**
     * 모든 트리거를 정리
     * 
     * @throws SchedulerException 스케줄러 관련 오류 발생 시
     */
    private void cleanupTriggers() throws SchedulerException {
        List<String> groups = scheduler.getTriggerGroupNames();
        for (String group : groups) {
            Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.groupEquals(group));
            for (TriggerKey triggerKey : triggerKeys) {
                try {
                    Trigger trigger = scheduler.getTrigger(triggerKey);
                    if (trigger != null) {
                        scheduler.unscheduleJob(triggerKey);
                        log.info("트리거 제거됨: {}", triggerKey);
                    }
                } catch (Exception e) {
                    log.error("트리거 제거 중 오류 발생: {}", triggerKey, e);
                }
            }
        }
    }

    /**
     * 스케줄러 종료 수행
     * 
     * @throws SchedulerException 스케줄러 관련 오류 발생 시
     */
    private void performSchedulerShutdown() throws SchedulerException {
        scheduler.clear();
        scheduler.shutdown(true); // true = 실행 중인 작업이 완료될 때까지 대기
        log.info("Quartz 스케줄러가 정상적으로 종료되었습니다.");
    }

    /**
     * 애플리케이션 컨텍스트 종료 전 실행되는 정리 메서드
     */
    @PreDestroy
    public void preDestroy() {
        try {
            if (!scheduler.isShutdown()) {
                log.info("ApplicationContext 종료 전 Quartz 정리 작업 수행");
                shutdownQuartzGracefully();
            }
        } catch (SchedulerException e) {
            log.error("PreDestroy 단계에서 Quartz 종료 중 오류 발생", e);
        }
    }

}
