package incheon.uis.ums.web;

import incheon.uis.ums.mapper.ConstructionSeqMapper;
import incheon.uis.ums.service.DynamicModelService;
import incheon.uis.ucf.model.UisDefaultModel;
import incheon.uis.ums.util.UisConfigUtils;
import incheon.uis.ums.util.UisConstructionNumberGenerator;
import incheon.uis.ucf.vo.UisDefaultVO;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;

@Slf4j
@Controller
public class UisBaseController {
	
	protected final ConstructionSeqMapper constructionSeqMapper;
	protected final UisConstructionNumberGenerator constructionNumberGenerator;
	protected final DynamicModelService dynamicModelService;

	@Autowired
	protected UisConfigUtils uisConfigUtils;

	public UisBaseController(ConstructionSeqMapper constructionSeqMapper
			, UisConstructionNumberGenerator constructionNumberGenerator
			, DynamicModelService dynamicModelService) {
		this.constructionSeqMapper = constructionSeqMapper;
		this.constructionNumberGenerator = constructionNumberGenerator;
		this.dynamicModelService = dynamicModelService;
	}

    /**
     * typeId에 따른 적절한 RequestVO 객체 생성
     */
    protected Object createRequestVO(String typeId, HttpServletRequest request) throws Exception {
        Object requestVO;
        
		String[] splitTypeId = StringUtils.split(typeId, ":");
		// rd:abc_def_g 와 같이 인풋된 typeId에서 실제 typeId(AbcDefG)로 가공
		typeId = strTypeIdToCamel(Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId));
		String groupId = Arrays.stream(splitTypeId)
		                      .findFirst()
		                      .filter(x -> splitTypeId.length > 1)
		                      .orElse("");
		
        try {
            // typeId 형태의 모델 클래스명 생성
            String voClassName = "incheon.uis." + groupId + ".vo." + typeId + "RequestVO";
            
            // 클래스 로드 및 인스턴스 생성
            Class<?> voClass = Class.forName(voClassName);
            requestVO = voClass.getDeclaredConstructor().newInstance();
            
            // typeId 설정
            if (requestVO instanceof UisDefaultVO) {
                ((UisDefaultVO) requestVO).setTypeId(typeId);
            }
            
        } catch (ClassNotFoundException e) {
            log.warn("RequestVO 클래스를 찾을 수 없음: {}, 기본 VO 사용", typeId);
            // 해당 클래스가 없으면 기본 Model 사용
            requestVO = new UisDefaultVO();
            ((UisDefaultVO) requestVO).setTypeId(typeId);
        }
        
        // HTTP 파라미터를 Model 필드에 매핑
        mapParametersToObject(requestVO, request, false);
        
        return requestVO;
    }
    
    /**
     * HTTP 파라미터를 Model 객체의 필드에 매핑
     */
    protected void mapParametersToObject(Object object, HttpServletRequest request, boolean isNew) throws Exception {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
        
        // 모든 요청 파라미터를 순회
        for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            String paramName = strToCamelKey(entry.getKey());
            String[] paramValues = entry.getValue();
            
            if (paramValues == null || paramValues.length == 0 || 
                paramValues[0] == null || paramValues[0].trim().isEmpty()) {
                continue;
            }
            
            String paramValue = paramValues[0].trim();

            try {
	            // Object 클래스에서 해당 필드 찾기
	            Field field = findFieldInClass(object.getClass(), paramName);
	            if (field != null) {
	                field.setAccessible(true);
	                
	                // 필드 타입에 따른 값 변환
	                Class<?> fieldType = field.getType();
	                Object convertedValue = convertValue(paramValue, fieldType, dateFormat);
	                
	                if (convertedValue != null) {
	                    field.set(object, convertedValue);
	                }
	            }
            } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
                log.debug("파라미터 매핑 실패: {} = {}", paramName, paramValue, e);
                // 매핑 실패는 무시하고 계속 진행
            }
        }
        
        try {
            // Object 클래스에서 해당 필드 찾기
            Field useYnfield = findFieldInClass(object.getClass(), "useYn");
            String useYnVal = (String) request.getAttribute("useYn");
            
            if (useYnfield != null && useYnVal != null) {
            	useYnfield.setAccessible(true);
                
                // 필드 타입에 따른 값 변환
                Class<?> fieldType = useYnfield.getType();
                Object convertedValue = convertValue(useYnVal, fieldType, dateFormat);
                
                if (convertedValue != null) {
                	useYnfield.set(object, convertedValue);
                }
            }
            
            // Object 클래스에서 해당 필드 찾기
            Field frstRegIdfield = findFieldInClass(object.getClass(), "frstRegId");
            Field frstLastMdfcnIdfield = findFieldInClass(object.getClass(), "lastMdfcnId");
            Field lastMdfcnDtfield = findFieldInClass(object.getClass(), "lastMdfcnDt");
            String userIdVal = "system"; // TODO: 세션 사용자ID 처리
            
            if(userIdVal != null) {
            	//최초 등록자 ID
            	if (frstRegIdfield != null) {
            		frstRegIdfield.setAccessible(true);
                    
                    // 필드 타입에 따른 값 변환
                    Class<?> fieldType = frstRegIdfield.getType();
                    Object convertedValue = convertValue(userIdVal, fieldType, dateFormat);
                    
                    if (convertedValue != null) {
                    	frstRegIdfield.set(object, convertedValue);
                    }
                }
            	//최종 수정자 ID
            	if (frstLastMdfcnIdfield != null) {
            		frstLastMdfcnIdfield.setAccessible(true);
                    
                    // 필드 타입에 따른 값 변환
                    Class<?> fieldType = frstLastMdfcnIdfield.getType();
                    Object convertedValue = convertValue(userIdVal, fieldType, dateFormat);
                    
                    if (convertedValue != null) {
                    	frstLastMdfcnIdfield.set(object, convertedValue);
                    }
                }
            	//최종 수정일시
            	if (lastMdfcnDtfield != null) {
            		lastMdfcnDtfield.setAccessible(true);
                    
                    // 필드 타입에 따른 값 변환
                    Object convertedValue = new Date();
                    
                    if (convertedValue != null) {
                    	lastMdfcnDtfield.set(object, convertedValue);
                    }
                }
            }


        } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
            log.info("파라미터 매핑 실패: userYn, frstRegId, lastMdfcnId, lastMdfcnDt", e);
            // 매핑 실패는 무시하고 계속 진행
        }

        // typeGb 설정
        if (object instanceof UisDefaultModel) {
        	String typeId = ((UisDefaultModel) object).getTypeId();
            ((UisDefaultModel) object).setTypeGb(uisConfigUtils.getTypeGbByTypeId(typeId));
        } else if (object instanceof UisDefaultVO) {
        	String typeGbFromRequest = request.getParameter("typeGb");
        	if (typeGbFromRequest == null || typeGbFromRequest.trim().isEmpty())
        	{
        		String typeId = ((UisDefaultVO) object).getTypeId();
        		typeGbFromRequest = uisConfigUtils.getTypeGbByTypeId(typeId);
        	}
            ((UisDefaultVO) object).setTypeGb(typeGbFromRequest);
        }
        
        // 신규등록 시 키 필드 자동 생성
        //if(isNew) {
        //    generateKeyFields(object, request);
        //}
    }

    /**
     * 신규등록 시 키 필드 자동 생성
     */
    @Deprecated
    private void generateKeyFields(Object model, HttpServletRequest request) {
    	
        String typeId = null;
        String typeGb = null;
        
        try {
            // typeId, typeGb 가져오기
            if (model instanceof UisDefaultModel) {
                typeId = ((UisDefaultModel) model).getTypeId();
                typeGb = ((UisDefaultModel) model).getTypeGb();
            } else if (model instanceof UisDefaultVO) {
                typeId = ((UisDefaultVO) model).getTypeId();
                typeGb = ((UisDefaultVO) model).getTypeGb();
            } else {
                try {
                    Field typeIdField = model.getClass().getDeclaredField("typeId");
                    typeIdField.setAccessible(true);
                    typeId = (String) typeIdField.get(model);

                    Field typeGbField = model.getClass().getDeclaredField("typeGb");
                    typeGbField.setAccessible(true);
                    typeGb = (String) typeGbField.get(model);
                } catch (ReflectiveOperationException | SecurityException e) {
                    log.debug("typeId/typeGb 필드를 찾을 수 없음", e);
                }
            }
            
            if (typeId == null || typeId.isEmpty()) {
                return;
            }
            
            log.info("키 필드 자동 생성 시작: typeId={}, typeGb={}", typeId, typeGb);
            
            // YAML 설정에서 key 속성을 가진 필드들 조회
            List<Map<String, Object>> keyFields = constructionNumberGenerator.getDetailFieldsWithAttribute(typeGb, typeId, "key");
            String mngtValue = null;
            
            // 키 필드들을 순회하며 처리
            for (Map<String, Object> fieldConfig : keyFields) {
                String fieldId = (String) fieldConfig.get("id");
                if (fieldId == null) {
                    continue;
                }
                
                log.debug("키 필드 처리: typeId={}, fieldId={}", typeId, fieldId);
                
                // 해당 필드가 Model에 존재하는지 확인
                Field field = findFieldInClass(model.getClass(), fieldId);
                if (field == null) {
                    log.debug("필드를 찾을 수 없음: {}", fieldId);
                    continue;
                }
                
                field.setAccessible(true);
                Object currentValue = field.get(model);
                
                // 자동생성 대상 필드들 처리 (idn)
                if ("idn".equals(fieldId) && constructionNumberGenerator.isFieldAutoGenerated(typeGb, typeId, fieldId)) {
                    if (currentValue == null || (currentValue instanceof String && ((String) currentValue).trim().isEmpty())) {
                        // mngt 값이 없으면 오류
                    	mngtValue = (String) findFieldInClass(model.getClass(), "mngt").get(model);
                        if (mngtValue == null) {
                        	// TODO: Login 한 사용자의 관리기관을 활용 가능한지는 확인 필요함
                        	log.info("idn - 자동생성 대상 필드 mngt 빈 기준 조회");
                        }
                        
                        String generatedIdn = generateConstructionNumber(typeGb, typeId, mngtValue);
                        // 필드 타입에 따라 값 변환 후 할당
                        Class<?> fieldType = field.getType();
                        if (fieldType == String.class) {
                            field.set(model, generatedIdn);
                        } else if (fieldType == Integer.class || fieldType == int.class) {
                            try {
                                field.set(model, Integer.parseInt(generatedIdn));
                            } catch (NumberFormatException nfe) {
                                log.warn("공사번호 자동 생성값을 Integer로 변환 실패: {} -> {}", generatedIdn, fieldId);
                                field.set(model, 0); // 기본값 할당
                            }
                        } else {
                            // 기타 타입은 String으로 할당 시도
                            field.set(model, generatedIdn);
                        }
                        log.info("공사번호 자동 생성: {} = {}", fieldId, generatedIdn);
                    }
                }
            }
            
            log.info("키 필드 자동 생성 완료: typeId={}", typeId);

        } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
            log.error("키 필드 자동 생성 중 오류: typeId={}", typeId, e);
        }
    }
    

    /**
     * typeId에 따른 적절한 Model 객체 생성
     */
    protected Object createRequestModel(String typeId, HttpServletRequest request, boolean isNew) throws Exception {
        Object requestModel;
        
        try {
            
            // typeId에 따른 Model 클래스명 생성
        	String[] splitTypeId = StringUtils.split(typeId, ":");
    		typeId = strTypeIdToCamel(Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId));
    		String groupId = Arrays.stream(splitTypeId)
    		                      .findFirst()
    		                      .filter(x -> splitTypeId.length > 1)
    		                      .orElse("");
        	// typeId 형태의 모델 클래스명 생성
            //String modelClassName = "incheon.uis." + groupId + ".model." + typeId;
            
            // 클래스 로드 및 인스턴스 생성
            //Class<?> modelClass = Class.forName(modelClassName);
            //requestModel = modelClass.getDeclaredConstructor().newInstance();
    		requestModel = dynamicModelService.getModelInstance(typeId);
            // typeId 설정
    		log.debug("## typeId : {}", typeId);
            ((UisDefaultModel) requestModel).setTypeId(typeId);
            log.debug("## requestModel : {}", requestModel);

        } catch (SecurityException e) {
            log.warn("Model 클래스를 찾을 수 없음: {}, 기본 Model 사용", typeId);
            log.error("Model 생성 중 오류 발생", e);
            // 해당 클래스가 없으면 기본 Model 사용
            requestModel = new UisDefaultModel();
            ((UisDefaultModel) requestModel).setTypeId(typeId);
        }
        
        // HTTP 파라미터를 Model 필드에 매핑
        mapParametersToObject(requestModel, request, isNew);
        
        return requestModel;
    }

    /**
     * 클래스 계층구조에서 필드 찾기
     */
    protected Field findFieldInClass(Class<?> clazz, String fieldName) {
        Class<?> current = clazz;
        while (current != null) {
            try {
            	Field field = current.getDeclaredField(fieldName);
            	field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException e) {
                current = current.getSuperclass();
            }
        }
        return null;
    }

    /**
     * 문자열 값을 해당 타입으로 변환
     */
    protected Object convertValue(String value, Class<?> targetType, SimpleDateFormat dateFormat) {
        try {
            if (targetType == String.class) {
                return value;
            } else if (targetType == Integer.class || targetType == int.class) {
                return Integer.parseInt(value);
            } else if (targetType == Long.class || targetType == long.class) {
                return Long.parseLong(value);
            } else if (targetType == Double.class || targetType == double.class) {
                return Double.parseDouble(value);
            } else if (targetType == Boolean.class || targetType == boolean.class) {
                return Boolean.parseBoolean(value);
            } else if (targetType == Date.class) {
                return dateFormat.parse(value);
            } else if (targetType == java.math.BigDecimal.class) {
                return new java.math.BigDecimal(value.replaceAll(",", ""));
            } else {
                return value;
            }
        } catch (NumberFormatException | java.text.ParseException e) {
            log.debug("값 변환 실패: {} -> {}", value, targetType.getSimpleName(), e);
            return null; // 변환 실패 시 null 반환
        }
    }

    /**
     * VO 객체의 입력값 검증
     * TODO : VO 가 아니라 yml 의 기준으로 변경 필요
     */
    @Deprecated
    private String validateRequestVO(String typeGb, Object requestModel) {
        // 기본 검증: null 체크
        if (requestModel == null) {
            return "요청 데이터가 없습니다.";
        }
        
        try {
            // typeId 가져오기
            String typeId = null;
            if (requestModel instanceof UisDefaultModel) {
                typeId = ((UisDefaultModel) requestModel).getTypeId();
            } else {
                // 리플렉션으로 typeId 가져오기
                try {
                    Field typeIdField = requestModel.getClass().getDeclaredField("typeId");
                    typeIdField.setAccessible(true);
                    typeId = (String) typeIdField.get(requestModel);
                } catch (NoSuchFieldException | IllegalAccessException | SecurityException e) {
                    log.debug("typeId 필드를 찾을 수 없음", e);
                }
            }
            
            if (typeId == null || typeId.isEmpty()) {
                return "대장 종류가 지정되지 않았습니다.";
            }
            
            // yml 설정 기반 필수 필드 검증
            String validationError = validateRequiredFields(typeGb, requestModel, typeId);
            if (validationError != null) {
                return validationError;
            }
            
            log.debug("Medel 검증 완료: {}", requestModel.getClass().getSimpleName());
            return null; // 검증 성공

        } catch (RuntimeException e) {
            log.error("Model 검증 중 오류 발생", e);
            return "검증 중 오류가 발생했습니다.";
        }
    }
    /**
     * yml 설정 기반 필수 필드 검증
     */
    @Deprecated
    private String validateRequiredFields(String typeGb, Object model, String typeId) {
        try {
            log.debug("필수 필드 검증 시작: typeId={}", typeId);
            
            // YAML에서 필수 필드와 disabled 필드 정보 가져오기
            List<String> requiredFields = constructionNumberGenerator.getRequiredFields(typeGb, typeId);
            List<String> disabledFields = constructionNumberGenerator.getDisabledFields(typeGb, typeId);
            
            if (requiredFields == null || requiredFields.isEmpty()) {
                log.debug("필수 필드 설정을 찾을 수 없음: {}", typeId);
                return null; // 설정이 없으면 검증 통과
            }
            
            // 필수 필드 검증
            for (String fieldName : requiredFields) {
                // disabled 필드는 검증에서 제외
                if (disabledFields != null && disabledFields.contains(fieldName)) {
                    log.debug("disabled 필드 제외: {}", fieldName);
                    continue;
                }
                
                try {
                    Field field = findFieldInClass(model.getClass(), fieldName);
                    if (field != null) {
                        field.setAccessible(true);
                        Object value = field.get(model);
                        
                        if (value == null || 
                            (value instanceof String && ((String) value).trim().isEmpty()) ||
                            (value instanceof Number && ((Number) value).doubleValue() == 0)) {
                            return String.format("필수 필드 '%s'가 입력되지 않았습니다.", fieldName);
                        }
                    } else {
                        log.warn("필수 필드를 찾을 수 없음: {}", fieldName);
                    }
                } catch (IllegalAccessException | SecurityException e) {
                    log.warn("필드 검증 중 오류: {}", fieldName, e);
                }
            }
            
            return null; // 모든 필수 필드 검증 통과

        } catch (RuntimeException e) {
            log.error("필수 필드 검증 중 오류", e);
            return "필수 필드 검증 중 오류가 발생했습니다.";
        }
    }
    

    /**
     * 공사번호 자동채번 (등록년도 + SEQ 4자리)
     * 예시: 20250001, 20250002, ...
     */
    private String generateConstructionNumber(String typeGb, String typeId, String mngt) {
        
        try {
            // 현재 년도 가져오기
            int currentYear = java.time.LocalDate.now().getYear();
            
            // YAML 설정을 기반으로 공사번호 생성
            Map<String, Object> rule = constructionNumberGenerator.getNumberingRule(typeGb, typeId);
            if (rule == null) {
                throw new IllegalArgumentException("지원하지 않는 테이블: " + typeId);
            }
            
            String prefix = (String) rule.get("prefix");
            String format = (String) rule.get("format");
            
            // 해당 년도, 테이블, 공사기관별로 다음 SEQ 조회
            String nextSeq = this.getNextConstructionSeq(typeGb, typeId, mngt, currentYear);
            
            // YAML 설정에 따른 공사번호 포맷팅
            String constructionNumber = constructionNumberGenerator.formatConstructionNumber(
                prefix, format, currentYear, Integer.parseInt(nextSeq));
            
            log.info("공사번호 생성: 테이블={}, 공사기관={}, 년도={}, SEQ={}, 프리픽스={}, 결과={}", 
                    typeId, mngt, currentYear, nextSeq, prefix, constructionNumber);
            
            return constructionNumber;

        } catch (IllegalArgumentException | NullPointerException e) {
            log.error("공사번호 자동채번 중 오류: 테이블={}, 공사기관={}", typeId, mngt, e);
            // 오류 발생 시 현재 시간 기반으로 임시 번호 생성
            int currentYear = java.time.LocalDate.now().getYear();
            long currentTime = System.currentTimeMillis();
            String fallbackNumber = String.valueOf(currentYear) + String.format("%04d", (int)(currentTime % 10000));
            log.warn("공사번호 생성 실패, 임시 번호 사용: {}", fallbackNumber);
            return fallbackNumber;
        }
    }
    
    // TODO: 차후 채번 방식에 대한 수정 필요할 듯
    protected String getNextConstructionSeq(String typeGb, String typeId, String mngt, int year) {
    	try {
    		
    		String[] splitTypeId = StringUtils.split(typeId, ":");
    		//typeId = strTypeIdToCamel(Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId));
    		typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
    		
            log.info("공사번호 SEQ 조회: typeId={}, 공사기관={}, 년도={}", typeId, mngt, year);
            
            // YAML 설정을 기반으로 파라미터 계산
            Map<String, Object> paramMap = constructionNumberGenerator.calculateParameters(typeGb, typeId, mngt, year);
            
            // 공통 매퍼 메소드 호출
            Integer maxSeq = constructionSeqMapper.getMaxConstructionSeq(paramMap);
            
            // 다음 SEQ 계산 (최대값 + 1, 없으면 1부터 시작)
            int nextSeq = (maxSeq != null) ? maxSeq + 1 : 1;
            
            log.info("공사번호 SEQ 조회 완료: typeId={}, 공사기관={}, 년도={}, 최대SEQ={}, 다음SEQ={}", 
                    typeId, mngt, year, maxSeq, nextSeq);
            
            return String.valueOf(nextSeq);

        } catch (RuntimeException e) {
            log.error("공사번호 SEQ 조회 중 오류: typeId={}, 공사기관={}, 년도={}", typeId, mngt, year, e);
            // 오류 발생 시 기본값 1 반환
            return "1";
        }
    }
    
    // abc_def_gh와 같이 언더바가 붙어서 오지만, 카멜케이스(abcDefGh)로 가공해야 함
    // 언더바를 카멜케이스로 변환
    protected String strToCamelKey(String paramName)
    {
        if (paramName.contains("_")) {
            StringBuilder camelName = new StringBuilder();
            String[] parts = paramName.split("_");
            camelName.append(parts[0]);
            for (int i = 1; i < parts.length; i++) {
                if (parts[i].length() > 0) {
                    camelName.append(parts[i].substring(0, 1).toUpperCase());
                    if (parts[i].length() > 1) {
                        camelName.append(parts[i].substring(1));
                    }
                }
            }
            return camelName.toString();
        }
        return paramName;
    }
    
    // typeId xyz:abc_def_gh와 같이 프리픽스와 언더바가 붙어서 오지만, 카멜케이스(xyz:AbcDefGh)로 가공해야 함
    protected String strTypeIdToCamel(String strTypeId)
    {
        final String [] rawTypeId = StringUtils.split(strTypeId, ":");
        if(rawTypeId.length > 1)
        {
            //2025-10-16 seyoung __attach(첨부파일) 테이블도 처리 가능하도록 수정
            return rawTypeId[0] + ":"
             + Arrays.stream(rawTypeId[1].split("_+")) // __가 여럿이 경우도 split 처리
               .filter(s -> !s.isEmpty()) // 빈 문자열 제거
               .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1))
             .reduce("", String::concat);
        }else {
            return Arrays.stream(rawTypeId[0].split("_+")) // __가 여럿이 경우도 split 처리
            .filter(s -> !s.isEmpty()) // 빈 문자열 제거
            .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1))
            .reduce("", String::concat);
    	}
    }
    
    // String typeId = "cmtCnstD"(카멜타입)를 "cmt_cnst_d" 로 변환
    protected String camelToSnake(String camelCase) {
        return camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
    }
    
    protected 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(Exception e) {
            log.error("공통 필드 세팅 실패 - {}", e);
            return;
        }
    }

    protected 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;
    }
    
}



