package com.spring.infra.db.orm.jpa;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Primary;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.lang.NonNull;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import com.spring.infra.db.PrimaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;

import lombok.RequiredArgsConstructor;

/**
 * 주 데이터베이스에 대한 JPA 설정을 담당하는 구성 클래스입니다.
 * 
 * <p>이 클래스는 다음과 같은 주요 기능을 제공합니다:</p>
 * <ul>
 *   <li>주 데이터베이스용 EntityManagerFactory 설정</li>
 *   <li>주 데이터베이스용 TransactionManager 설정</li>
 *   <li>JPA 리포지토리 활성화 및 필터링</li>
 * </ul>
 * 
 * @author mindol
 * @version 1.0
 */
@Configuration
@EnableJpaRepositories(
    basePackages = PrimaryJpaConfig.BASE_PACKAGE,
    entityManagerFactoryRef = PrimaryJpaConfig.ENTITY_MANAGER_FACTORY,
    transactionManagerRef = PrimaryJpaConfig.TRANSACTION_MANAGER,
    includeFilters = @Filter(type = FilterType.CUSTOM, classes = PrimaryJpaConfig.DatabaseFilter.class)
)
@RequiredArgsConstructor
public class PrimaryJpaConfig {

    public static final String TRANSACTION_MANAGER = "primaryTransactionManager";
    public static final String ENTITY_MANAGER_FACTORY = "primaryEntityManagerFactory";
    private static final String BASE_PACKAGE = "com.spring.domain";
    private static final String PERSISTENCE_UNIT = "primaryPersistenceUnit";

    private final JpaProperties jpaProperties;
    private final HibernateProperties hibernateProperties;

    /**
     * 주 데이터베이스용 EntityManagerFactory를 생성합니다.
     * 
     * @param builder EntityManagerFactory 빌더
     * @param dataSource 주 데이터 소스
     * @return 구성된 LocalContainerEntityManagerFactoryBean
     */
    @Primary
    @Bean(name = ENTITY_MANAGER_FACTORY)
    LocalContainerEntityManagerFactoryBean entityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource
    ) {
        var properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
        var entities = EntityScanner.scanEntities(BASE_PACKAGE);
        return builder
                .dataSource(dataSource)
                .packages(entities)
                .persistenceUnit(PERSISTENCE_UNIT)
                .properties(properties)
                .build();
    }

    /**
     * 주 데이터베이스용 TransactionManager를 생성합니다.
     * 
     * @param factory 주 EntityManagerFactory
     * @return 구성된 PlatformTransactionManager
     */
    @Primary
    @Bean(TRANSACTION_MANAGER)
    PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }

    /**
     * 데이터베이스 선택을 위한 커스텀 필터 클래스입니다.
     * 
     * <p>이 필터는 DatabaseSelector 어노테이션이 없는 엔티티만 선택합니다.</p>
     */
    public static class DatabaseFilter implements TypeFilter {
        @Override
        public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
            return !reader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
        }
    }

}
