package incheon.com.excel;

import com.fasterxml.jackson.databind.ObjectMapper;
import incheon.com.cmm.ComDefaultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 엑셀 내보내기 레지스트리
 */
@Component
@Lazy(false)
@Slf4j
public class ExcelExportRegistry {

    private final Map<String, ExcelExportConfig> configs = new HashMap<>();
    private final ApplicationContext applicationContext;
    private final ObjectMapper objectMapper;

    public ExcelExportRegistry(ApplicationContext applicationContext, ObjectMapper objectMapper) {
        this.applicationContext = applicationContext;
        this.objectMapper = objectMapper;
    }

    public void register(ExcelExportConfig config) {
        configs.put(config.type(), config);
        log.info("엑셀 내보내기 설정 등록: type={}, service={}, method={}",
                config.type(), config.serviceBeanName(), config.listMethodName());
    }

    public Set<String> getSupportedTypes() {
        return configs.keySet();
    }

    public boolean isSupported(String type) {
        return configs.containsKey(type);
    }

    /**
     * 페이징 사용 여부 확인
     */
    public boolean isPaging(String type) {
        ExcelExportConfig config = configs.get(type);
        return config != null && config.paging();
    }

    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> fetchDataPaged(String type, Map<String, Object> searchParams,
                                                     int pageNo, int pageSize) throws Exception {
        ExcelExportConfig config = configs.get(type);
        if (config == null) {
            throw new IllegalArgumentException("지원하지 않는 엑셀 타입: " + type);
        }

        ComDefaultVO searchVO = createSearchVO(config.searchVoClass(), searchParams);

        // 페이징 사용 시에만 페이징 파라미터 설정
        if (config.paging()) {
            searchVO.setFirstIndex(pageNo * pageSize);
            searchVO.setRecordCountPerPage(pageSize);
        }

        Object service = applicationContext.getBean(config.serviceBeanName());
        Method method = findMethod(service.getClass(), config.listMethodName(), config.searchVoClass());
        if (method == null) {
            throw new NoSuchMethodException(
                    "메서드를 찾을 수 없습니다: " + config.serviceBeanName() + "." + config.listMethodName());
        }

        log.debug("엑셀 조회 - type: {}, paging: {}, pageNo: {}, pageSize: {}", type, config.paging(), pageNo, pageSize);
        Object result = method.invoke(service, searchVO);

        // 결과가 List인지 확인
        if (!(result instanceof List)) {
            return List.of();
        }

        List<?> resultList = (List<?>) result;
        if (resultList.isEmpty()) {
            return List.of();
        }

        // 이미 Map인 경우 그대로 반환
        if (resultList.get(0) instanceof Map) {
            return (List<Map<String, Object>>) result;
        }

        // VO인 경우 ObjectMapper로 Map으로 변환
        return resultList.stream()
                .map(vo -> objectMapper.convertValue(vo, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {}))
                .toList();
    }

    private ComDefaultVO createSearchVO(Class<? extends ComDefaultVO> voClass, Map<String, Object> params)
            throws Exception {
        if (params == null || params.isEmpty()) {
            return voClass.getDeclaredConstructor().newInstance();
        }
        return objectMapper.convertValue(params, voClass);
    }

    private Method findMethod(Class<?> clazz, String methodName, Class<?> paramType) {
        try {
            return clazz.getMethod(methodName, paramType);
        } catch (NoSuchMethodException e) {
            for (Class<?> iface : clazz.getInterfaces()) {
                try {
                    return iface.getMethod(methodName, paramType);
                } catch (NoSuchMethodException ignored) {
                }
            }
            if (clazz.getSuperclass() != null) {
                return findMethod(clazz.getSuperclass(), methodName, paramType);
            }
        }
        return null;
    }
}
