package com.spring.infra.quartz;

import java.util.Map;

import org.springframework.aop.support.AopUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import com.spring.infra.batch.AbstractBatch;

import lombok.RequiredArgsConstructor;

/**
 * Quartz 작업 등록기 클래스입니다.
 * 
 * <p>이 클래스는 Spring의 ApplicationContext 내에서 Quartz 작업을 등록하고 관리하는 역할을 합니다.</p>
 * 
 * <p>주요 기능:</p>
 * <ul>
 *   <li>애플리케이션 컨텍스트 내의 모든 빈을 검사하여 QuartzJob 어노테이션이 붙은 메소드를 식별합니다.</li>
 *   <li>AbstractBatchJob을 상속받은 클래스를 식별하고, 해당 메소드를 Quartz 작업으로 등록합니다.</li>
 *   <li>각 작업에 대한 JobDetail 및 Trigger를 생성합니다.</li>
 * </ul>
 * 
 * <p>이 클래스는 {@link ApplicationListener}를 구현하여 {@link ApplicationReadyEvent}가 발생할 때 자동으로 실행됩니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@Component
@RequiredArgsConstructor
public class QuartzJobRegistrar implements ApplicationListener<ApplicationReadyEvent> {

    /**
     * 애플리케이션 컨텍스트 객체입니다.
     * 
     * <p>Spring의 ApplicationContext를 통해 필요한 빈을 가져옵니다.</p>
     */
    private final ApplicationContext applicationContext;

    /**
     * Quartz 스케줄 서비스 객체입니다.
     * 
     * <p>Quartz 작업을 등록하고 관리하는 데 사용됩니다.</p>
     */
    private final QuartzScheduleService quartzScheduleService;

    /**
     * 애플리케이션 컨텍스트가 리프레시될 때 호출되는 메소드입니다.
     * 
     * <p>QuartzJob 어노테이션이 붙은 메소드와 AbstractBatchJob을 상속받은 클래스를 등록합니다.</p>
     * 
     * @param event ContextRefreshedEvent 객체
     */
    @Override
    public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
        refreshJobs();
    }

    /**
     * 리프레시된 스코프에서 Quartz 작업을 등록합니다.
     * 
     * <p>리프레시된 스코프에서 Quartz 작업을 등록하기 위해 호출됩니다.</p>
     * 
     * @param event RefreshScopeRefreshedEvent 객체
     */
    @EventListener
    public void onRefreshScopeRefreshed(@NonNull RefreshScopeRefreshedEvent event) {
        refreshJobs();
    }

    /**
     * Quartz 작업을 새로 고칩니다.
     * 
     * <p>기존의 Quartz 작업을 지우고 새로운 작업을 등록합니다.</p>
     */
    private void refreshJobs() {
        quartzScheduleService.clearJobs();
        registerBatchJobs();
    }

    /**
     * AbstractBatchJob을 상속받은 모든 클래스를 찾아 Quartz 스케줄러에 등록합니다.
     * 
     * <p>애플리케이션 컨텍스트에서 AbstractBatch 타입의 빈을 검색하고,
     * AOP 프록시가 아닌 경우 Quartz 작업을 생성합니다.</p>
     */
    private void registerBatchJobs() {
        Map<String, AbstractBatch> batchJobs = applicationContext.getBeansOfType(AbstractBatch.class);
        for (Map.Entry<String, AbstractBatch> entry : batchJobs.entrySet()) {
            AbstractBatch batch = entry.getValue();
            if (!AopUtils.isAopProxy(batch)) {
                quartzScheduleService.createJobTrigger(removeScopedTargetPrefix(entry.getKey()));
            }
        }
    }

    /**
     * 스코프가 지정된 타겟 프리픽스를 제거합니다.
     * 
     * <p>주어진 빈 이름에서 "scopedTarget." 접두사를 제거하여 반환합니다.</p>
     * 
     * @param beanName 빈 이름
     * @return 수정된 빈 이름
     */
    private String removeScopedTargetPrefix(String beanName) {
        return beanName.startsWith("scopedTarget.") ? beanName.substring("scopedTarget.".length()) : beanName;
    }

}
