package com.arms.api.requirement.service;

import com.arms.api.analysis.time.dto.TimeAggrDTO;
import com.arms.api.issue.almapi.model.entity.AlmIssueEntity;
import com.arms.api.requirement.model.dto.FilteredResults;
import com.arms.api.requirement.model.dto.RequirementAggrDTO;
import com.arms.api.requirement.model.dto.RequirementDTO;
import com.arms.api.requirement.model.dto.SubtaskAndLinkedIssuesRequestDTO;
import com.arms.api.requirement.model.vo.HierarchicalAlmIssue;
import com.arms.api.requirement.model.vo.ReqProgressVO;
import com.arms.api.requirement.model.vo.RequirementVO;
import com.arms.api.util.model.dto.PdServiceAndIsReqDTO;
import com.arms.egovframework.javaservice.esframework.esquery.filter.RangeQueryFilter;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.SearchDocDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.SortDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.request.AggregationRequestDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.SubGroupFieldDTO;
import com.arms.egovframework.javaservice.esframework.model.vo.DocumentAggregations;
import com.arms.egovframework.javaservice.esframework.model.vo.DocumentBucket;

import com.arms.egovframework.javaservice.esframework.repository.common.EsCommonRepositoryWrapper;
import com.arms.egovframework.javaservice.esframework.esquery.SimpleQuery;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.arms.egovframework.javaservice.esframework.esquery.SimpleQuery.*;
import static java.util.stream.Collectors.toList;

@Slf4j
@Service("요구사항_서비스")
@AllArgsConstructor
public class RequirementServiceImpl implements RequirementService {

    private final EsCommonRepositoryWrapper<AlmIssueEntity> esCommonRepositoryWrapper;

    @Override
    public List<AlmIssueEntity> 함께_생성된_요구사항_이슈목록(Long 제품서비스_아이디, List<Long> 버전_아이디들, Long 요구사항_아이디) {
        return esCommonRepositoryWrapper.findRecentHits(
                    termQueryMust("pdServiceId", 제품서비스_아이디)
                        .andTermQueryMust("isReq", true)
                        .andTermQueryMust("cReqLink", 요구사항_아이디)
                        .andTermsQueryFilter("pdServiceVersions",버전_아이디들)).toDocs();
    }

    @Override
    public List<RequirementVO> 제품_요구사항별_담당자_목록(RequirementAggrDTO requirementAggrDTO) {

        boolean 요구사항여부 = requirementAggrDTO.getIsReq();
        String rootField = 요구사항여부 ? "key" : "parentReqKey";
        String rootFieldAlias = 요구사항여부 ? "requirement" : "parentRequirement";

        DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecentDocs(
                aggregation(
                    AggregationRequestDTO.builder()
                        .mainField(rootField)
                        .mainFieldAlias(rootFieldAlias)
                        .addGroup(
                            SubGroupFieldDTO.builder()
                                    .subFieldAlias("assignees")
                                    .subField("assignee.assignee_accountId.keyword")
                                    .size(10000)
                                    .isAscending(false)
                                .build(),
                            SubGroupFieldDTO.builder()
                                    .subFieldAlias("displayNames")
                                    .subField("assignee.assignee_displayName.keyword")
                                    .size(10000)
                                .build(),
                            SubGroupFieldDTO.builder()
                                    .subFieldAlias("cReqLink")
                                    .subField("cReqLink")
                                    .size(10000)
                                .build())
                        .build()
                )
        );
        List<DocumentBucket> documentBuckets = documentAggregations.deepestList();

        List<RequirementVO> requirementVOList = new ArrayList<>();

        for (DocumentBucket documentBucket : documentBuckets) {
            String reqKey = documentBucket.valueByName(rootFieldAlias);
            String assigneeKey = documentBucket.valueByName("assignees");
            String displayNameKey = documentBucket.valueByName("displayNames");
            String reqLinkKey = documentBucket.valueByName("cReqLink");
            RequirementVO requirementVO = RequirementVO.builder()
                    .reqKey(reqKey)
                    .reqLinkKey(reqLinkKey)
                    .assigneeKey(assigneeKey)
                    .displayNameKey(displayNameKey)
                    .isReq(요구사항여부)
                    .build();
            requirementVOList.add(requirementVO);
        }

        return requirementVOList;
    }

    @Override
    public List<AlmIssueEntity> deletedIssueList(Long pdServiceLink,List<Long> pdServiceVersionLinks){

        return esCommonRepositoryWrapper.findRecentHits(
                termQueryMust("pdServiceId",pdServiceLink)
                        .andTermsQueryFilter("pdServiceVersions", pdServiceVersionLinks)
                        .andTermQueryFilter("deleted.deleted_isDeleted","true")
        ).toDocs();

    }

    @Override
    public int 이슈_삭제철회(List<AlmIssueEntity> 삭제_철회대상_목록) throws Exception {
        try {
            List<AlmIssueEntity> 철회업데이트 = Optional.ofNullable(삭제_철회대상_목록)
                    .orElse(Collections.emptyList())
                    .stream()
                    .map(지라이슈_엔티티 -> {
                        지라이슈_엔티티.getDeleted().setIsDeleted(false);
                        return 지라이슈_엔티티;
                    })
                    .collect(toList());

            List<AlmIssueEntity> 지라이슈s
                = 철회업데이트.stream()
                .map(이슈 -> esCommonRepositoryWrapper.save(이슈))
                .collect(toList());

            int count = 지라이슈s.size();

            if (count == 0) {
                log.info("이슈 삭제 철회 작업에서 업데이트된 엔티티가 없습니다.");
            } else {
                log.info("이슈 삭제 철회 작업에서 {}개의 엔티티가 업데이트되었습니다.", count);
            }

            return count;

        } catch (Exception e) {
            log.error("이슈 삭제 철회 작업 중 오류 발생", e);
            throw new Exception("이슈 삭제 철회 작업 중 오류가 발생했습니다.");
        }
    }

    // gantt 에서 사용
    @Override
    public List<ReqProgressVO> 일반_버전필터_해결책유무_검색(RequirementDTO requirementDTO, String resolution) {
        PdServiceAndIsReqDTO pdServiceAndIsReq = requirementDTO.getPdServiceAndIsReq();

        DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecentDocs(
                aggregation(
                        AggregationRequestDTO.builder()
                            .mainField("cReqLink")
                            .mainFieldAlias("cReqLink")
                        .build())
                .andTermQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                .andTermQueryMust("isReq", pdServiceAndIsReq.getIsReq())
                .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
                .andExistsQueryFilter(resolution)
        );
        List<DocumentBucket> documentBuckets = documentAggregations.deepestList();

        return buildReqProgressList(documentBuckets);
    }

    private List<ReqProgressVO> buildReqProgressList(List<DocumentBucket> documentBuckets) {
        List<ReqProgressVO> reqProgressList = new ArrayList<>();

        if (documentBuckets.isEmpty()) {
            return Collections.emptyList();
        }

        for (DocumentBucket documentBucket : documentBuckets) {
            reqProgressList.add(ReqProgressVO.builder()
                    .cReqLink(Long.valueOf(documentBucket.valueByName("cReqLink")))
                    .docCount(documentBucket.countByName("cReqLink"))
                    .build());
        }

        return  reqProgressList;
    }

    @Override
    public List<AlmIssueEntity> issueListByUpdatedDate(RequirementDTO requirementDTO) {

        List<AlmIssueEntity> allList = this.getAllListByRequirementDTO(requirementDTO);

        if (allList.isEmpty()) {
            return Collections.emptyList();
        }

        List<AlmIssueEntity> filteredList = allList.stream()
                .filter(entity -> entity.getUpdated() != null)
                .collect(Collectors.toList());

        filteredList.sort(
                Comparator.comparing(AlmIssueEntity::stringValueOfUpdatedDate,
                        Comparator.nullsLast(Comparator.naturalOrder()))
                        .reversed());
        return filteredList;
    }

    @Override
    public List<AlmIssueEntity> issueListByUpdated2ndTab(RequirementDTO requirementDTO) {

        PdServiceAndIsReqDTO pdServiceAndIsReq = requirementDTO.getPdServiceAndIsReq();
        Long pdServiceId = pdServiceAndIsReq.getPdServiceId();
        List<Long> pdServiceVersions = pdServiceAndIsReq.getPdServiceVersions();

        return esCommonRepositoryWrapper.findRecentDocsByScrollApi(
                SimpleQuery.termsQueryFilter("linkedIssuePdServiceIds", List.of(pdServiceId))
                        .andTermsQueryFilter("linkedIssuePdServiceVersions", pdServiceVersions)
                        .andExistsQueryFilter("updated")
                        .orderBy(SortDTO.builder().field("updated").sortType("desc").build())
        );
    }

    @Override
    public List<AlmIssueEntity> issueListByOverallUpdatedDate(RequirementDTO requirementDTO) {
        PdServiceAndIsReqDTO pdServiceAndIsReq = requirementDTO.getPdServiceAndIsReq();

        return esCommonRepositoryWrapper.findRecentDocsByScrollApi(
                termQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                        .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
                        .andExistsQueryFilter("overallUpdatedDate")
                        .orderBy(SortDTO.builder().field("overallUpdatedDate").sortType("desc").build())
        );
    }

    @Override
    public List<HierarchicalAlmIssue> issueListByAdvancedDiscovery(RequirementDTO requirementDTO) {

        PdServiceAndIsReqDTO pdServiceAndIsReq = requirementDTO.getPdServiceAndIsReq();

        List<AlmIssueEntity> depth1IssueList = esCommonRepositoryWrapper.findRecentDocsByScrollApi(
                termQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
                .orderBy(
                        SortDTO.builder()
                                .field("updated")
                                .sortType("desc")
                                .build()
                )
        );

        Set<String> reqIdSet = new HashSet<>();
        Set<String> notReqIdSet = new HashSet<>();
        Set<String> linkedIssueSetByIssuesHavePdServiceAndVersions = new HashSet<>();
        for (AlmIssueEntity issue : depth1IssueList) {
            if (Boolean.TRUE.equals(issue.getIsReq())) {
                reqIdSet.add(issue.getRecentId());
            } else { // isReq is null or false
                notReqIdSet.add(issue.getRecentId());
            }
            if (issue.getLinkedIssues() != null) {
                linkedIssueSetByIssuesHavePdServiceAndVersions.addAll(issue.getLinkedIssues());
            }
        }

        DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecentDocs(
                aggregation(
                        AggregationRequestDTO.builder()
                                .mainField("upperKey")
                                .mainFieldAlias("upperKey")
                        .build()
                )
                .andExistsQueryFilter("upperKey")
                .andTermQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
        );
        List<DocumentBucket> subtaskDocBuckets = documentAggregations.deepestList();
        Map<String, Long> subtaskKeyCountMap = new HashMap<>();
        for (DocumentBucket doc : subtaskDocBuckets) {
            String issueKey = doc.valueByName("upperKey");
            Long issueCount = doc.countByName("upperKey");
            subtaskKeyCountMap.put(issueKey, issueCount);
        }

        // convert depth1Issuess to HierarchicalAlmIssue (요구사항, 하위이슈 모두있음)
        List<HierarchicalAlmIssue> hierarchicalAlmIssues = hierarchicalAlmIssuesFromAlmIssueList(depth1IssueList, subtaskKeyCountMap);
        Map<String, HierarchicalAlmIssue> recentIdHIssuesMap = hierarchicalAlmIssues.stream()
                .collect(
                        Collectors.toMap(
                                hierarchicalAlmIssue -> hierarchicalAlmIssue.getAlmIssue().getRecentId(),
                                hierarchicalAlmIssue -> hierarchicalAlmIssue)
                );

        // 1차 linkedIssue 조회를 위한 처리 - 연결이슈의 recentId 목록에서 요구사항과 하위이슈 recentId와 겹치면 Set에서 제거
        List<String> linkedIssueIdList = linkedIssueSetByIssuesHavePdServiceAndVersions.stream().toList();
        for (String linkedIssue : linkedIssueIdList) {
            // 요구사항과 겹치는 경우 - 조회 대상 제외
            if(reqIdSet.contains(linkedIssue)) {
                linkedIssueSetByIssuesHavePdServiceAndVersions.remove(linkedIssue);
            // 하위이슈와 겹치는 경우 - 조회 대상 제외
            } else if(notReqIdSet.contains(linkedIssue)) {
                linkedIssueSetByIssuesHavePdServiceAndVersions.remove(linkedIssue);
            }
        }

        // 뎁스1에서 가져온 연결이슈의 recentId 목록
        List<String> linkedIssueIdListNeededSearch = linkedIssueSetByIssuesHavePdServiceAndVersions.stream().toList();

        // 뎁스1에서 도출한 연결이슈의 목록(depth1)
        List<AlmIssueEntity> linkedIssueSearchResult = esCommonRepositoryWrapper.findRecentDocsByScrollApi(
                    termsQueryFilter("recent_id", linkedIssueIdListNeededSearch)
                    .orderBy(SortDTO.builder().field("updated").sortType("desc").build())
        );

        List<String> linkedIssueKeyList = linkedIssueSearchResult.stream().map(AlmIssueEntity::getKey).toList();

        DocumentAggregations documentAggregations1 = esCommonRepositoryWrapper.aggregateRecentDocs(
                aggregation(AggregationRequestDTO.builder()
                                .mainField("upperKey")
                                .mainFieldAlias("upperKey").build())
                .andTermsQueryFilter("upperKey", linkedIssueKeyList));
        List<DocumentBucket> linkedSubtaskDocBuckets = documentAggregations1.deepestList();

        // 뎁스1의 연결이슈의 하위이슈 갯수 맵
        Map<String, Long> linkedSubtaskKeyCountMap = new HashMap<>();
        for (DocumentBucket doc : linkedSubtaskDocBuckets) {
            String issueKey = doc.valueByName("upperKey");
            Long issueCount = doc.countByName("upperKey");
            linkedSubtaskKeyCountMap.put(issueKey, issueCount);
        }

        List<HierarchicalAlmIssue> linkedHierarchicalAlmIssues = hierarchicalAlmIssuesFromAlmIssueList(linkedIssueSearchResult, linkedSubtaskKeyCountMap);
        Map<String, HierarchicalAlmIssue> recentIdLinkedHIssuesMap = linkedHierarchicalAlmIssues.stream()
                .collect(
                        Collectors.toMap(
                                hierarchicalAlmIssue -> hierarchicalAlmIssue.getAlmIssue().getRecentId(),
                                hierarchicalAlmIssue -> hierarchicalAlmIssue)
                );

        Map<String, HierarchicalAlmIssue> combinedMap =
                Stream.concat(recentIdHIssuesMap.entrySet().stream(), recentIdLinkedHIssuesMap.entrySet().stream())
                        .collect(Collectors.toMap(
                                Map.Entry::getKey,
                                Map.Entry::getValue,
                                (v1, v2) -> v1 // 충돌 시 첫 번째 값을 사용
                        ));



        List<HierarchicalAlmIssue> reqHIssueList = new ArrayList<>();
        Map<String, List<HierarchicalAlmIssue>> upperKeyHIssueListMap = new HashMap<>();
        for (HierarchicalAlmIssue hierarchicalAlmIssue : hierarchicalAlmIssues) {
            // 요구사항
            if(hierarchicalAlmIssue.getAlmIssue().getIsReq()) {
                reqHIssueList.add(hierarchicalAlmIssue);
            } else {
                upperKeyHIssueListMap.computeIfAbsent(
                        hierarchicalAlmIssue.getAlmIssue().getUpperKey(), // key
                        k -> new ArrayList<>() // value supplier
                ).add(hierarchicalAlmIssue);
            }
        }

        // 최종 반환할 List 세팅
        List<HierarchicalAlmIssue> returnList = new ArrayList<>();
        for (HierarchicalAlmIssue hierarchicalAlmIssue : reqHIssueList) {
            returnList.add(hierarchicalAlmIssue);
            // 다음 idx에 subtask 추가
            if(upperKeyHIssueListMap.containsKey(hierarchicalAlmIssue.getAlmIssue().getKey())) {
                returnList.addAll(upperKeyHIssueListMap.get(hierarchicalAlmIssue.getAlmIssue().getKey()));
            }
            // 다음 idx에 linkedList 추가
            if (hierarchicalAlmIssue.getLinkedIssueCount() > 0L) {
                List<String> recentIdsOfLinkedIssues = hierarchicalAlmIssue.getAlmIssue().getLinkedIssues();
                for (String recentId : recentIdsOfLinkedIssues) {
                    HierarchicalAlmIssue almIssue = combinedMap.get(recentId);
                    if (almIssue != null) {
                        HierarchicalAlmIssue hierarchicalAlmIssue1 = HierarchicalAlmIssue.copyObject(almIssue);
                        hierarchicalAlmIssue1.setIsReqName(hierarchicalAlmIssue.getAlmIssue().getKey()+"_link");
                        returnList.add(hierarchicalAlmIssue1);
                    } else {
                        log.warn("[RequirementServiceImpl :: issueListByAdvancedDiscovery] :: linked issue not found. recentId: {}", recentId);
                    }
                }
            }
        }

        return returnList;
    }

    @Override
    public List<HierarchicalAlmIssue> subtasksAndLinkedIssues(SubtaskAndLinkedIssuesRequestDTO subtaskAndLinkedIssuesRequestDTO) {

        String key = subtaskAndLinkedIssuesRequestDTO.getKey();
        String upperKey = subtaskAndLinkedIssuesRequestDTO.getUpperKey();
        String parentReqKey = subtaskAndLinkedIssuesRequestDTO.getParentReqKey();
        List<String> linkedissues = subtaskAndLinkedIssuesRequestDTO.getLinkedIssues();

        RequirementDTO searchDTO = new RequirementDTO();

        List<HierarchicalAlmIssue> returnList = new ArrayList<>();

        if(subtaskAndLinkedIssuesRequestDTO.getSubtaskCount() > 0) {
            // 1. 이 이슈를 upperKey 로 갖는 하위이슈 서치
            // 하위이슈 조회
            // 이 이슈의 하위이슈들
            List<AlmIssueEntity> fetchedSubtasks = esCommonRepositoryWrapper.findRecentDocsByScrollApi(
                    termQueryMust("upperKey", key)
                            .orderBy(SortDTO.builder().field("updated").sortType("desc").build())
            );

            // 하위 이슈들의 subtask 집계
            DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecentDocs(
                aggregation(AggregationRequestDTO.builder()
                                .mainField("upperKey")
                                .mainFieldAlias("upperKey")
                                .build())
                            .andTermsQueryFilter("upperKey", fetchedSubtasks.stream().map(AlmIssueEntity::getKey).toList())
            );
            List<DocumentBucket> subtaskDocBuckets = documentAggregations.deepestList();
            Map<String, Long> subtaskKeyCountMap = new HashMap<>();
            for (DocumentBucket doc : subtaskDocBuckets) {
                String issueKey = doc.valueByName("upperKey");
                Long issueCount = doc.countByName("upperKey");
                subtaskKeyCountMap.put(issueKey, issueCount);
            }

            returnList.addAll(hierarchicalAlmIssuesFromAlmIssueList(fetchedSubtasks, subtaskKeyCountMap));
        }

        if(subtaskAndLinkedIssuesRequestDTO.getLinkedIssueCount() > 0) {
            // 2. 이 이슈의 linkedIssues 의 recentId를 활용해서 서치
            // 연결이슈 조회
            // 이 이슈의 연결이슈 조회
            List<AlmIssueEntity> fetchedLinkedIssues = esCommonRepositoryWrapper.findRecentDocsByScrollApi(
                    termsQueryFilter("recent_id", linkedissues)
                            .orderBy(SortDTO.builder().field("updated").sortType("desc").build())
            );
            // 연결이슈들의 subtask 집계
            DocumentAggregations documentAggregations1 = esCommonRepositoryWrapper.aggregateRecentDocs(
                    aggregation(AggregationRequestDTO.builder().mainField("upperKey").mainFieldAlias("upperKey").build())
                            .andTermsQueryFilter("upperKey", fetchedLinkedIssues.stream().map(AlmIssueEntity::getKey).toList())
            );
            List<DocumentBucket> linkedSubtaskDocBuckets = documentAggregations1.deepestList();

            Map<String, Long> linkedSubtaskKeyCountMap = new HashMap<>();
            for (DocumentBucket doc : linkedSubtaskDocBuckets) {
                String issueKey = doc.valueByName("upperKey");
                Long issueCount = doc.countByName("upperKey");
                linkedSubtaskKeyCountMap.put(issueKey, issueCount);
            }

            List<HierarchicalAlmIssue> linkedHierarchicalAlmIssues = hierarchicalAlmIssuesFromAlmIssueList(fetchedLinkedIssues, linkedSubtaskKeyCountMap);
            for (HierarchicalAlmIssue hierarchicalAlmIssue : linkedHierarchicalAlmIssues) {
                hierarchicalAlmIssue.addIsReqName(key+"_link");
            }

            returnList.addAll(linkedHierarchicalAlmIssues);
        }

        return returnList;
    }

    private List<HierarchicalAlmIssue> hierarchicalAlmIssuesFromAlmIssueList(List<AlmIssueEntity> almIssueEntities, Map<String, Long> subtaskKeyCountMap) {
        List<HierarchicalAlmIssue> result = new ArrayList<>();

        for (AlmIssueEntity issue : almIssueEntities) {

            HierarchicalAlmIssue.HierarchicalAlmIssueBuilder hBuilder = HierarchicalAlmIssue.builder();

            if (issue.getIsReq()) {
                hBuilder.isReqName("요구사항");
            } else if (!ObjectUtils.isEmpty(issue.getUpperKey())) {
                hBuilder.isReqName(issue.getUpperKey());
            } else {
                hBuilder.isReqName("");
            }

            hBuilder.subtaskCount(subtaskKeyCountMap.getOrDefault(issue.getKey(), 0L));

            if (!ObjectUtils.isEmpty(issue.getLinkedIssues())) {
                hBuilder.linkedIssueCount((long) issue.getLinkedIssues().size());
            } else {
                hBuilder.linkedIssueCount(0L);
            }

            hBuilder.almIssue(issue);

            result.add(hBuilder.build());
        }

        return result;
    }

    @Override
    public List<AlmIssueEntity> getIssuesByRequirementDTO(SimpleQuery<SearchDocDTO> simpleQuery) {

        List<AlmIssueEntity> returnList = new ArrayList<>();
        Set<String> totalRecentIdSet = new HashSet<>();
        Set<String> linkedIssuesRecentIdSet = new HashSet<>();
        Set<String> keySet = new HashSet<>();

        List<AlmIssueEntity> depth1IssueList = esCommonRepositoryWrapper.findRecentHits(simpleQuery).toDocs();
        if (ObjectUtils.isEmpty(depth1IssueList)) {
            return Collections.emptyList();
        }

        for (AlmIssueEntity issue : depth1IssueList) {
            totalRecentIdSet.add(issue.getRecentId());
            keySet.add(issue.getKey());
            if(!ObjectUtils.isEmpty(issue.getLinkedIssues())) {
                linkedIssuesRecentIdSet.addAll(issue.getLinkedIssues());
            }
            returnList.add(issue);
        }

        linkedIssuesRecentIdSet.removeIf(totalRecentIdSet::contains);

        processLinkedIssues(totalRecentIdSet, linkedIssuesRecentIdSet, returnList);
        processSubtasks(totalRecentIdSet, keySet.stream().toList(), returnList);

        return returnList;
    }

    @Override
    public List<AlmIssueEntity> getIssuesByRequirementDTO(SimpleQuery<SearchDocDTO> simpleQuery, String field, String startDate, String endDate) {

        List<AlmIssueEntity> returnList = new ArrayList<>();
        Set<String> totalRecentIdSet = new HashSet<>();
        Set<String> linkedIssuesRecentIdSet = new HashSet<>();
        Set<String> keySet = new HashSet<>();

        // 1) 요구사항 (날짜 제한 없음)
        List<AlmIssueEntity> depth1IssueList = esCommonRepositoryWrapper.findRecentHits(simpleQuery).toDocs();
        if (ObjectUtils.isEmpty(depth1IssueList)) {
            return Collections.emptyList();
        }

        for (AlmIssueEntity issue : depth1IssueList) {
            totalRecentIdSet.add(issue.getRecentId());
            keySet.add(issue.getKey());
            if(!ObjectUtils.isEmpty(issue.getLinkedIssues())) {
                linkedIssuesRecentIdSet.addAll(issue.getLinkedIssues());
            }
            returnList.add(issue);
        }

        linkedIssuesRecentIdSet.removeIf(totalRecentIdSet::contains);

        // 2) 날짜 필터 적용
        processLinkedIssues(totalRecentIdSet, linkedIssuesRecentIdSet, returnList, field, startDate, endDate);
        processSubtasks(totalRecentIdSet, keySet.stream().toList(), returnList, field, startDate, endDate);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

        // 3) 요구사항 날짜 필터링 후 리턴
        return returnList.stream()
                .filter(e -> {
                    if (Boolean.TRUE.equals(e.getIsReq())) {
                        if (StringUtils.equals(field, "created")) {
                            Date created = e.getCreated();
                            try {
                                return created != null
                                        && ( !created.before(sdf.parse(startDate+ "T00:00:00.000+0900"))
                                        && !created.after(sdf.parse(endDate + "T23:59:59.999+0900")) );
                            } catch (ParseException ex) {
                                log.warn(ex.getMessage());
                                return false;
                            }
                        } else {
                            Date updated = e.getUpdated();
                            try {
                                return updated != null
                                        && ( !updated.before(sdf.parse(startDate+ "T00:00:00.000+0900"))
                                        && !updated.after(sdf.parse(endDate + "T23:59:59.999+0900")) );
                            } catch (ParseException ex) {
                                log.warn(ex.getMessage());
                                return false;
                            }
                        }
                    }
                    return true;
                })
                .toList();
    }

    @Override
    public List<AlmIssueEntity> getRelationIssuesByRequirementDTO(TimeAggrDTO timeAggrDTO) {

        List<AlmIssueEntity> returnList = new ArrayList<>();
        Set<String> totalRecentIdSet = new HashSet<>();
        Set<String> linkedIssuesRecentIdSet = new HashSet<>();
        Set<String> keySet = new HashSet<>();

        // 1) 요구사항 (날짜 제한 없음)
        List<AlmIssueEntity> depth1IssueList = esCommonRepositoryWrapper.findRecentHits(
                SimpleQuery.termQueryMust("pdServiceId", timeAggrDTO.getPdServiceLink())
                        .andTermsQueryFilter("pdServiceVersions", timeAggrDTO.getPdServiceVersionLinks())
                        .andTermQueryMust("isReq", true)
        ).toDocs();
        if (ObjectUtils.isEmpty(depth1IssueList)) {
            return Collections.emptyList();
        }

        for (AlmIssueEntity issue : depth1IssueList) {
            totalRecentIdSet.add(issue.getRecentId());
            keySet.add(issue.getKey());
            if (!ObjectUtils.isEmpty(issue.getLinkedIssues())) {
                linkedIssuesRecentIdSet.addAll(issue.getLinkedIssues());
            }
        }

        linkedIssuesRecentIdSet.removeIf(totalRecentIdSet::contains);

        // 2) 날짜 필터 적용

        String field = "updated";
        String startDate = timeAggrDTO.getStartDate();
        String endDate = timeAggrDTO.getEndDate();

        processLinkedIssues(totalRecentIdSet, linkedIssuesRecentIdSet, returnList, field, startDate, endDate);
        processSubtasks(totalRecentIdSet, keySet.stream().toList(), returnList, field, startDate, endDate);

        // 3) 요구사항은 제외하고 연결/하위만 반환
        return returnList.stream()
                .filter(e -> !Boolean.TRUE.equals(e.getIsReq()))
                .toList();
    }

    @Override
    public List<AlmIssueEntity> getAllListByRequirementDTO(RequirementDTO requirementDTO) {
        PdServiceAndIsReqDTO pdServiceAndIsReq = requirementDTO.getPdServiceAndIsReq();

        List<AlmIssueEntity> returnList = new ArrayList<>();
        Set<String> totalRecentIdSet = new HashSet<>();
        Set<String> linkedIssuesRecentIdSet = new HashSet<>();
        Set<String> keySet = new HashSet<>();

        List<AlmIssueEntity> depth1IssueList = esCommonRepositoryWrapper.findRecentHits(
                termQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
                .orderBy(SortDTO.builder().field("updated").sortType("desc").build())).toDocs();

        for (AlmIssueEntity issue : depth1IssueList) {
            totalRecentIdSet.add(issue.getRecentId());
            keySet.add(issue.getKey());
            if(!ObjectUtils.isEmpty(issue.getLinkedIssues())) {
                linkedIssuesRecentIdSet.addAll(issue.getLinkedIssues());
            }
            returnList.add(issue);
        }
        // 연결이슈 -  allSet에 있는 항목 제거
        linkedIssuesRecentIdSet.removeIf(totalRecentIdSet::contains);

        processLinkedIssues(totalRecentIdSet, linkedIssuesRecentIdSet, returnList);
        processSubtasks(totalRecentIdSet, keySet.stream().toList(), returnList);

        return returnList;
    }

    private List<AlmIssueEntity> getAlmIssuesByRecentIds(List<String> recentIds) {
        return esCommonRepositoryWrapper.findRecentHits(termsQueryFilter("recent_id", recentIds)).toDocs();
    }

    private List<AlmIssueEntity> getAlmIssuesByRecentIds(List<String> recentIds, String field, String startDate, String endDate) {
        return esCommonRepositoryWrapper.findHits(
                    termsQueryFilter("recent_id", recentIds)
                        .andRangeQueryFilter(RangeQueryFilter.of(field).betweenDate(startDate, endDate))
                ).toDocs();
    }

    private List<AlmIssueEntity> getSubtasksByUpperKeys(List<String> upperKeys) {
        return esCommonRepositoryWrapper.findRecentHits(termsQueryFilter("upperKey", upperKeys)).toDocs();
    }

    private List<AlmIssueEntity> getSubtasksByUpperKeys(List<String> upperKeys, String field, String startDate, String endDate) {
        return esCommonRepositoryWrapper.findHits(
                    termsQueryFilter("upperKey", upperKeys)
                        .andRangeQueryFilter(RangeQueryFilter.of(field).betweenDate(startDate, endDate))
                ).toDocs();
    }

    private void processIssues(Set<String> totalRecentIdSet, Set<String> linkedIssueRecentIdSet, List<AlmIssueEntity> fetchedIssues, List<AlmIssueEntity> returnList) {
        if (ObjectUtils.isEmpty(fetchedIssues)) return;

        List<String> issueKeyList = fetchedIssues.stream()
                .map(AlmIssueEntity::getKey)
                .collect(Collectors.toSet())
                .stream().toList();

        List<String> newlyFoundLinkedIssueIds = fetchedIssues.stream()
                .flatMap(issue -> Optional.ofNullable(issue.getLinkedIssues()).stream())
                .flatMap(Collection::stream)
                .filter(linkedId -> !totalRecentIdSet.contains(linkedId))
                .toList();

        if (!ObjectUtils.isEmpty(newlyFoundLinkedIssueIds)) {
            linkedIssueRecentIdSet.addAll(newlyFoundLinkedIssueIds);
        }

        processLinkedIssues(totalRecentIdSet, linkedIssueRecentIdSet, returnList);
        processSubtasks(totalRecentIdSet, issueKeyList, returnList);
    }

    private void processIssues(
            Set<String> totalRecentIdSet,
            Set<String> linkedIssueRecentIdSet,
            List<AlmIssueEntity> fetchedIssues,
            List<AlmIssueEntity> returnList,
            String field,
            String startDate, String endDate) {

        if (ObjectUtils.isEmpty(fetchedIssues)) return;

        List<String> issueKeyList = fetchedIssues.stream()
                .map(AlmIssueEntity::getKey)
                .collect(Collectors.toSet())
                .stream().toList();

        List<String> newlyFoundLinkedIssueIds = fetchedIssues.stream()
                .flatMap(issue -> Optional.ofNullable(issue.getLinkedIssues()).stream())
                .flatMap(Collection::stream)
                .filter(linkedId -> !totalRecentIdSet.contains(linkedId))
                .toList();

        if (!ObjectUtils.isEmpty(newlyFoundLinkedIssueIds)) {
            linkedIssueRecentIdSet.addAll(newlyFoundLinkedIssueIds);
        }

        processLinkedIssues(totalRecentIdSet, linkedIssueRecentIdSet, returnList, field, startDate, endDate);
        processSubtasks(totalRecentIdSet, issueKeyList, returnList, field, startDate, endDate);
    }

    private void processLinkedIssues(Set<String> totalRecentIdSet, Set<String> linkedIssueRecentIdSet, List<AlmIssueEntity> returnList) {
        if (ObjectUtils.isEmpty(linkedIssueRecentIdSet)) {
            return;
        }

        List<AlmIssueEntity> fetchedLinkedIssues = getAlmIssuesByRecentIds(linkedIssueRecentIdSet.stream().toList());

        FilteredResults filteredResults = processAndFilterIssuesAndTrackNew(totalRecentIdSet, fetchedLinkedIssues, returnList);

        processIssues(totalRecentIdSet, filteredResults.getLinkedIds(), filteredResults.getUniqueIssues(), returnList);
    }

    private void processLinkedIssues(
            Set<String> totalRecentIdSet,
            Set<String> linkedIssueRecentIdSet,
            List<AlmIssueEntity> returnList,
            String field,
            String startDate, String endDate) {

        if (ObjectUtils.isEmpty(linkedIssueRecentIdSet)) return;

        List<AlmIssueEntity> fetchedLinkedIssues =
                getAlmIssuesByRecentIds(linkedIssueRecentIdSet.stream().toList(), field, startDate, endDate);

        FilteredResults filteredResults =
                processAndFilterIssuesAndTrackNew(totalRecentIdSet, fetchedLinkedIssues, returnList);

        processIssues(totalRecentIdSet, filteredResults.getLinkedIds(), filteredResults.getUniqueIssues(),
                returnList, field, startDate, endDate);
    }

    private void processSubtasks(Set<String> totalRecentIdSet, List<String> upperKeyIds, List<AlmIssueEntity> returnList) {
        if (ObjectUtils.isEmpty(upperKeyIds)) {
            return;
        }

        List<AlmIssueEntity> subtasksByUpperKeys = getSubtasksByUpperKeys(upperKeyIds);

        FilteredResults filteredResults = processAndFilterIssuesAndTrackNew(totalRecentIdSet, subtasksByUpperKeys, returnList);


        processIssues(totalRecentIdSet, filteredResults.getLinkedIds(), filteredResults.getUniqueIssues(), returnList);
    }

    private void processSubtasks(
            Set<String> totalRecentIdSet,
            List<String> upperKeyIds,
            List<AlmIssueEntity> returnList,
            String field,
            String startDate, String endDate) {

        if (ObjectUtils.isEmpty(upperKeyIds)) return;

        List<AlmIssueEntity> subtasksByUpperKeys =
                getSubtasksByUpperKeys(upperKeyIds, field, startDate, endDate);

        FilteredResults filteredResults =
                processAndFilterIssuesAndTrackNew(totalRecentIdSet, subtasksByUpperKeys, returnList);

        processIssues(totalRecentIdSet, filteredResults.getLinkedIds(), filteredResults.getUniqueIssues(),
                returnList, field, startDate, endDate);
    }

    /**
     * 문제를 처리하고, 중복되지 않은 ID를 필터링하여 반환
     */
    private FilteredResults processAndFilterIssuesAndTrackNew(
            Set<String> totalRecentIdSet, List<AlmIssueEntity> issues, List<AlmIssueEntity> returnList) {

        Set<String> linkedIdSet = new HashSet<>();
        List<AlmIssueEntity> uniqueIssues = new ArrayList<>();

        for (AlmIssueEntity issue : issues) {
            // 이미 처리된 issue RecentID 인지 확인
            if (!totalRecentIdSet.contains(issue.getRecentId())) {
                totalRecentIdSet.add(issue.getRecentId());// 처리된 issue ID 추가
                returnList.add(issue);                    // 결과 리스트로 추가
                uniqueIssues.add(issue);                  // 고유 issue 리스트로 추가
            }

            // linkedIssues(연결된 이슈) 추출
            if (!ObjectUtils.isEmpty(issue.getLinkedIssues())) {
                linkedIdSet.addAll(issue.getLinkedIssues());
            }
        }
        linkedIdSet.removeIf(totalRecentIdSet::contains); // 이미 처리된 ID는 제외

        return new FilteredResults(linkedIdSet, uniqueIssues);
    }
}
