package incheon.sgp.rst.util;


import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

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

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class RstConfigUtils {

    private final ResourceLoader resourceLoader;

    // 스프링 의존성 주입용 생성자
    @Autowired
    public RstConfigUtils(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /**
     * yml 설정에서 geometry 타입의 필드명을 찾는 메서드
     * @param ymlData 파싱된 yml 데이터
     * @return geometry 타입 필드명, 없으면 null
     */
    public String findGeometryFieldName(Map<String, Object> ymlData) {
        try {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> fieldList = (List<Map<String, Object>>) ymlData.get("field-list");

            if (fieldList != null) {
                for (Map<String, Object> field : fieldList) {
                    String type = (String) field.get("type");
                    if ("geometry".equals(type)) {
                        return (String) field.get("id");
                    }
                }
            }
        } catch (Exception e) {
            log.debug("Geometry 필드 검색 중 오류: {}", e.getMessage());
        }
        return null;
    }

    /**
     * yml 설정에서 특정 type의 필드명을 찾는 메서드
     * @param ymlData 파싱된 yml 데이터
     * @param fieldType 찾을 필드 타입
     * @return 해당 타입의 필드명, 없으면 null
     */
    public String findFieldNameByType(Map<String, Object> ymlData, String fieldType) {
        try {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> fieldList = (List<Map<String, Object>>) ymlData.get("field-list");

            if (fieldList != null) {
                for (Map<String, Object> field : fieldList) {
                    String type = (String) field.get("type");
                    if (fieldType.equals(type)) {
                        return (String) field.get("id");
                    }
                }
            }
        } catch (Exception e) {
            log.debug("{} 타입 필드 검색 중 오류: {}", fieldType, e.getMessage());
        }
        return null;
    }
    
    /**
     * yml 파일에서 대장검색/시설물검색 정보를 파싱하는 메서드
     * @return Parshing 정보 (모든 대장/시설물 종류와 검색필드 포함)
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> parseSearchYmlData(String fileName) throws Exception {
        
        // YAML 파일 읽기
        InputStream inputStream = resourceLoader.getResource("classpath:uisMap/" + fileName + ".yml").getInputStream();
        
        // YAML 파서 설정
        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        
        // YAML을 Map으로 파싱
        Map<String, Object> yamlData = mapper.readValue(inputStream, Map.class);
        /**
         * 배열에서 모든 필드 정보 추출
         * 예시 :
         *  {
                "MNGT": {
                    "name": "공사기관",
                    "type": "combo",
                    "options": [...],
                    "code": "278"
                },
                "IDN": {
                    "name": "공사번호",
                    "type": "txt"
                },
                "SYMD": {
                    "name": "계약상년월일",
                    "type": "date"
                }
                // ...
            }
         */
        
        // 필드 정보 추출 및 매핑 정보 생성
        Map<String, Object> allFieldInfo = this.getFieldInfo((List<Map<String, Object>>) yamlData.get("field-list"));
        
        // 배열에서 모든 속성 정보 추출
        List<Map<String, Object>> list = (List<Map<String, Object>>) yamlData.get(fileName);
        
        Map<String, Object> data = new HashMap<>();
        List<Map<String, Object>> types = new ArrayList<>();
        
        // 각 대장/시설물 종류별로 정보 파싱
        for (Map<String, Object> targetMap : list) {
            Map<String, Object> info = new HashMap<>();
            
            // 기본 정보
            info.put("id", targetMap.get("id"));
            info.put("name", targetMap.get("name"));
            info.put("description", targetMap.get("description"));
            
            // 탭 정보 파싱
            List<Map<String, Object>> tabs = (List<Map<String, Object>>) targetMap.get("tabs");
            List<Map<String, Object>> parsedTabs = new ArrayList<>();
            
            for (Map<String, Object> tab : tabs) {
                Map<String, Object> tabInfo = new HashMap<>();
                tabInfo.put("id", tab.get("id"));
                tabInfo.put("tab-name", tab.get("tab-name"));
                
                // tab 내 배치된 필드리스트가 우선
                Map<String, Object> fieldInfo = this.getFieldInfo((List<Map<String, Object>>) tab.get("field-list"));
            	
                List<Map<String, Object>> searchFields = (List<Map<String, Object>>) tab.get("search-fields");
                if(searchFields == null || searchFields.isEmpty()) break;
                List<Map<String, Object>> parsedSearchFields = searchFields.stream()
                    .map(field -> {
                        String fieldId = (String) field.get("id");
                        Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
                        if (baseInfo == null) {
                        	log.debug("[" + info + "] search-fields not match to field-list : " + fieldId);
                        	return null;
                        }
                        
                        Map<String, Object> searchField = new HashMap<>();
                        searchField.put("id", fieldId);
                        
                        //name만 오버라이드하는 경우
                        String overrideName = (String) field.get("name");
                        
                        if (overrideName != null && !overrideName.isEmpty()) {
                            searchField.put("name", overrideName);
                        } else {
                            searchField.put("name", baseInfo.get("name"));
                        }
                        
                        // type 처리: combo인 경우와 아닌 경우
                        String baseType = (String) baseInfo.get("type");
                        String conditions = (String) field.get("conditions");
                        if ("combo".equals(baseType)) {
                            searchField.put("type", "combo");
                            if (baseInfo.get("options") != null) {
                                searchField.put("options", baseInfo.get("options"));
                                searchField.put("code", baseInfo.get("code"));
                            }
                        } else {
                            searchField.put("type", baseType + "-" + conditions);
                        }

                        // attributes 정보 복사(required 등)
                        List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
                        if (attributes != null) {
                            searchField.put("attributes", attributes);
                        }

                        return searchField;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
                
                // 검색결과 필드 파싱
                List<Map<String, Object>> searchResult = (List<Map<String, Object>>) tab.get("search-result");
                List<Map<String, Object>> parsedSearchResult = searchResult.stream()
                    .map(field -> {
                        String fieldId = (String) field.get("id");
                        Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
                        if (baseInfo == null) {
                        	log.debug("[" + info + "] search-result not match to field-list : " + fieldId);
                        	return null;
                        }

                        Map<String, Object> resultField = new HashMap<>();
                        resultField.put("id", fieldId);
                        
                        //name만 오버라이드하는 경우
                        String overrideName = (String) field.get("name");
                        
                        if (overrideName != null && !overrideName.isEmpty()) {
                            resultField.put("name", overrideName);
                        } else {
                            resultField.put("name", baseInfo.get("name"));
                        }

                        //minWidth(검색결과 테이블 스타일 설정 (ex. "300px"))
                        String minWidth = (String) field.get("minWidth");
                        if (minWidth != null && !minWidth.isEmpty()) {
                            resultField.put("minWidth", minWidth);
                        }

                        resultField.put("type", baseInfo.get("type"));  // 원본 type 그대로 사용
                        // combo의 경우 code(list_id)도 함께 전달
                        if (baseInfo.get("code") != null) {
                            resultField.put("code", baseInfo.get("code"));
                        }

                        // attributes 정보 복사 (hidden 등)
                        List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
                        if (attributes != null) {
                            resultField.put("attributes", attributes);
                        }

                        return resultField;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
                
                if(parsedSearchFields.isEmpty() || parsedSearchResult.isEmpty()) {
                	log.debug("parsedSearchFields.isEmpty() || parsedSearchResult.isEmpty() : " + tab.get("id"));
                	continue;
                }
                tabInfo.put("search-fields", parsedSearchFields);
                tabInfo.put("search-result", parsedSearchResult);
                
                parsedTabs.add(tabInfo);
            }
            
            info.put("tabs", parsedTabs);
            types.add(info);
        }
        
        data.put("types", types);
        
        return data;
    }
    
    /**
     * yml 파일에서 대장검색/시설물검색 정보를 파싱하는 메서드
     * @return Parshing 정보 (모든 대장/시설물 종류와 검색필드 포함)
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> parseDetailYmlData(String typeId) throws Exception {
    	
    	String[] ymlFileNames = { "construction", "facilities" };
    	Map<String, Object> data = new HashMap<>();
        List<Map<String, Object>> types = new ArrayList<>();
        
        if(StringUtils.isBlank(typeId)) {
        	log.error("[UisConfigUtil] parseDetailYmlData Parameter typeId is null!");
        	return data;
        }
    	
    	for(String fileName : ymlFileNames)
    	{
	        // YAML 파일 읽기
	        InputStream inputStream = resourceLoader.getResource("classpath:uisMap/" + fileName + ".yml").getInputStream();
	        
	        // 시설물은 프리픽스 제외
	        if("facilities".equals(fileName)) {
	        	typeId = typeId != null && typeId.contains(":") 
	        		    ? typeId.substring(typeId.indexOf(':') + 1) 
	        		    : typeId;
	        }
	        
	        // YAML 파서 설정
	        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
	        
	        // YAML을 Map으로 파싱
	        Map<String, Object> yamlData = mapper.readValue(inputStream, Map.class);
	        /**
	         * 배열에서 모든 필드 정보 추출
	         * 예시 :
	         *  {
	                "MNGT": {
	                    "name": "공사기관",
	                    "type": "combo",
	                    "options": [...],
	                    "code": "278"
	                },
	                "IDN": {
	                    "name": "공사번호",
	                    "type": "txt"
	                },
	                "SYMD": {
	                    "name": "계약상년월일",
	                    "type": "date"
	                }
	                // ...
	            }
	         */
	        
	        // 필드 정보 추출 및 매핑 정보 생성
	        Map<String, Object> allFieldInfo = this.getFieldInfo((List<Map<String, Object>>) yamlData.get("field-list"));
	
	        // 배열에서 모든 속성 정보 추출
	        List<Map<String, Object>> list = (List<Map<String, Object>>) yamlData.get(fileName);
	        
	        final String filterTypeId = typeId;
	        Optional <Map<String, Object>> targetMap = list.stream()
	        	    .filter(yml -> filterTypeId.equals(yml.get("id")))
	        	    .findFirst();
	        
	        if (targetMap.isPresent()) {
		        // 각 대장/시설물 종류별로 정보 파싱
	            Map<String, Object> info = new HashMap<>();
	            
	            Map<String, Object> typeMap = targetMap.get();
	            
	            // 기본 정보
	            info.put("id", typeMap.get("id"));
	            info.put("name", typeMap.get("name"));
	            info.put("description", typeMap.get("description"));
	            
	            // 탭 정보 파싱
	            List<Map<String, Object>> tabs = (List<Map<String, Object>>) typeMap.get("tabs");
	            List<Map<String, Object>> parsedTabs = new ArrayList<>();
	            
	            for (Map<String, Object> tab : tabs) {
	                Map<String, Object> tabInfo = new HashMap<>();
	                tabInfo.put("id", tab.get("id"));
	                tabInfo.put("tab-name", tab.get("tab-name"));
	                
	                // tab 내 배치된 필드리스트가 우선
	                Map<String, Object> fieldInfo = this.getFieldInfo((List<Map<String, Object>>) tab.get("field-list"));

	                // 상세정보 필드 파싱 (search-detail)
	                List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
	                if (searchDetail != null) {
	                    List<Map<String, Object>> parsedSearchDetail = searchDetail.stream()
	                        .map(field -> {
	                            String fieldId = (String) field.get("id");
	                            Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
		                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
	                            
	                            Map<String, Object> detailField = new HashMap<>();
	                            detailField.put("id", fieldId);
	                            
	                            // baseInfo가 있으면 name과 type 설정, 없으면 fieldId를 name으로 사용
	                            if (baseInfo != null) {
	                                
	                                //name만 오버라이드할 경우
	                                String overrideName = (String) field.get("name");

	                                if (overrideName != null && !overrideName.isEmpty()) {
	                                    detailField.put("name", overrideName);
	                                } else {
	                                    detailField.put("name", baseInfo.get("name"));
	                                }
	                                
	                                detailField.put("type", baseInfo.get("type"));
	                                // combo의 경우 code(list_id)도 함께 전달
	                                if (baseInfo.get("code") != null) {
	                                    detailField.put("code", baseInfo.get("code"));
	                                }
	                            } else {
	                                detailField.put("name", fieldId);
	                                detailField.put("type", "txt");
	                            }
	
	                            // search-detail에 직접 정의된 type이 있으면 덮어쓴다.
	                            if (field.get("type") != null) {
	                                detailField.put("type", field.get("type"));
	                            }
	
	                            // default 가 있는 경우 함께 전달
	                            if (field.get("default") != null) {
	                                detailField.put("default", field.get("default"));
	                            }
	                            
	                            // attributes 정보 그대로 복사
	                            List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
	                            if (attributes != null) {
	                                detailField.put("attributes", attributes);
	                            }
	                            
	                            return detailField;
	                        })
	                        .collect(Collectors.toList());
	                    tabInfo.put("search-detail", parsedSearchDetail);
	                } else {
	                    // search-detail이 없으면 빈 배열로 설정
	                    tabInfo.put("search-detail", new ArrayList<>());
	                }
	
	                // 상세 하위 탭(detail-tab) 파싱
	                List<Map<String, Object>> detailTabs = (List<Map<String, Object>>) tab.get("detail-tab");
	                if (detailTabs != null) {
	                    List<Map<String, Object>> parsedDetailTabs = detailTabs.stream()
	                        .map(dtab -> {
	                            Map<String, Object> dti = new HashMap<>();
	                            dti.put("id", dtab.get("id"));
	                            dti.put("name", dtab.get("name"));
	
	                            // 탭 검색 키(search-fields) 파싱: id 및 param 매핑 보존
	                            List<Map<String, Object>> dtSearchFields = (List<Map<String, Object>>) dtab.get("search-fields");
	                            if (dtSearchFields != null) {
	                                List<Map<String, Object>> parsedDtSearchFields = dtSearchFields.stream()
	                                    .map(sf -> {
	                                        Map<String, Object> f = new HashMap<>();
	                                        f.put("id", sf.get("id"));
	                                        if (sf.get("param") != null) f.put("param", sf.get("param"));
	                                        return f;
	                                    })
	                                    .collect(Collectors.toList());
	                                dti.put("search-fields", parsedDtSearchFields);
	                            } else {
	                                dti.put("search-fields", new ArrayList<>());
	                            }
	
	                            // 탭 리스트 필드(list-fields) 파싱: id, type 및 attributes 보존
	                            List<Map<String, Object>> dtListFields = (List<Map<String, Object>>) dtab.get("list-fields");
	                            if (dtListFields != null) {
	                                List<Map<String, Object>> parsedDtListFields = dtListFields.stream()
	                                    .map(lf -> {
	                                        String fieldId = (String) lf.get("id");
	                                        Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
	            	                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
	            	                        
	                                        Map<String, Object> out = new HashMap<>();
	                                        out.put("id", fieldId);
	                            if (baseInfo != null && baseInfo.get("name") != null) out.put("name", baseInfo.get("name"));
	                                        if (baseInfo != null && baseInfo.get("type") != null) out.put("type", baseInfo.get("type"));
	                            if (baseInfo != null && baseInfo.get("code") != null) out.put("code", baseInfo.get("code"));
	                                        List<Map<String, Object>> attrs = (List<Map<String, Object>>) lf.get("attributes");
	                                        if (attrs != null) out.put("attributes", attrs);
	                                        return out;
	                                    })
	                                    .collect(Collectors.toList());
	                                dti.put("list-fields", parsedDtListFields);
	                            } else {
	                                dti.put("list-fields", new ArrayList<>());
	                            }
	
	                            return dti;
	                        })
	                        .collect(Collectors.toList());
	                    tabInfo.put("detail-tab", parsedDetailTabs);
	                } else {
	                    tabInfo.put("detail-tab", new ArrayList<>());
	                }
		            parsedTabs.add(tabInfo);
	            }
	            
	            info.put("tabs", parsedTabs);
	            types.add(info);
		        
	        }

	        data.put("types", types);
    	}
        
        return data;
    }
    
    /**
     * typeId에 대한 typeGb를 확인하여 리턴
     * @param typeId
     * @return String typeGb
     */
    public String getTypeGbByTypeId(String typeId) {

		String[] ymlFileNames = { "construction", "facilities" };

	    if(StringUtils.isBlank(typeId)) {
	    	log.error("[UisConfigUtil] getTypeGbByTypeId Parameter typeId is null!");
	    	return ymlFileNames[0];
	    }

		for(String fileName : ymlFileNames)
		{
	        // YAML 파일 읽기 및 파싱
	        try {
				InputStream inputStream = resourceLoader.getResource("classpath:uisMap/" + fileName + ".yml").getInputStream();
		        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
		        Map<String, Object> yamlData = mapper.readValue(inputStream, Map.class);

		        // 시설물은 프리픽스 제외
		        String searchTypeId = typeId;
		        if("facilities".equals(fileName)) {
		        	searchTypeId = typeId != null && typeId.contains(":")
		        		    ? typeId.substring(typeId.indexOf(':') + 1)
		        		    : typeId;
		        }

		        // 해당 파일의 배열에서 typeId 검색
		        List<Map<String, Object>> list = (List<Map<String, Object>>) yamlData.get(fileName);
		        if (list != null) {
			        for (Map<String, Object> item : list) {
			            String itemId = (String) item.get("id");
			            if (searchTypeId != null && searchTypeId.equals(itemId)) {
							log.debug("[UisConfigUtil] found id - {} in {}.yml file.!", new Object[] {fileName, typeId});
							log.debug("[UisConfigUtil] typeGb = {} in", new Object[] {fileName});
			                return fileName;
			            }
			        }
		        }

			} catch (IOException e) {
				log.error("[UisConfigUtil] yml file read err!", e);
				continue;
			}
		}

		// 해당하는 타입을 찾지 못한 경우 기본값 반환
		log.debug("[UisConfigUtil] not found id - {} in yml file.!", new Object[] {typeId});
		return ymlFileNames[0];
	}
    
    private Map<String, Object> getFieldInfo (List<Map<String, Object>> fieldList)
    {
    	if (fieldList == null || fieldList.isEmpty()) return new HashMap<>();
    	// 필드 정보 추출 및 매핑 정보 생성
        return fieldList.stream()
            .collect(Collectors.toMap(
                field -> (String) field.get("id"),
                field -> {
                    Map<String, Object> info = new HashMap<>();
                    info.put("name", field.get("name"));
                    info.put("type", field.get("type"));
                    if (field.get("options") != null) {
                        info.put("options", field.get("options"));
                        // options가 List<Map> 형태일 경우 code 값 추출
                        List<Map<String, Object>> options = (List<Map<String, Object>>) field.get("options");
                        if (options != null && !options.isEmpty()) {
                            info.put("code", options.get(0).get("code"));
                        }
                    }
                    return info;
                },
                // 중복된 id가 있을 경우 나중에 나온 값으로 덮어쓰기
                (existing, replacement) -> replacement
            ));
    }
    
}