package com.spring.infra.quartz;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * Quartz 스케줄러의 설정 속성을 관리하는 클래스입니다.
 * 
 * <p>이 클래스는 application.properties 또는 application.yml 파일에서
 * "spring.quartz.properties.org.quartz" 접두사로 시작하는 설정을 읽어옵니다.</p>
 * 
 * <p>주요 기능:</p>
 * <ul>
 *   <li>JobStore, Scheduler, ThreadPool 관련 설정 관리</li>
 *   <li>설정값을 Properties 객체로 변환</li>
 * </ul>
 * 
 * @author mindol
 * @version 1.0
 */
@Slf4j
@Getter
@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "spring.quartz.properties.org.quartz")
public class QuartzProperties {

    private static final String PREFIX = "org.quartz";
    
    private final JobStore jobStore;
    private final Scheduler scheduler;
    private final ThreadPool threadPool;
    
    @Getter
    @RequiredArgsConstructor
    public static class JobStore {
        private final String tablePrefix;
        private final String isClustered;
        private final int clusterCheckinInterval;
    }

    @Getter
    @RequiredArgsConstructor
    public static class Scheduler {
        private final String instanceId;
        private final String instanceName;
    }

    @Getter
    @RequiredArgsConstructor
    public static class ThreadPool {
        private final int threadCount;
        private final int threadPriority;
    }

    /**
     * 현재 객체의 모든 속성을 Properties 객체로 변환합니다.
     * 
     * @return 변환된 Properties 객체
     */
    public Properties toProperties() {
        Properties properties = new Properties();
        addProperties(PREFIX, this, properties);
        return properties;
    }

    /**
     * 주어진 객체의 모든 필드를 재귀적으로 순회하며 Properties 객체에 추가합니다.
     * 
     * @param prefix 속성 키의 접두사
     * @param object 속성을 추출할 객체
     * @param properties 속성을 저장할 Properties 객체
     */
    private void addProperties(String prefix, Object object, Properties properties) {
        Arrays.stream(object.getClass().getDeclaredFields())
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .forEach(field -> setProperties(prefix, object, properties, field));
    }

    /**
     * 주어진 필드의 값을 Properties 객체에 추가합니다.
     * 
     * @param prefix 속성 키의 접두사
     * @param object 속성을 추출할 객체
     * @param properties 속성을 저장할 Properties 객체
     * @param field 처리할 필드
     */
    private void setProperties(String prefix, Object object, Properties properties, Field field) {
        try {
            Object value = field.get(object);
            if (value != null) {
                String fieldName = field.getName();
                String key = prefix + "." + fieldName;
                if (isSimpleType(field.getType())) {
                    properties.setProperty(key, String.valueOf(value));
                } else {
                    addProperties(key, value, properties);
                }
            }
        } catch (IllegalAccessException e) {
            log.error("Error getting property value for {}", field.getName(), e);
        }
    }

    /**
     * 주어진 타입이 단순 타입(primitive, String, Number, Boolean, Character)인지 확인합니다.
     * 
     * @param type 확인할 클래스 타입
     * @return 단순 타입이면 true, 그렇지 않으면 false
     */
    private boolean isSimpleType(Class<?> type) {
        return type.isPrimitive() 
            || String.class == type
            || Number.class.isAssignableFrom(type)
            || Boolean.class == type
            || Character.class == type;
    }

}
