package com.spring.infra.batch;

import java.util.ArrayList;
import java.util.List;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
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>
 * 이 클래스는 배치 작업의 기본 설정 및 실행을 관리합니다.
 * </p>
 * 
 * @author mindol
 * @version 1.0
 */
@Configuration
public abstract class AbstractBatchTask implements AbstractBatch, ApplicationContextAware, InitializingBean {

    private final BatchJobInfo batchJobInfo;
    private BatchJobInfoService batchJobInfoService;
    private BatchJobInfoData batchJobInfoData;
    private ApplicationContext applicationContext;
    private JobRepository jobRepository;
    private PlatformTransactionManager transactionManager;

    /**
     * 기본 생성자입니다.
     * <p>
     * BatchJobInfo 어노테이션을 찾고, 없으면 예외를 발생시킵니다.
     * </p>
     */
    protected AbstractBatchTask() {
        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;
        this.batchJobInfoService = applicationContext.getBean(BatchJobInfoService.class);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        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;
    }

    /**
     * 배치 작업을 생성합니다.
     * 
     * @return 생성된 Job 객체
     * @throws IllegalStateException STEP이 정의되지 않은 경우 예외 발생
     */
    @Override
    public Job createJob() {
        List<Step> steps = createSteps();
        if (steps.isEmpty()) {
            throw new IllegalStateException("No steps defined for job: " + batchJobInfoData.getJobName());
        }
        var jobBuilder = new JobBuilder(batchJobInfoData.getJobName())
            .incrementer(new RunIdIncrementer())
            .repository(jobRepository)
            .start(steps.get(0));
        for (int i = 1; i < steps.size(); i++) {
            jobBuilder = jobBuilder.next(steps.get(i));
        }
        return jobBuilder.build();
    }

    /**
     * 배치 작업의 STEP을 생성합니다.
     * 
     * @return 생성된 Step 리스트
     */
    protected List<Step> createSteps() {
        List<Step> steps = new ArrayList<>();
        steps.add(addStep(batchJobInfoData.getJobName() + "Step", createTasklet()));
        return steps;
    }

    /**
     * STEP을 추가합니다.
     * 
     * @param stepName STEP의 이름
     * @param tasklet STEP에서 실행할 Tasklet
     * @return 생성된 Step 객체
     */
    protected Step addStep(String stepName, Tasklet tasklet) {
        return new StepBuilder(stepName)
                .repository(jobRepository)
                .transactionManager(transactionManager)
                .tasklet(tasklet)
                .build();
    }

    /**
     * 서브클래스에서 구현해야 하는 Tasklet을 생성합니다.
     * 
     * @return 생성된 Tasklet 객체
     */
    protected abstract Tasklet createTasklet();

}
