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;

@Configuration
public abstract class AbstractBatchChunk implements AbstractBatch, ApplicationContextAware, InitializingBean {

    private final BatchJobInfo batchJobInfo;
    private BatchJobInfoService batchJobInfoService;
    private ApplicationContext applicationContext;
    protected BatchJobInfoData batchJobInfoData;
    protected JobRepository jobRepository;
    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");
        }
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.batchJobInfoService = applicationContext.getBean(BatchJobInfoService.class);
        initializeBatchJobInfo();
        registerJobBean();
    }

    @Override
    public void initializeBatchJobInfo() {
        String beanName = applicationContext.getBeanNamesForType(this.getClass())[0];
        this.batchJobInfoData = batchJobInfoService.getBatchJobInfo(removeScopedTargetPrefix(beanName));
    }

    /**
     * 배치 작업을 Spring의 Bean으로 등록합니다.
     */
    @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;
    }

    @Override
    public abstract Job createJob();

}
