package incheon.uis.ums.service.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Value;

import incheon.uis.ums.util.UisApplicationContextProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import incheon.cmm.g2f.layer.mapper.G2FLayerMapper;
import incheon.cmm.g2f.layer.service.G2FLayerService;
import incheon.com.cmm.context.RequestContext;
import incheon.uis.ums.mapper.UisLayerMapper;
import incheon.uis.ums.service.UisFacilityService;

/**
 * 시설물 서비스 구현체
 *
 * @author 이재룡
 * @since 2025.09.02
 * @version 2.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UisFacilityServiceImpl implements UisFacilityService {

    @Autowired
    private UisApplicationContextProvider applicationContextProvider;

    @Autowired
    private G2FLayerService g2FLayerService;

    @Autowired
    private G2FLayerMapper g2FLayerMapper;
    
    @Autowired
    private UisLayerMapper uisLayerMapper;

    @Value("${uis.schema.name:icuis}")
    private String SCHEMA_NAME;

    @Value("${uis.geometry.srid:5186}")
    private String SRID;

//    @Autowired
//    private CommonCodeMapper commonCodeMapper;

	@Override
	public List<?> selectList(Object requestVO) throws Exception {
		// requestModel가 동적 오브젝트일 때 TypeId 속성 값
        String typeId = "";
        String typeGb = "";
        try {
            // getTypeId() 메서드가 있으면 호출
            typeId = (String) requestVO.getClass().getMethod("getTypeId").invoke(requestVO);
            typeGb = (String) requestVO.getClass().getMethod("getTypeGb").invoke(requestVO);

        } catch (NoSuchMethodException e) {
            // 메서드가 없을 때만 필드 접근 시도
            try {
                typeId = (String) requestVO.getClass().getField("TypeId").get(requestVO);
                typeGb = (String) requestVO.getClass().getField("TypeGb").get(requestVO);

            } catch (NoSuchFieldException | IllegalAccessException ex) {
                // 필드도 없거나 접근 불가 → 빈 값 유지
                typeId = "";
                typeGb = "";
            }

        } catch (IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
            // 메서드는 있으나 호출 중 오류
            typeId = "";
            typeGb = "";
        }
        
        // typeId + "ListMapper" 형태의 Mapper를 동적으로 가져와서 selectContructionList 호출
        if (typeId == null || typeId.isEmpty()) {
            throw new IllegalArgumentException("typeId가 비어있습니다.");
        }
        
        // 주입받은 ApplicationContext 사용
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }
        
        String [] splitTypeId = StringUtils.split(typeId, ":");
        typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
        
        // typeId + "ListMapper" 형태의 리스트 검색 전용 매퍼 사용
        String mapperBeanName = Character.toLowerCase(typeId.charAt(0)) + typeId.substring(1) + "ListMapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);
        
        String methodName = "constructions".equals(typeGb) ? "selectContructionList" : "selectFacilityList";

        // 매퍼의 selectContructionList 메서드 찾기
        java.lang.reflect.Method[] methods = mapper.getClass().getMethods();
        java.lang.reflect.Method targetMethod = null;
        for (java.lang.reflect.Method method : methods) {
            if (methodName.equals(method.getName())) {
                targetMethod = method;
                break;
            }
        }
        
        if (targetMethod == null) {
            throw new IllegalStateException(mapperBeanName + "에서 "+methodName+" 메소드를 찾을 수 없습니다.");
        }
        
        Object result = targetMethod.invoke(mapper, requestVO);
        return (List<?>) result;
	}

	@Override
	public int selectListTotalCount(Object requestVO) throws Exception {
		String typeId = "";
		String typeGb = "";
        try {
            typeId = (String) requestVO.getClass().getMethod("getTypeId").invoke(requestVO);
            typeGb = (String) requestVO.getClass().getMethod("getTypeGb").invoke(requestVO);

        } catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
            typeId = "";
            typeGb = "";
        }

        if (typeId == null || typeId.isEmpty()) {
            throw new IllegalArgumentException("typeId가 비어있습니다.");
        }
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }
        
        String [] splitTypeId = StringUtils.split(typeId, ":");
        typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
        String mapperBeanName = Character.toLowerCase(typeId.charAt(0)) + typeId.substring(1) + "ListMapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);

		String methodName = "constructions".equals(typeGb) ? "selectContructionListTotalCount" : "selectFacilityListTotalCount";

        java.lang.reflect.Method[] methods = mapper.getClass().getMethods();
        java.lang.reflect.Method targetMethod = null;
        for (java.lang.reflect.Method method : methods) {
            if (methodName.equals(method.getName())) {
                targetMethod = method;
                break;
            }
        }
        if (targetMethod == null) {
            throw new IllegalStateException(mapperBeanName + "에서 "+methodName+" 메소드를 찾을 수 없습니다.");
        }
        Object result = targetMethod.invoke(mapper, requestVO);
        return (result instanceof Integer) ? (Integer) result : Integer.parseInt(String.valueOf(result));
	}

	@Override
	public Object selectDetail(String typeId, Object requestModel){

        Map<String, Object> paramMap = new HashMap<>();

        // requestModel이 Map인 경우 직접 사용, 그렇지 않은 경우 필드 복사
        if (requestModel instanceof Map) {
            paramMap.putAll((Map<String, Object>) requestModel);
        } else {
            // requestModel에서 모든 필드를 paramMap으로 복사 (HashMap 등의 시스템 클래스는 제외)
            try {
                for (Field field : requestModel.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object value = field.get(requestModel);
                    if (value != null) {
                        paramMap.put(field.getName(), value);
                    }
                }
            } catch (IllegalAccessException | SecurityException e) {
                // 리플렉션 접근 제한 / 보안 매니저로 인한 실패
                log.warn("필드 접근 실패 (시스템 클래스일 수 있음)", e);
            }

        }

        log.debug("상세조회 파라미터: typeId={}, requestModel={}, paramMap={}", typeId, requestModel.getClass().getSimpleName(), paramMap);

        if (typeId == null || typeId.isEmpty()) {
            throw new IllegalArgumentException("typeId가 비어있습니다.");
        }

        // 주입받은 ApplicationContext 사용
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }

        String[] splitTypeId = StringUtils.split(typeId, ":");
		typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
        String mapperBeanName = Character.toLowerCase(typeId.charAt(0)) + typeId.substring(1) + "Mapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);

        log.debug("매퍼 빈: {}", mapperBeanName);

        // 1. 매퍼의 모든 메소드 중 selectByPrimaryKey 찾기
        Method[] methods = mapper.getClass().getMethods();
        Method targetMethod = null;
        for (Method method : methods) {
            if ("selectByPrimaryKey".equals(method.getName())) {
                targetMethod = method;
                break;
            }
        }
        if (targetMethod == null) {
            throw new IllegalStateException(mapperBeanName + "에서 selectByPrimaryKey 메소드를 찾을 수 없습니다.");
        }
        
        // 3. 메소드 호출
        try {
            log.debug("매퍼 메서드 호출: {}.selectByPrimaryKey()", mapperBeanName);
            //Object result = targetMethod.invoke(mapper, args);
            Long gid = (Long) paramMap.get("gid");
            Object result = targetMethod.invoke(mapper, new Object[]{gid});
            log.debug("매퍼 메서드 호출 완료: 결과 타입 = {}", result != null ? result.getClass().getSimpleName() : "null");
            return result;
        } catch (IllegalAccessException | IllegalArgumentException e) {
            log.error("매퍼 메서드 호출 중 리플렉션 접근 오류", e);
            throw new IllegalStateException("매퍼 메서드 호출 중 오류가 발생했습니다.", e);

        } catch (java.lang.reflect.InvocationTargetException e) {
            Throwable cause = (e.getCause() != null) ? e.getCause() : e;
            log.error("매퍼 메서드 호출 대상 메서드에서 예외 발생", cause);
            throw new IllegalStateException("매퍼 메서드 호출 중 오류가 발생했습니다.", cause);
        }
	}


    @Override
    @SuppressWarnings("unchecked")
    @Transactional
    public Object insert(Object requestModel) throws Exception {
        
        Map<String, Object> historyParamMap = new HashMap<>();
        Map<String, Object> ParamMap = new HashMap<>();

        String typeId = "";
        
        // typeId 추출
        try {
            typeId = (String) requestModel.getClass().getMethod("getTypeId").invoke(requestModel);
        } catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
            log.debug("typeId 추출 실패", e);
        }

        if (typeId == null || typeId.isEmpty()) {
            throw new IllegalArgumentException("typeId가 비어있습니다.");
        }
        
        //공통 필드 추가(최종수정자명)
        setInsertFields(requestModel);
        
        // requestModel에서 모든 필드를 historyParamMap 복사
        for (Field field : requestModel.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            Object value = field.get(requestModel);
            if (value != null) {
                ParamMap.put(field.getName(), value);
            }
        }

        // 주입받은 ApplicationContext 사용
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }
        
        String[] splitTypeId = StringUtils.split(typeId, ":");
		typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
		typeId = Character.toLowerCase(typeId.charAt(0)) + typeId.substring(1); // 모든 가공은 여기서 수행
        String mapperBeanName = typeId + "Mapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);
        
        log.debug("매퍼 빈: {}", mapperBeanName);
        
        // 매퍼의 insert 메서드 찾기
        Method[] methods = mapper.getClass().getMethods();
        Method targetMethod = null;
        for (Method method : methods) {
            if ("insert".equals(method.getName())) {
                targetMethod = method;
                break;
            }
        }
        
        if (targetMethod == null) {
            throw new IllegalStateException(mapperBeanName + "에서 insert 메소드를 찾을 수 없습니다.");
        }
        
        //TODO: 임시 ObjectId 생성 로직 구현 필요 - 2025-09-17 불필요 이재룡
        //generateObjectId(requestModel, typeId);
        
        // 매퍼 메서드 호출
        Object result = targetMethod.invoke(mapper, requestModel);
        int affectedRows = (Integer) result;

        if(affectedRows > 0) {
            // useGeneratedKeys에 의해 requestModel.gid에 생성된 키가 설정됨
            try {
                Long generatedGid = (Long) requestModel.getClass().getMethod("getGid").invoke(requestModel);
                if (generatedGid != null) {
                    log.info("생성된 GID: {}", generatedGid);
                    ParamMap.put("gid", generatedGid);
                }
            } catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
                log.error("GID 추출 실패", e);
            }

        }

        List<Map<String, Object>> features = new ArrayList<>();
        Map<String, Object> properties = new HashMap<>();
        //2025-11-11 seyoung : __attach 붙은 경우 분기 처리
        Object savedModel = null;
        if(typeId.endsWith("Attach")) {
          int idx = typeId.indexOf("Attach");
          typeId = typeId.substring(0,idx);
          savedModel = selectDetail(camelToSnake(typeId) + "__attach", ParamMap);
        }else {
          savedModel = selectDetail(camelToSnake(typeId), ParamMap);
        }
        Map<String, Object> modelMap = new HashMap<>();

        // savedModel이 Map인 경우 직접 사용, 그렇지 않은 경우 필드 복사
        if (savedModel instanceof Map) {
            modelMap.putAll((Map<String, Object>) savedModel);
        } else {
            // savedModel에서 모든 필드를 modelMap으로 복사 (시스템 클래스는 제외)
            try {
                for (Field field : savedModel.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object value = field.get(savedModel);
                    if (value != null) {
                        modelMap.put(field.getName(), value);
                    }
                }
            } catch (IllegalAccessException | SecurityException e) {
                // 시스템/보안 제한 등으로 접근 실패 → 로그만 남기고 계속 진행
                log.warn("savedModel 필드 접근 실패", e);
            }

        }
        
        String historyTableName = camelToSnake(typeId) + "_h";
        boolean existsHistoryTable = g2FLayerMapper.checkHistoryTableExists(SCHEMA_NAME, historyTableName);
        if(existsHistoryTable) {
            // 등록이력 저장 (g2f layer 이용)
            List<Map<String, Object>> tableColumns = g2FLayerMapper.selectTableColumns(SCHEMA_NAME, historyTableName);
            Map<String, Object> columnMap = new HashMap<>();
            columnMap.put("column_name", "gid");
            columnMap.put("data_type", "bigint");
            columnMap.put("is_nullable", "NO");
            tableColumns.add(columnMap);
            
            // modelMap의 key를 camelToSnake로 변경하여 snakeCaseModelMap을 생성
            Map<String, Object> snakeCaseModelMap = new HashMap<>();
            for (Map.Entry<String, Object> entry : modelMap.entrySet()) {
                String snakeKey = camelToSnake(entry.getKey());
                snakeCaseModelMap.put(snakeKey, entry.getValue());
            }
            properties.put("properties", snakeCaseModelMap);
            features.add(properties);
            historyParamMap.put("features", features);
            historyParamMap.put("schemaName", SCHEMA_NAME);
            historyParamMap.put("tableName", camelToSnake(typeId));
            historyParamMap.put("srid", SRID);
            historyParamMap.put("columns", tableColumns); // 메타데이터 추가
            g2FLayerService.insertHistoryAdded(SCHEMA_NAME, camelToSnake(typeId), historyParamMap);
        }
        return result;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object update(Object requestModel) throws Exception {
        String typeId = "";
        Map<String, Object> paramMap = new HashMap<>();

        Map<String, Object> historyParamMap = new HashMap<>();
        Map<String, Object> ParamMap = new HashMap<>();
        
        // requestModel에서 모든 필드를 paramMap으로 복사
        for (Field field : requestModel.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            Object value = field.get(requestModel);
            if (value != null) {
                paramMap.put(field.getName(), value);
            }
        }
        
        // typeId 추출
        typeId = requestModel.getClass().getSimpleName();

        if (typeId == null || typeId.isEmpty()) {
            throw new IllegalArgumentException("typeId가 비어있습니다.");
        }
        
        // 주입받은 ApplicationContext 사용
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }
        
        //공통 필드 추가(최종수정자명)
        setCommonFields(requestModel);
        
        String mapperBeanName = Character.toLowerCase(typeId.charAt(0)) + typeId.substring(1) + "Mapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);
        
        log.debug("매퍼 빈: {}", mapperBeanName);
        
        // 매퍼의 updateByPrimaryKey 메서드 찾기
        Method[] methods = mapper.getClass().getMethods();
        Method targetMethod = null;
        for (Method method : methods) {
            if ("updateByPrimaryKey".equals(method.getName())) {
                targetMethod = method;
                break;
            }
        }
        
        if (targetMethod == null) {
            throw new IllegalStateException(mapperBeanName + "에서 updateByPrimaryKey 메소드를 찾을 수 없습니다.");
        }
        
        // 매퍼 메서드 호출
        Object result = targetMethod.invoke(mapper, requestModel);
        
        List<Map<String, Object>> features = new ArrayList<>();
        Map<String, Object> properties = new HashMap<>();
        Object savedModel = null;
        if(typeId.endsWith("Attach")) {
            int idx = typeId.indexOf("Attach");
            typeId = typeId.substring(0,idx);
            savedModel = selectDetail(camelToSnake(typeId) + "__attach", requestModel);
        }else {
            savedModel = selectDetail(camelToSnake(typeId), requestModel);
        }
        Map<String, Object> modelMap = new HashMap<>();
        
        // savedModel이 Map인 경우 직접 사용, 그렇지 않은 경우 필드 복사
        if (savedModel instanceof Map) {
            modelMap.putAll((Map<String, Object>) savedModel);
        } else {
            // savedModel에서 모든 필드를 modelMap으로 복사 (시스템 클래스는 제외)
            try {
                for (Field field : savedModel.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object value = field.get(savedModel);
                    if (value != null) {
                        modelMap.put(field.getName(), value);
                    }
                }
            } catch (IllegalAccessException | SecurityException e) {
                // 시스템 클래스 접근 실패 시 로깅만 하고 계속 진행
                log.warn("savedModel 필드 접근 실패", e);
            }

        }
        
        String historyTableName = camelToSnake(typeId) + "_h";
        boolean existsHistoryTable = g2FLayerMapper.checkHistoryTableExists(SCHEMA_NAME, historyTableName);
        if(existsHistoryTable) {
            // 등록이력 저장 (g2f layer 이용)
            List<Map<String, Object>> tableColumns = g2FLayerMapper.selectTableColumns(SCHEMA_NAME, historyTableName);
            
            // modelMap의 key를 camelToSnake로 변경하여 snakeCaseModelMap을 생성
            Map<String, Object> snakeCaseModelMap = new HashMap<>();
            Long savedGid = null;
            for (Map.Entry<String, Object> entry : modelMap.entrySet()) {
                if(entry.getKey().equals("gid")) {
                    savedGid = (Long) entry.getValue();
                }
                String snakeKey = camelToSnake(entry.getKey());
                snakeCaseModelMap.put(snakeKey, entry.getValue());
            }
            
            properties.put("gid", savedGid);
            properties.put("properties", snakeCaseModelMap);
            features.add(properties);
            historyParamMap.put("features", features);
            historyParamMap.put("schemaName", SCHEMA_NAME);
            historyParamMap.put("tableName", camelToSnake(typeId));
            historyParamMap.put("srid", SRID);
            historyParamMap.put("columns", tableColumns); // 메타데이터 추가
            g2FLayerService.insertHistoryModified(SCHEMA_NAME, camelToSnake(typeId), historyParamMap);
        }
        return result;
    }

    
    
    @Override
    @SuppressWarnings("unchecked")
    public Object delete(Object requestModel) {
        String typeId = "";
        Map<String, Object> paramMap = new HashMap<>();
        Map<String, Object> historyParamMap = new HashMap<>();

        // requestModel에서 모든 필드를 paramMap으로 복사
        for (Field field : requestModel.getClass().getDeclaredFields()) {
            try {
                field.setAccessible(true);
                Object value = field.get(requestModel);
                if (value != null) {
                    paramMap.put(field.getName(), value);
                }
            } catch (IllegalAccessException | SecurityException e) {
                // 시스템/보안 제한 등으로 접근 불가 → 로그만 남기고 진행
                log.debug("requestModel 필드 접근 실패", e);
            }
        }

        // typeId 추출
        try {
            typeId = (String) requestModel.getClass().getMethod("getTypeId").invoke(requestModel);
        } catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
            log.debug("typeId 추출 실패", e);
        }

        log.debug("삭제 파라미터: typeId={}, requestModel={}, paramMap={}",
                typeId, requestModel.getClass().getSimpleName(), paramMap);

        if (typeId == null || typeId.isEmpty()) {
            throw new IllegalArgumentException("typeId가 비어있습니다.");
        }

        // 주입받은 ApplicationContext 사용
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }

        // 공통 필드 추가(최종수정자명)
        setCommonFields(requestModel);

        String[] splitTypeId = StringUtils.split(typeId, ":");
        typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);

        String mapperBeanName = Character.toLowerCase(typeId.charAt(0)) + typeId.substring(1) + "Mapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);

        log.debug("매퍼 빈: {}", mapperBeanName);

        // soft delete: updateByPrimaryKey로 use_yn='N' 업데이트
        Method[] methods = mapper.getClass().getMethods();
        Method targetMethod = null;
        for (Method method : methods) {
            if ("updateByPrimaryKey".equals(method.getName())) {
                targetMethod = method;
                break;
            }
        }

        if (targetMethod == null) {
            throw new IllegalStateException(mapperBeanName + "에서 updateByPrimaryKey 메소드를 찾을 수 없습니다.");
        }

        // 매퍼 메서드 호출
        try {
            log.debug("매퍼 메서드 호출: {}.updateByPrimaryKey() (soft delete)", mapperBeanName);

            Object result = targetMethod.invoke(mapper, requestModel);

            log.debug("매퍼 메서드 호출 완료: 결과 타입 = {}",
                    result != null ? result.getClass().getSimpleName() : "null");

            // 삭제 이력 저장 (stts_cd='D')
            List<Map<String, Object>> features = new ArrayList<>();
            Map<String, Object> properties = new HashMap<>();
            Object savedModel;

            if (typeId.endsWith("Attach")) {
                int idx = typeId.indexOf("Attach");
                typeId = typeId.substring(0, idx);
                savedModel = selectDetail(camelToSnake(typeId) + "__attach", requestModel);
            } else {
                savedModel = selectDetail(camelToSnake(typeId), requestModel);
            }

            Map<String, Object> modelMap = new HashMap<>();

            // savedModel이 Map인 경우 직접 사용, 그렇지 않은 경우 필드 복사
            if (savedModel instanceof Map) {
                modelMap.putAll((Map<String, Object>) savedModel);
            } else {
                // savedModel에서 모든 필드를 modelMap으로 복사 (시스템 클래스는 제외)
                try {
                    for (Field field : savedModel.getClass().getDeclaredFields()) {
                        field.setAccessible(true);
                        Object value = field.get(savedModel);
                        if (value != null) {
                            modelMap.put(field.getName(), value);
                        }
                    }
                } catch (IllegalAccessException | SecurityException e) {
                    log.warn("savedModel 필드 접근 실패", e);
                }
            }

            String historyTableName = camelToSnake(typeId) + "_h";
            boolean existsHistoryTable = g2FLayerMapper.checkHistoryTableExists(SCHEMA_NAME, historyTableName);
            if (existsHistoryTable) {
                // 등록이력 저장 (g2f layer 이용)
                List<Map<String, Object>> tableColumns =
                        g2FLayerMapper.selectTableColumns(SCHEMA_NAME, historyTableName);

                // modelMap의 key를 camelToSnake로 변경하여 snakeCaseModelMap을 생성
                Map<String, Object> snakeCaseModelMap = new HashMap<>();
                Long savedGid = null;
                for (Map.Entry<String, Object> entry : modelMap.entrySet()) {
                    if ("gid".equals(entry.getKey())) {
                        // savedModel 타입에 따라 Integer/Long 섞일 수 있어 방어
                        Object gidVal = entry.getValue();
                        if (gidVal instanceof Long) savedGid = (Long) gidVal;
                        else if (gidVal instanceof Integer) savedGid = ((Integer) gidVal).longValue();
                    }
                    String snakeKey = camelToSnake(entry.getKey());
                    snakeCaseModelMap.put(snakeKey, entry.getValue());
                }

                properties.put("gid", savedGid);
                properties.put("properties", snakeCaseModelMap);
                features.add(properties);

                historyParamMap.put("features", features);
                historyParamMap.put("schemaName", SCHEMA_NAME);
                historyParamMap.put("tableName", historyTableName);
                historyParamMap.put("srid", SRID);
                historyParamMap.put("columns", tableColumns); // 메타데이터 추가

                try {
                    int insertedCount = uisLayerMapper.batchInsertHistorySoftDeleted(historyParamMap);
                    log.debug("삭제 이력 저장 완료 (stts_cd='D'): {}.{} - {} 건", SCHEMA_NAME, historyTableName, insertedCount);
                } catch (Exception e) {
                    log.warn("삭제 이력 저장 실패 (무시) - table: {}.{}", SCHEMA_NAME, historyTableName, e);
                }
            }

            return result;

        } catch (IllegalAccessException | IllegalArgumentException e) {
            log.error("매퍼 메서드 호출 중 리플렉션 접근 오류", e);
            throw new IllegalStateException("매퍼 메서드 호출 중 오류가 발생했습니다.", e);

        } catch (java.lang.reflect.InvocationTargetException e) {
            Throwable cause = (e.getCause() != null) ? e.getCause() : e;
            log.error("매퍼 메서드 호출 대상 메서드에서 예외 발생", cause);
            throw new IllegalStateException("매퍼 메서드 호출 중 오류가 발생했습니다.", cause);
        }
    }


    @Override
    public List<?> selectDetailTabList(String tabId, Map<String, Object> params) throws Exception {
        if (tabId == null || tabId.isEmpty()) {
            throw new IllegalArgumentException("tabId가 비어있습니다.");
        }
        if (applicationContextProvider == null) {
            throw new IllegalStateException("ApplicationContext가 주입되지 않았습니다.");
        }

        String [] splitTabId = StringUtils.split(tabId, ":");
        tabId = Arrays.stream(splitTabId).skip(1).findFirst().orElse(tabId);
        // 탭 ID 기반 리스트 매퍼 빈명: lowerCamel(tabId)+"ListMapper"
        String mapperBeanName = Character.toLowerCase(tabId.charAt(0)) + tabId.substring(1) + "ListMapper";
        Object mapper = applicationContextProvider.getBean(mapperBeanName, Object.class);

        // 메서드 선택: selectList
        java.lang.reflect.Method[] methods = mapper.getClass().getMethods();
        java.lang.reflect.Method targetMethod = null;
        for (java.lang.reflect.Method method : methods) {
            if ("selectList".equals(method.getName())) {
                targetMethod = method; break;
            }
        }
        if (targetMethod == null)
        {
            throw new IllegalStateException(mapperBeanName + "에서 selectList 메소드를 찾을 수 없습니다.");
        }
        if(params.isEmpty())
        {
        	throw new IllegalStateException(mapperBeanName + "에서 parmaeter를 찾을 수 없습니다.");
        }
        Object result = targetMethod.invoke(mapper, params);
        return (List<?>) result;
    }
    
    @Override
    public List<Map<String, Object>> selectChangeHistoryList(final String tableName, final String gid) {
    	
    	// 테이블 메타데이터 미리 조회
        List<Map<String, Object>> tableColumns = uisLayerMapper.selectTableColumns(SCHEMA_NAME, tableName); 
    	
    	Map<String, Object> params = new HashMap<>();
        params.put("schemaName", SCHEMA_NAME);
        params.put("tableName", tableName);
        params.put("gid", gid);
        params.put("srid", SRID);
        params.put("columns", tableColumns); // 메타데이터 추가
        
        List<Map<String, Object>> rows = uisLayerMapper.batchInsertHistorySelect(params);
		return rows;
	}

    
    @Override
    public List<Map<String, Object>> batchTabConnTargetTableSelect(final String tableName, final Map<String, Object> inputParam) {
    	
    	// 테이블 메타데이터 미리 조회
        List<Map<String, Object>> tableColumns = uisLayerMapper.selectTableColumns(SCHEMA_NAME, tableName); 
    	
    	Map<String, Object> params = new HashMap<>();
        params.put("schemaName", SCHEMA_NAME);
        params.put("tableName", tableName);
        params.put("columns", tableColumns); // 메타데이터 추가
        params.put("inputParam", inputParam); // 입력 파라미터 추가
        
        List<Map<String, Object>> rows = uisLayerMapper.batchTabConnTargetTableSelect(params);
		return rows;
	}

    /**
     * @deprecated 2025-11-28 seyoung : 연결 테이블 정보 저장 시 사용하지 않음 - 저장 공통으로 처리
     * @param tableName 테이블 이름
     * @param inputParam 입력 파라미터
     * @return 삽입된 행 목록
     */
    @Deprecated
    @Override
    public List<Map<String, Object>> batchTabConnTargetTableInsert(final String tableName, final Map<String, Object> inputParam) {
    	
    	// 테이블 메타데이터 미리 조회
        List<Map<String, Object>> tableColumns = uisLayerMapper.selectTableColumns(SCHEMA_NAME, tableName); 
    	
    	Map<String, Object> params = new HashMap<>();
        params.put("schemaName", SCHEMA_NAME);
        params.put("tableName", tableName);
        params.put("columns", tableColumns); // 메타데이터 추가
        params.put("inputParam", inputParam); // 입력 파라미터 추가
        
        List<Map<String, Object>> insertedRows = uisLayerMapper.batchTabConnTargetTableInsert(params);
        if(insertedRows.size() == 0) {
            return insertedRows;
        }

        Map<String, Object> savedModel = insertedRows.get(0);
        Map<String, Object> historyParamMap = new HashMap<>();
        Map<String, Object> modelMap = new HashMap<>();
        Map<String, Object> properties = new HashMap<>();
        List<Map<String, Object>> features = new ArrayList<>();

        // savedModel이 Map인 경우 직접 사용, 그렇지 않은 경우 필드 복사
        if (savedModel instanceof Map) {
            modelMap.putAll((Map<String, Object>) savedModel);
        } else {
            // savedModel에서 모든 필드를 modelMap으로 복사 (시스템 클래스는 제외)
            try {
                for (Field field : savedModel.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object value = field.get(savedModel);
                    if (value != null) {
                        modelMap.put(field.getName(), value);
                    }
                }
            } catch (IllegalAccessException | SecurityException e) {
                // 시스템 클래스/보안 제한 등으로 접근 실패 → 로그만 남기고 계속 진행
                log.warn("savedModel 필드 접근 실패", e);
            }
        }
        
        String historyTableName = camelToSnake(tableName) + "_h";
        boolean existsHistoryTable = g2FLayerMapper.checkHistoryTableExists(SCHEMA_NAME, historyTableName);
        if(existsHistoryTable) {
            // 등록이력 저장 (g2f layer 이용)
            List<Map<String, Object>> histTableColumns = g2FLayerMapper.selectTableColumns(SCHEMA_NAME, historyTableName);
            Map<String, Object> columnMap = new HashMap<>();
            columnMap.put("column_name", "gid");
            columnMap.put("data_type", "bigint");
            columnMap.put("is_nullable", "NO");
            histTableColumns.add(columnMap);
            
            // modelMap의 key를 camelToSnake로 변경하여 snakeCaseModelMap을 생성
            Map<String, Object> snakeCaseModelMap = new HashMap<>();
            for (Map.Entry<String, Object> entry : modelMap.entrySet()) {
                String snakeKey = camelToSnake(entry.getKey());
                snakeCaseModelMap.put(snakeKey, entry.getValue());
            }
            properties.put("properties", snakeCaseModelMap);
            features.add(properties);
            historyParamMap.put("features", features);
            historyParamMap.put("schemaName", SCHEMA_NAME);
            historyParamMap.put("tableName", camelToSnake(tableName));
            historyParamMap.put("srid", SRID);
            historyParamMap.put("columns", histTableColumns); // 메타데이터 추가
            g2FLayerService.insertHistoryAdded(SCHEMA_NAME, camelToSnake(tableName), historyParamMap);
        }
		return insertedRows;
	}

    
    @Override
    public int batchTabConnTargetTableUpdate(final String tableName, final Map<String, Object> inputParam) {
    	
        String typeId = tableName;
        
        Map<String, Object> historyParamMap = new HashMap<>();
        Map<String, Object> selectParam = new HashMap<>();
        Object savedModel = null;
        Map<String, Object> properties = new HashMap<>();

    	// 테이블 메타데이터 미리 조회
        List<Map<String, Object>> tableColumns = uisLayerMapper.selectTableColumns(SCHEMA_NAME, tableName); 
    	
    	Map<String, Object> params = new HashMap<>();
        params.put("schemaName", SCHEMA_NAME);
        params.put("tableName", tableName);
        params.put("columns", tableColumns); // 메타데이터 추가
        params.put("inputParam", inputParam); // 입력 파라미터 추가
        
        int resutl = uisLayerMapper.batchTabConnTargetTableUpdate(params);

        if(resutl == 0) {
            return resutl;
        }

        try {
            log.info("savedModel 조회 시작: {}", typeId);
            // inputParam.get("gid")는 String이므로 Long으로 변환해서 넣어줌
            Object gidObj = inputParam.get("gid");
            Long gidLong = null;
            if (gidObj != null) {
                if (gidObj instanceof String) {
                    try {
                        gidLong = Long.parseLong((String) gidObj);
                    } catch (NumberFormatException e) {
                        log.warn("gid 값이 Long으로 변환되지 않음: {}", gidObj);
                    }
                } else if (gidObj instanceof Number) {
                    gidLong = ((Number) gidObj).longValue();
                }
            }
            selectParam.put("gid", gidLong);

            if (typeId.endsWith("Attach")) {
                int idx = typeId.indexOf("Attach");
                typeId = typeId.substring(0, idx);
                savedModel = selectDetail(camelToSnake(typeId) + "__attach", selectParam);
            } else {
                savedModel = selectDetail(camelToSnake(typeId), selectParam);
            }

        } catch (IllegalArgumentException e) {
            // 잘못된 인자(테이블명/파라미터 등) 가능
            log.error("savedModel 조회 실패(잘못된 인자)", e);
            return resutl;

        } catch (RuntimeException e) {
            // selectDetail 내부 예외 등
            log.error("savedModel 조회 실패", e);
            return resutl;
        }


        Map<String, Object> modelMap = new HashMap<>();
        List<Map<String, Object>> features = new ArrayList<>();
        
        // savedModel이 Map인 경우 직접 사용, 그렇지 않은 경우 필드 복사
        if (savedModel instanceof Map) {
            modelMap.putAll((Map<String, Object>) savedModel);
        } else {
            // savedModel에서 모든 필드를 modelMap으로 복사 (시스템 클래스는 제외)
            try {
                for (Field field : savedModel.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object value = field.get(savedModel);
                    if (value != null) {
                        modelMap.put(field.getName(), value);
                    }
                }
            } catch (IllegalAccessException | SecurityException e) {
                // 시스템 클래스 접근 실패 시 로깅만 하고 계속 진행
                log.warn("savedModel 필드 접근 실패", e);
            }

        }
        
        String historyTableName = camelToSnake(typeId) + "_h";
        boolean existsHistoryTable = g2FLayerMapper.checkHistoryTableExists(SCHEMA_NAME, historyTableName);
        if(existsHistoryTable) {
            // 등록이력 저장 (g2f layer 이용)
            List<Map<String, Object>> histTableColumns = g2FLayerMapper.selectTableColumns(SCHEMA_NAME, historyTableName);
            
            // modelMap의 key를 camelToSnake로 변경하여 snakeCaseModelMap을 생성
            Map<String, Object> snakeCaseModelMap = new HashMap<>();
            Long savedGid = null;
            for (Map.Entry<String, Object> entry : modelMap.entrySet()) {
                if(entry.getKey().equals("gid")) {
                    savedGid = (Long) entry.getValue();
                }
                String snakeKey = camelToSnake(entry.getKey());
                snakeCaseModelMap.put(snakeKey, entry.getValue());
            }
            
            properties.put("gid", savedGid);
            properties.put("properties", snakeCaseModelMap);
            features.add(properties);
            historyParamMap.put("features", features);
            historyParamMap.put("schemaName", SCHEMA_NAME);
            historyParamMap.put("tableName", camelToSnake(typeId));
            historyParamMap.put("srid", SRID);
            historyParamMap.put("columns", histTableColumns); // 메타데이터 추가
            g2FLayerService.insertHistoryModified(SCHEMA_NAME, camelToSnake(typeId), historyParamMap);
        }

        return resutl;
	}

    //2025-11-05 seyoung : 저장/수정/삭제 시 공통 필드 값 추가
    private void setCommonFields(Object requestModel) {
        //최종수정자
        setField(requestModel, "lastMdfcnId", RequestContext.getCurrentUserId());
        //최종수정일시
        setField(requestModel, "lastMdfcnDt",  new Date());
    }

    //insert 필드 추가
    private void setInsertFields(Object requestModel) {
        //공통 필드 추가
        setCommonFields(requestModel);
        setField(requestModel, "frstRegId", RequestContext.getCurrentUserId());
    }

    private void setField(Object requestModel, String fieldNm, Object value) {
        try {
            Field field = findField(requestModel.getClass(), fieldNm);
            if (field == null) return;

            boolean canAccess = field.canAccess(requestModel);
            field.setAccessible(true);
            field.set(requestModel, value);
            field.setAccessible(canAccess);

        } catch (IllegalAccessException | SecurityException e) {
            log.error("공통 필드 세팅 실패", e);
            return;
        }

    }

    private static Field findField(Class<?> type, String name) {
        for (Class<?> c = type; c != null && c != Object.class; c = c.getSuperclass()) {
            try {
                return c.getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                // 정상 흐름: 상위 클래스로 계속 탐색
                continue;
            }
        }
        return null;
    }

    // String typeId = "cmtCnstD"(카멜타입)를 "cmt_cnst_d" 로 변환
    private String camelToSnake(String camelCase) {
        return camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
    }
}


