package com.spring.infra.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.transaction.PlatformTransactionManager;

/**
 * 배치 청크 처리를 위한 추상 클래스입니다.
 * 
 * <p>이 클래스는 Spring Batch의 청크 기반 처리 로직을 구현하기 위한 기본 클래스로,
 * 배치 작업의 초기화 및 등록, JobRepository 및 TransactionManager와의 통합을 제공합니다.</p>
 * 
 * <p>구현 클래스는 이 클래스를 상속받아 특정 배치 작업의 로직을 정의해야 합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@Configuration
public abstract class AbstractBatchChunk implements AbstractBatch, ApplicationContextAware, InitializingBean {

    /**
     * 배치 작업 정보.
     * 
     * <p>BatchJobInfo 어노테이션을 통해 정의된 배치 작업의 메타데이터를 포함합니다.</p>
     */
    private final BatchJobInfo batchJobInfo;

    /**
     * 배치 작업 정보 서비스.
     * 
     * <p>배치 작업 정보를 관리하는 서비스 객체입니다.</p>
     */
    private BatchJobInfoService batchJobInfoService;

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

    /**
     * 배치 작업 정보 데이터.
     * 
     * <p>배치 작업의 실행에 필요한 데이터를 포함합니다.</p>
     */
    protected BatchJobInfoData batchJobInfoData;

    /**
     * JobRepository.
     * 
     * <p>배치 작업의 상태를 관리하는 리포지토리입니다.</p>
     */
    protected JobRepository jobRepository;

    /**
     * 트랜잭션 매니저.
     * 
     * <p>배치 작업의 트랜잭션 관리를 담당합니다.</p>
     */
    protected PlatformTransactionManager transactionManager;

    /**
     * 기본 생성자입니다.
     * 
     * <p>BatchJobInfo 어노테이션을 찾고, 없으면 예외를 발생시킵니다.</p>
     */
    protected AbstractBatchChunk() {
        this.batchJobInfo = AnnotationUtils.findAnnotation(getClass(), BatchJobInfo.class);
        if (this.batchJobInfo == null) {
            throw new IllegalStateException("BatchJobInfo annotation is missing");
        }
    }

    /**
     * 애플리케이션 컨텍스트를 설정합니다.
     * 
     * @param applicationContext 설정할 ApplicationContext
     * @throws BeansException 빈 설정 중 발생할 수 있는 예외
     */
    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 빈의 속성이 설정된 후 호출됩니다.
     * 
     * <p>배치 작업 정보 서비스 초기화 및 배치 작업 등록을 수행합니다.</p>
     * 
     * @throws Exception 초기화 중 발생할 수 있는 예외
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        this.batchJobInfoService = applicationContext.getBean(BatchJobInfoService.class);
        initializeBatchJobInfo();
        registerJobBean();
    }

    /**
     * 배치 작업 정보를 초기화합니다.
     * 
     * <p>배치 작업의 메타데이터를 로드하여 설정합니다.</p>
     */
    @Override
    public void initializeBatchJobInfo() {
        String beanName = applicationContext.getBeanNamesForType(this.getClass())[0];
        this.batchJobInfoData = batchJobInfoService.getBatchJobInfo(removeScopedTargetPrefix(beanName));
    }

    /**
     * 배치 작업을 Spring의 Bean으로 등록합니다.
     * 
     * <p>JobRepository에 배치 작업을 등록하여 실행할 수 있도록 합니다.</p>
     */
    @Override
    public void registerJobBean() {
        var beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
        var registry = (BeanDefinitionRegistry) beanFactory;
        String jobBeanName = batchJobInfoData.getJobName();
        if (!registry.containsBeanDefinition(jobBeanName)) {
            var beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Job.class, this::createJob);
            registry.registerBeanDefinition(jobBeanName, beanDefinitionBuilder.getBeanDefinition());
        }
    }

    /**
     * JobRepository를 설정합니다.
     * 
     * @param jobRepository 설정할 JobRepository
     */
    @Autowired
    public void setJobRepository(JobRepository jobRepository) {
        this.jobRepository = jobRepository;
    }

    /**
     * PlatformTransactionManager를 설정합니다.
     * 
     * @param transactionManager 설정할 PlatformTransactionManager
     */
    @Autowired
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     * 배치 작업을 생성합니다.
     * 
     * <p>구현 클래스에서 정의해야 하는 추상 메서드입니다.</p>
     * 
     * @return 생성된 Job 객체
     */
    @Override
    public abstract Job createJob();

}
