package incheon.uis.ums.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import incheon.uis.ums.util.UisConfigUtils;

/**
 * 공사번호 자동채번 유틸리티
 * YAML 설정을 기반으로 프리픽스와 포맷을 적용하여 공사번호 생성
 */
@Slf4j
@Component
public class UisConstructionNumberGenerator {
    
    @Autowired
    private final ResourceLoader resourceLoader;

    @Autowired
    private UisConfigUtils uisConfigUtils;
    // typeGb별로 설정 정보를 캐싱하기 위한 맵
    private final Map<String, Map<String, Object>> configCache = new ConcurrentHashMap<>();

    public UisConstructionNumberGenerator(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /**
     * YAML 설정 로드 (캐싱 적용)
     */
    private Map<String, Object> loadConfig(String typeGb) {
        // typeGb가 null이거나 비어있으면 기본값으로 'constructions' 사용
        String effectiveTypeGb = (typeGb == null || typeGb.trim().isEmpty()) ? "constructions" : typeGb;

        // 캐시에서 먼저 찾고, 없으면 파일을 로드하여 캐시에 저장
        return configCache.computeIfAbsent(effectiveTypeGb, key -> {
            log.info("Loading YML config for typeGb: {}", key);
            try {
                Resource resource = resourceLoader.getResource("classpath:uisMap/" + key + ".yml");
                if (!resource.exists()) {
                    log.error("YML 설정 파일을 찾을 수 없습니다: classpath:uisMap/{}.yml", key);
                    throw new RuntimeException("설정 파일을 찾을 수 없습니다: " + key + ".yml");
                }
                try (InputStream inputStream = resource.getInputStream()) {
                    Yaml yaml = new Yaml();
                    return yaml.load(inputStream);
                }
            } catch (IOException e) {
                log.error("YML 설정 파일 로드 중 오류 발생: {}", key, e);
                throw new RuntimeException("설정 파일 로드 중 오류 발생: " + key + ".yml", e);
            }
        });
    }
    
    /**
     * 테이블별 공사번호 채번 규칙 조회
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> getNumberingRule(String typeGb, String typeId) {
        String effectiveTypeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        Map<String, Object> config = loadConfig(effectiveTypeGb);
        List<Map<String, Object>> constructions = (List<Map<String, Object>>) config.get(effectiveTypeGb);

        if (constructions == null) {
            log.warn("YML 파일에 루트 키가 없거나 설정이 비어있습니다: {}", effectiveTypeGb);
            return null;
        }
        
        // 해당 typeId의 construction 항목 찾기
        for (Map<String, Object> construction : constructions) {
            String id = (String) construction.get("id");
            if (id.endsWith(typeId)) {
                // search-detail에서 idn 필드의 rules 찾기
                List<Map<String, Object>> tabs = (List<Map<String, Object>>) construction.get("tabs");
                for (Map<String, Object> tab : tabs) {
                    List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
                    for (Map<String, Object> field : searchDetail) {
                        String fieldId = (String) field.get("id");
                        if ("idn".equals(fieldId)) {
                            List<Map<String, Object>> rules = (List<Map<String, Object>>) field.get("rules");
                            if (rules != null) {
                                for (Map<String, Object> rule : rules) {
                                    String ruleId = (String) rule.get("id");
                                    if ("keygen".equals(ruleId)) {
                                        return rule;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
    
    /**
     * (대장)공사번호 & (시설물)개별번호 생성에 필요한 파라미터 계산
     */
    public Map<String, Object> calculateParameters(String typeGb, String typeId, String mngt, int year) {
    	typeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        Map<String, Object> rule = getNumberingRule(typeGb, typeId);
        if (rule == null) {
            throw new IllegalArgumentException("지원하지 않는 테이블 ID: " + typeId);
        }

        String prefix = (String) rule.get("prefix");
        String format = (String) rule.get("format");

        // typeId에서 테이블명 자동 생성 (예: CmtCnstD -> icgis.cmt_cnst_d)
        String[] splitTypeId = StringUtils.split(typeId, ":");
		typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
        String tableName = generateTableName(typeId);

        // 기본값은 null로 두어 MyBatis에서 조건문으로 처리하도록 함
        Integer seqStartPos = null;
        Integer yearStartPos = null;
        Integer expectedLength = null;
        String searchPattern = null;

        // format에 따라 대장(년+SEQ)인지 시설물(SEQ만)인지 분기
        if (format != null && format.contains("{year}")) {
            // 대장 검색 및 이전대장 (년도를 포함하는 포맷)
            if (prefix != null && !prefix.isEmpty()) {
                // 프리픽스가 있는 경우 (예: RD20250001)
                seqStartPos = prefix.length() + 5; // 프리픽스 + 년도(4) + 1
                yearStartPos = prefix.length() + 1; // 프리픽스 + 1
                expectedLength = prefix.length() + 8; // 프리픽스 + 년도(4) + SEQ(4)
                searchPattern = prefix + String.valueOf(year) + "%";
            } else {
                // 프리픽스가 없는 경우 (예: 20250001)
                seqStartPos = 5; // 년도(4) + 1
                yearStartPos = 1; // 시작 위치
                expectedLength = 8; // 년도(4) + SEQ(4)
                searchPattern = String.valueOf(year) + "%";
            }
            // 대장은 mngt 별로 시퀀스 채번하므로 mngt 파라미터 유지
        } else {
            // 시설물 검색: {seq:d} 와 같이 SEQ만 존재
            // 구분없이 정수로 채번되므로 mngt로 필터링하지 않음
            seqStartPos = null; // 전체 문자열에서 SEQ 시작 (테이블 설계에 따라 조정 가능)
            yearStartPos = null; // 년도 체크 불필요
            expectedLength = null; // 길이 제한하지 않음
            searchPattern = null;
            mngt = null; // mapper의 mngt 조건이 동작하지 않도록 null로 설정
        }

        Map<String, Object> params = new java.util.HashMap<>();
        params.put("tableName", tableName);
        params.put("mngt", mngt);
        params.put("yearPrefix", String.valueOf(year));
        params.put("seqStartPos", seqStartPos);
        params.put("yearStartPos", yearStartPos);
        params.put("expectedLength", expectedLength);
        params.put("searchPattern", searchPattern);

        return params;
    }
    
    /**
     * 필드별 기본값 조회
     */
    @SuppressWarnings("unchecked")
    public String getFieldDefaultValue(String typeGb, String typeId, String fieldId) {
        String effectiveTypeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        Map<String, Object> config = loadConfig(effectiveTypeGb);
        List<Map<String, Object>> constructions = (List<Map<String, Object>>) config.get(effectiveTypeGb);

        if (constructions == null) {
            log.warn("YML 파일에 루트 키가 없거나 설정이 비어있습니다: {}", effectiveTypeGb);
            return null;
        }
        
        for (Map<String, Object> construction : constructions) {
            String id = (String) construction.get("id");
            if (typeId.equals(id)) {
                List<Map<String, Object>> tabs = (List<Map<String, Object>>) construction.get("tabs");
                for (Map<String, Object> tab : tabs) {
                    List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
                    for (Map<String, Object> field : searchDetail) {
                        String fieldIdInConfig = (String) field.get("id");
                        if (fieldId.equals(fieldIdInConfig)) {
                            // 기본값 규칙 확인
                            List<Map<String, Object>> rules = (List<Map<String, Object>>) field.get("rules");
                            if (rules != null) {
                                for (Map<String, Object> rule : rules) {
                                    String ruleId = (String) rule.get("id");
                                    if ("default".equals(ruleId)) {
                                        return (String) rule.get("value");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 필드가 자동생성 대상인지 확인
     */
    @SuppressWarnings("unchecked")
    public boolean isFieldAutoGenerated(String typeGb, String typeId, String fieldId) {
        String effectiveTypeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        Map<String, Object> config = loadConfig(effectiveTypeGb);
        List<Map<String, Object>> constructions = (List<Map<String, Object>>) config.get(effectiveTypeGb);

        if (constructions == null) {
            log.warn("YML 파일에 루트 키가 없거나 설정이 비어있습니다: {}", effectiveTypeGb);
            return false;
        }
        
        for (Map<String, Object> construction : constructions) {
            String id = (String) construction.get("id");
            if (typeId.equals(id)) {
                List<Map<String, Object>> tabs = (List<Map<String, Object>>) construction.get("tabs");
                for (Map<String, Object> tab : tabs) {
                    List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
                    for (Map<String, Object> field : searchDetail) {
                        String fieldIdInConfig = (String) field.get("id");
                        if (fieldId.equals(fieldIdInConfig)) {
                            List<Map<String, Object>> rules = (List<Map<String, Object>>) field.get("rules");
                            if (rules != null) {
                                for (Map<String, Object> rule : rules) {
                                    String ruleId = (String) rule.get("id");
                                    if ("keygen".equals(ruleId)) {
                                        return true;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * 필드가 기본값 설정 대상인지 확인
     */
    @SuppressWarnings("unchecked")
    public boolean isFieldDefaultValueTarget(String typeGb, String typeId, String fieldId) {
        String effectiveTypeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        Map<String, Object> config = loadConfig(effectiveTypeGb);
        List<Map<String, Object>> constructions = (List<Map<String, Object>>) config.get(effectiveTypeGb);

        if (constructions == null) {
            log.warn("YML 파일에 루트 키가 없거나 설정이 비어있습니다: {}", effectiveTypeGb);
            return false;
        }
        
        for (Map<String, Object> construction : constructions) {
            String id = (String) construction.get("id");
            if (typeId.equals(id)) {
                List<Map<String, Object>> tabs = (List<Map<String, Object>>) construction.get("tabs");
                for (Map<String, Object> tab : tabs) {
                    List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
                    for (Map<String, Object> field : searchDetail) {
                        String fieldIdInConfig = (String) field.get("id");
                        if (fieldId.equals(fieldIdInConfig)) {
                            // 기본값 규칙이 있거나 공통 기본값 대상인 경우
                            List<Map<String, Object>> rules = (List<Map<String, Object>>) field.get("rules");
                            if (rules != null) {
                                for (Map<String, Object> rule : rules) {
                                    String ruleId = (String) rule.get("id");
                                    if ("default".equals(ruleId)) {
                                        return true;
                                    }
                                }
                            }
                            // 공통 기본값 대상 필드들
                            return "mngt".equals(fieldId);
                        }
                    }
                }
            }
        }
        return false;
    }
    
    /**
     * typeId에서 테이블명 자동 생성
     * 예: CmtCnstD -> icuis.cmt_cnst_d
     * 예: RdtCnstD -> icuis.rdt_cnst_d
     */
    @Deprecated
    private String generateTableName(String typeId) {
        // 첫 번째 대문자 이후의 모든 대문자를 소문자로 변환하고 앞에 언더스코어 추가
        StringBuilder tableName = new StringBuilder();
        tableName.append("icuis.");
        
        String[] splitTypeId = StringUtils.split(typeId, ":");
		typeId = Arrays.stream(splitTypeId).skip(1).findFirst().orElse(typeId);
        for (int i = 0; i < typeId.length(); i++) {
            char c = typeId.charAt(i);
            if (Character.isUpperCase(c) && i > 0) {
                tableName.append("_");
            }
            tableName.append(Character.toLowerCase(c));
        }
        
        return tableName.toString();
    }
    
    /**
     * 공사번호/개별번호 포맷팅
     */
    public String formatConstructionNumber(String prefix, String format, int year, int seq) {
        // format 예: "{year}{seq:d}" 또는 "{year}{seq:04d}" 등에서 seq의 포맷(d, 04d 등)을 추출하여 적용
        String seqFormat = "d"; // 기본값
        if (format != null && !format.isEmpty()) {
            // {seq:포맷} 패턴에서 포맷 추출 (d, 04d 등 어떤 값이든)
            java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("\\{seq:([^}]+)\\}").matcher(format);
            if (matcher.find()) {
                seqFormat = matcher.group(1);
            }
        }

        // format에서 {year}, {seq:포맷}을 실제 값으로 치환
        String resultFormat = (format == null) ? "" : format;
        resultFormat = resultFormat.replaceAll("\\{year\\}", String.format("%04d", year));
        resultFormat = resultFormat.replaceAll("\\{seq:[^}]+\\}", "%"+seqFormat);

        if (prefix != null && !prefix.isEmpty()) {
            if (resultFormat.contains("%"+seqFormat)) {
                return String.format("%s%s", prefix, String.format(resultFormat, seq));
            } else {
                return String.format("%s%s", prefix, resultFormat);
            }
        } else {
            if (resultFormat.contains("%"+seqFormat)) {
                return String.format(resultFormat, seq);
            } else {
                return resultFormat;
            }
        }
    }

    /**
     * 특정 속성을 가진 필드 목록 조회
     */
    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> getDetailFieldsWithAttribute(String typeGb, String typeId, String attributeId) {
        String effectiveTypeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
    	Map<String, Object> config = loadConfig(effectiveTypeGb);
    	
    	List<Map<String, Object>> result = new ArrayList<>();
    	List<Map<String, Object>> types = (List<Map<String, Object>>) config.get(effectiveTypeGb);

        if (types == null) {
            log.warn("YML 파일에 루트 키가 없거나 설정이 비어있습니다: {}", effectiveTypeGb);
            return result;
        }
    	
    	for (Map<String, Object> type : types) {
            String id = (String) type.get("id");
            if (typeId.equals(id)) {
                List<Map<String, Object>> tabs = (List<Map<String, Object>>) type.get("tabs");
                for (Map<String, Object> tab : tabs) {
                    List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
                    if (searchDetail != null) {
                        for (Map<String, Object> field : searchDetail) {
                            List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
                            if (attributes != null) {
                                for (Map<String, Object> attr : attributes) {
                                    String attrId = (String) attr.get("id");
                                    if (attributeId.equals(attrId)) {
                                        result.add(field);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                break;
            }
        }
        return result;
    }
    
    /**
     * 필수 필드 목록 조회
     */
    public List<String> getRequiredFields(String typeGb, String typeId) {
    	typeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        List<Map<String, Object>> requiredFields = getDetailFieldsWithAttribute(typeGb, typeId, "required");
        List<String> result = new ArrayList<>();
        
        for (Map<String, Object> field : requiredFields) {
            String fieldId = (String) field.get("id");
            if (fieldId != null) {
                result.add(fieldId);
            }
        }
        
        return result;
    }
    
    /**
     * disabled 필드 목록 조회
     */
    public List<String> getDisabledFields(String typeGb, String typeId) {
    	typeGb = (typeGb == null || typeGb.trim().isEmpty()) ? uisConfigUtils.getTypeGbByTypeId(typeId) : typeGb;
        List<Map<String, Object>> disabledFields = getDetailFieldsWithAttribute(typeGb, typeId, "disabled");
        List<String> result = new ArrayList<>();
        
        for (Map<String, Object> field : disabledFields) {
            String fieldId = (String) field.get("id");
            if (fieldId != null) {
                result.add(fieldId);
            }
        }
        
        return result;
    }
} 