package com.arms.egovframework.javaservice.esframework.config;

import com.arms.egovframework.javaservice.esframework.repository.common.EsCommonRepository;
import com.arms.egovframework.javaservice.esframework.repository.common.EsCommonRepositoryImpl;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

@Configuration
@EnableElasticsearchRepositories(basePackages = {"${spring.elasticsearch-repository.path}"}, repositoryBaseClass = EsCommonRepositoryImpl.class)
public class RepositoryConfiguration {

    @Value("${spring.elasticsearch-repository.path}")
    private String[] basePackages;

    private boolean hasFieldOfType(Class<?> beanClass,Class<?> clazz) {

        Class<?> current = beanClass;
        while (current != null && current != Object.class) {
            for (Field field : current.getDeclaredFields()) {
                if (clazz.isAssignableFrom(field.getType())) {
                    return true;
                }
            }
            current = current.getSuperclass();
        }
        return false;
    }

    private final TypeFilter hasFieldOfTypeFilter = (metadataReader, metadataReaderFactory) -> {
        try {
            String className = metadataReader.getClassMetadata().getClassName();
            int lastDot = className.lastIndexOf('.');
            String pkg = (lastDot >= 0) ? className.substring(0, lastDot) : "";
            if (pkg.equals(EsCommonRepository.class.getPackageName())) {
                return false;
            }
            Class<?> clazz = Class.forName(className);

            return hasFieldOfType(clazz, EsCommonRepository.class);
        } catch (Exception e) {
            throw new IllegalArgumentException("hasFieldOfTypeInterfaceFilter::클래스 스캔 실패", e);
        }
    };

    private final TypeFilter esCommonRepositoryFilter = (metadataReader, metadataReaderFactory) -> {
        try {
            String className = metadataReader.getClassMetadata().getClassName();
            Class<?> clazz = Class.forName(className);
            return isAssignableFromEsCommonRepositoryInterface(clazz);
        } catch (Exception e) {
            throw new IllegalArgumentException("esCommonRepositoryFilter::클래스 스캔 실패", e);
        }
    };


    @Bean
    public ApplicationListener<ContextRefreshedEvent> restrictAccessModifierWithRepository() {

        return event -> {

            ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(false){
                    @Override
                    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                        return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isInterface();
                    }
                };

            scanner.addIncludeFilter(hasFieldOfTypeFilter);

            for (String basePackage : basePackages) {
                for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
                    try{

                        Class<?> beanClass = Class.forName(bd.getBeanClassName());

                        int modifiers = beanClass.getModifiers();

                        if(modifiers<=1){
                            throw new IllegalStateException("EsCommonRepository 타입을 변수로 가질 수 없습니다." + beanClass);
                        }

                    }catch (ClassNotFoundException e){
                        throw new IllegalArgumentException("restrictAccessModifierWithRepository::클래스가 없습니다.",e);
                    }
                }
            }
        };
    }

    @Bean
    public ApplicationListener<ContextRefreshedEvent> restrictCommonRepository() {

        return event -> {

            ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(false){
                    @Override
                    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                        return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isInterface();
                    }
                };

            scanner.addIncludeFilter(hasFieldOfTypeFilter);

            for (String basePackage : basePackages) {

                for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
                    try{

                        Class<?> beanClass = Class.forName(bd.getBeanClassName());

                        Method[] methods = beanClass.getDeclaredMethods();

                        if(methods.length>0){
                            throw new IllegalStateException("공통저장소를 구현한 클래스는 변수로 사용 하실수 없습니다." + beanClass);
                        }

                    }catch (ClassNotFoundException e){
                        throw new IllegalArgumentException("restrictCommonRepository::클래스가 없습니다.",e);
                    }
                }
            }
        };
    }

    @Bean
    public ApplicationListener<ContextRefreshedEvent> validateRepositories() {

        return event -> {

            ClassPathScanningCandidateComponentProvider scanner =
                    new ClassPathScanningCandidateComponentProvider(false){
                        @Override
                        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                            return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isInterface();
                        }
                    };

            scanner.addIncludeFilter(esCommonRepositoryFilter);

            for (String basePackage : basePackages) {

                for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
                    try{

                        Class<?> beanClass = Class.forName(bd.getBeanClassName());

                        Method[] methods = beanClass.getDeclaredMethods();

                        if(methods.length>0){
                            throw new IllegalStateException("공통저장소를 상속한 인터페이스는 메소드를 가질수 없습니다. " + beanClass);
                        }

                    }catch (ClassNotFoundException e){
                        throw new IllegalArgumentException("validateRepositories::클래스가 없습니다.",e);
                    }
                }
            }
        };
    }

    private static boolean isAssignableFromEsCommonRepositoryInterface(Class<?> clazz) {
        return EsCommonRepository.class.isAssignableFrom(clazz) && clazz.isInterface() && !clazz.equals(EsCommonRepository.class);
    }

}
