package incheon.uis.ums.factory;

import incheon.com.config.annotation.DynamicModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

/**
 * 동적 모델 인스턴스 생성을 위한 팩토리 클래스
 * @DynamicModel 어노테이션이 붙은 모든 클래스를 자동으로 스캔하여 등록
 */
@Slf4j
@Component
public class AutoScanModelFactory {
    
    private final Map<String, Class<?>> modelClasses = new ConcurrentHashMap<>();
    private final Map<String, String> modelDescriptions = new ConcurrentHashMap<>();
    
    /**
     * 스캔할 패키지 목록
     */
    private static final String[] BASE_PACKAGES = {
        "incheon.uis.urf.model",	//도로시설물
        "incheon.uis.ucf.model",	//공통시설물
        "incheon.uis.uef.model",	//경제청시설물
        "incheon.uis.ugf.model",	//녹지시설물
        "incheon.uis.usf.model",	//하수시설물
        "incheon.uis.uuf.model",	//지하시설물
        "incheon.uis.uwf.model"		//상수시설물
    };
    
    /**
     * Spring 컨테이너 초기화 후 자동으로 모든 @DynamicModel 클래스들을 스캔
     */
    @PostConstruct
    public void scanAndRegisterModels() {
        log.info("Starting to scan @DynamicModel annotated classes...");
        
        ClassPathScanningCandidateComponentProvider scanner = 
            new ClassPathScanningCandidateComponentProvider(false);
        
        // @DynamicModel 어노테이션이 붙은 클래스만 스캔하도록 필터 설정
        scanner.addIncludeFilter(new AnnotationTypeFilter(DynamicModel.class));
        
        int totalScanned = 0;
        
        // 각 패키지별로 스캔 수행
        for (String basePackage : BASE_PACKAGES) {
            try {
                Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
                
                for (BeanDefinition beanDefinition : candidateComponents) {
                    registerModelClass(beanDefinition);
                    totalScanned++;
                }
                
                log.debug("Scanned package: {} - Found {} models", basePackage, candidateComponents.size());
                
            } catch (Exception e) {
                log.error("Failed to scan package: {}", basePackage, e);
            }
        }
        
        log.info("Model scanning completed! Total registered: {} models", totalScanned);
        log.info("Registered models: {}", String.join(", ", modelClasses.keySet()));
    }
    
    /**
     * 스캔된 클래스를 모델 레지스트리에 등록
     */
    private void registerModelClass(BeanDefinition beanDefinition) {
        try {
            Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
            DynamicModel annotation = clazz.getAnnotation(DynamicModel.class);
            
            // 모델명 결정: 어노테이션 value가 있으면 사용, 없으면 클래스명 사용
            String modelName = annotation.value().isEmpty() ? 
                clazz.getSimpleName() : annotation.value();
            
            // 중복 모델명 체크
            if (modelClasses.containsKey(modelName)) {
                log.warn("Duplicate model name detected: {} (existing: {}, new: {})", 
                    modelName, modelClasses.get(modelName).getName(), clazz.getName());
                return;
            }
            
            // 모델 등록
            modelClasses.put(modelName, clazz);
            modelDescriptions.put(modelName, annotation.description());
            
            log.debug("Registered model: {} -> {} ({})", 
                modelName, clazz.getSimpleName(), annotation.description());
                
        } catch (ClassNotFoundException e) {
            log.error("Failed to load class: {}", beanDefinition.getBeanClassName(), e);
        } catch (Exception e) {
            log.error("Failed to register model from: {}", beanDefinition.getBeanClassName(), e);
        }
    }
    
    /**
     * 지정된 모델명으로 새로운 인스턴스 생성
     * @param modelName 모델 이름
     * @return 생성된 모델 인스턴스
     * @throws IllegalArgumentException 알 수 없는 모델명인 경우
     * @throws IllegalStateException 인스턴스 생성 실패시
     */
    public Object createInstance(String modelName) {
        Class<?> modelClass = modelClasses.get(modelName);
        
        if (modelClass == null) {
            log.error("Unknown model requested: {}. Available models: {}", 
                modelName, String.join(", ", modelClasses.keySet()));
            throw new IllegalArgumentException("Unknown model: " + modelName + 
                ". Available models: " + String.join(", ", modelClasses.keySet()));
        }
        
        try {
            Constructor<?> constructor = modelClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object instance = constructor.newInstance();
            log.debug("Created instance of model: {}", modelName);
            return instance;
            
        } catch (NoSuchMethodException e) {
            log.error("Model instance creation failed - No default constructor: modelName={}, className={}", 
                modelName, modelClass.getName(), e);
            throw new IllegalStateException("모델 인스턴스 생성에 실패했습니다. 기본 생성자가 필요합니다.", e);
        } catch (InstantiationException e) {
            log.error("Model instance creation failed - Cannot instantiate abstract class or interface: modelName={}, className={}", 
                modelName, modelClass.getName(), e);
            throw new IllegalStateException("모델 인스턴스 생성에 실패했습니다. 추상 클래스나 인터페이스는 인스턴스화할 수 없습니다.", e);
        } catch (IllegalAccessException e) {
            log.error("Model instance creation failed - Constructor access denied: modelName={}, className={}", 
                modelName, modelClass.getName(), e);
            throw new IllegalStateException("모델 인스턴스 생성에 실패했습니다. 생성자 접근 권한이 없습니다.", e);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            log.error("Model instance creation failed - Constructor threw exception: modelName={}, className={}", 
                modelName, modelClass.getName(), cause != null ? cause : e);
            throw new IllegalStateException("모델 인스턴스 생성 중 오류가 발생했습니다.", cause != null ? cause : e);
        } catch (SecurityException e) {
            log.error("Model instance creation failed - Security violation: modelName={}, className={}", 
                modelName, modelClass.getName(), e);
            throw new IllegalStateException("모델 인스턴스 생성에 실패했습니다. 보안 제약으로 인해 접근할 수 없습니다.", e);
        } catch (Exception e) {
            log.error("Model instance creation failed - Unexpected error: modelName={}, className={}", 
                modelName, modelClass.getName(), e);
            throw new IllegalStateException("모델 인스턴스 생성 중 예상치 못한 오류가 발생했습니다.", e);
        }
    }
    
    /**
     * 타입 안전한 인스턴스 생성 (제네릭 버전)
     */
    @SuppressWarnings("unchecked")
    public <T> T createInstance(String modelName, Class<T> expectedType) {
        Object instance = createInstance(modelName);
        
        if (!expectedType.isInstance(instance)) {
            throw new ClassCastException("Model " + modelName + " is not of expected type " + expectedType.getName());
        }
        
        return (T) instance;
    }
    
    /**
     * 등록된 모든 모델명 목록 반환
     */
    public Set<String> getRegisteredModelNames() {
        return modelClasses.keySet();
    }
    
    /**
     * 특정 모델의 클래스 정보 반환
     */
    public Class<?> getModelClass(String modelName) {
        return modelClasses.get(modelName);
    }
    
    /**
     * 특정 모델의 설명 반환
     */
    public String getModelDescription(String modelName) {
        return modelDescriptions.getOrDefault(modelName, "No description");
    }
    
    /**
     * 등록된 모델 개수 반환
     */
    public int getRegisteredModelCount() {
        return modelClasses.size();
    }
    
    /**
     * 모델 존재 여부 확인
     */
    public boolean hasModel(String modelName) {
        return modelClasses.containsKey(modelName);
    }
}
