package com.spring.infra.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.ReferenceJobFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

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

/**
 * 배치 작업 정보에 대한 BeanPostProcessor입니다.
 * 
 * <p>이 클래스는 Spring의 BeanPostProcessor 인터페이스를 구현하여,
 * 배치 작업에 대한 메타데이터를 처리하고, 배치 작업이 초기화될 때
 * 관련 정보를 설정하는 역할을 합니다.</p>
 * 
 * <p>배치 작업에 대한 @BatchJobInfo 어노테이션을 처리하여,
 * 배치 작업의 정보를 서비스에 저장합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class BatchJobInfoBeanPostProcessor implements BeanPostProcessor {

    /**
     * 배치 작업 정보 서비스를 제공하는 서비스 객체입니다.
     */
    private final BatchJobInfoService batchJobInfoService;

    /**
     * JobRegistry 객체입니다.
     * 
     * <p>Spring Batch의 JobRegistry를 사용하여 배치 작업을 등록합니다.</p>
     */
    private final JobRegistry jobRegistry;

    /**
     * 초기화 전에 호출되는 메서드입니다.
     * 
     * <p>배치 작업 정보 어노테이션을 처리하여 관련 정보를 설정합니다.</p>
     * 
     * @param bean 초기화할 Bean 객체
     * @param beanName Bean의 이름
     * @return 초기화된 Bean 객체
     * @throws BeansException Bean 초기화 중 발생할 수 있는 예외
     */
    @Override
    @Nullable
    public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
        processBatchJobInfoAnnotations(bean, beanName);
        return bean;
    }

    /**
     * 초기화 후에 호출되는 메서드입니다.
     * 
     * <p>배치 작업이 등록되지 않은 경우 JobRegistry에 등록합니다.</p>
     * 
     * @param bean 초기화된 Bean 객체
     * @param beanName Bean의 이름
     * @return 초기화된 Bean 객체
     * @throws BeansException Bean 초기화 중 발생할 수 있는 예외
     */
    @Override
    @Nullable
    public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
        if (bean instanceof AbstractBatch) {
            AbstractBatch abstractBatch = (AbstractBatch) bean;
            Job job = abstractBatch.createJob();
            String jobName = job.getName();
            if (!jobRegistry.getJobNames().contains(jobName)) {
                try {
                    jobRegistry.register(new ReferenceJobFactory(job));
                } catch (DuplicateJobException e) {
                    log.warn("Unexpected DuplicateJobException for job: {}", jobName, e);
                }
            }
        }
        return bean;
    }

    /**
     * 배치 작업 정보 어노테이션을 처리합니다.
     * 
     * <p>주어진 Bean에서 @BatchJobInfo 어노테이션을 찾아 관련 정보를 설정합니다.</p>
     * 
     * @param bean 처리할 Bean 객체
     * @param beanName Bean의 이름
     */
    private void processBatchJobInfoAnnotations(Object bean, String beanName) {
        if (bean.getClass().isAnnotationPresent(BatchJobInfo.class)) {
            BatchJobInfo batchJobInfo = bean.getClass().getAnnotation(BatchJobInfo.class);
            batchJobInfoService.setBatchJobInfoData(removeScopedTargetPrefix(beanName), batchJobInfo);
        }
    }

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

}
