package com.arms.api.analysis.cost.service;

import com.arms.api.analysis.cost.model.dto.AssigneeSalaryDTO;
import com.arms.api.analysis.cost.model.dto.CostAggrDTO;
import com.arms.api.analysis.cost.model.dto.CostDTO;
import com.arms.api.analysis.cost.model.dto.CostRequestDTO;
import com.arms.api.analysis.cost.model.vo.*;
import com.arms.api.analysis.resource.model.vo.UniqueAssigneeVO;
import com.arms.api.issue.almapi.model.entity.AlmIssueEntity;
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.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.model.vo.DocumentResultWrapper;
import com.arms.egovframework.javaservice.esframework.repository.common.EsCommonRepositoryWrapper;
import com.arms.egovframework.javaservice.esframework.esquery.SimpleQuery;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static com.arms.egovframework.javaservice.esframework.esquery.SimpleQuery.aggregation;

@Service
@AllArgsConstructor
public class AnalysisCostImpl implements AnalysisCost {

    private final DateTimeFormatter JIRA_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    private final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
    private final EsCommonRepositoryWrapper<AlmIssueEntity> esCommonRepositoryWrapper;

    @Override
    public List<CostVO> getAssigneeList(CostAggrDTO dto) {

        DocumentAggregations aggregations = esCommonRepositoryWrapper.aggregateRecent(
                aggregation(
                        AggregationRequestDTO.builder()
                                .mainField("pdServiceVersions")
                                .mainFieldAlias("pdServiceVersions")
                                .size(10000)
                                .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()
                                )
                                .build()
                )
                        .andTermQueryMust("pdServiceId", dto.getPdServiceLink())
                        .andTermsQueryFilter("pdServiceVersions", dto.getPdServiceVersionLinks())
                        .andExistsQueryFilter("assignee")
        );

        List<DocumentBucket> subList = aggregations.deepestList();

        List<CostVO> costVOList = new ArrayList<>();
        for (DocumentBucket docBucket : subList) {
            String versionKey = docBucket.valueByName("pdServiceVersions");
            Long versionCount = docBucket.countByName("pdServiceVersions");
            String assigneeKey = docBucket.valueByName("assignees");
            Long assigneeCount = docBucket.countByName("assignees");
            String displayNameKey = docBucket.valueByName("displayNames");
            Long displayNameCount = docBucket.countByName("displayNames");
            CostVO costVO = CostVO.builder()
                    .versionKey(versionKey)
                    .versionCount(versionCount)
                    .assigneeKey(assigneeKey)
                    .assigneeCount(assigneeCount)
                    .displayNameKey(displayNameKey)
                    .displayNameCount(displayNameCount)
                    .build();
            costVOList.add(costVO);
        }

        return costVOList;
    }

    @Override
    public List<CostVO> getAssigneeListByProductVersionAndRequirement(CostAggrDTO dto) {

        PdServiceAndIsReqDTO pdServiceAndIsReq = dto.getPdServiceAndIsReq();

        boolean isReq = pdServiceAndIsReq.getIsReq();

        DocumentAggregations aggregations = esCommonRepositoryWrapper.aggregateRecent(
                aggregation(
                        AggregationRequestDTO.builder()
                                .mainField("pdServiceVersions")
                                .mainFieldAlias("pdServiceVersions")
                                .size(10000)
                                .addGroup(
                                        SubGroupFieldDTO.builder()
                                                .subFieldAlias(isReq ? "requirement" : "parentRequirement")
                                                .subField(isReq ? "key" : "parentReqKey")
                                                .size(10000)
                                                .build(),
                                        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()
                                )
                                .build()
                )
                        .andTermQueryMust("pdServiceId", dto.getPdServiceLink())
                        .andTermQueryMust("isReq", isReq)
                        .andTermsQueryFilter("pdServiceVersions", dto.getPdServiceVersionLinks())
                        .andExistsQueryFilter("assignee")
        );

        List<DocumentBucket> subList = aggregations.deepestList();

        List<CostVO> costVOList = new ArrayList<>();
        for (DocumentBucket docBucket : subList) {
            String versionKey = docBucket.valueByName("pdServiceVersions");
            Long versionCount = docBucket.countByName("pdServiceVersions");
            String reqKey = docBucket.valueByName("requirement");
            Long reqCount = docBucket.countByName("requirement");
            String subReqKey = docBucket.valueByName("parentRequirement");
            Long subReqCount = docBucket.countByName("parentRequirement");
            String assigneeKey = docBucket.valueByName("assignees");
            Long assigneeCount = docBucket.countByName("assignees");
            String displayNameKey = docBucket.valueByName("displayNames");
            Long displayNameCount = docBucket.countByName("displayNames");
            CostVO costVO = CostVO.builder()
                    .versionKey(versionKey)
                    .versionCount(versionCount)
                    .reqKey(reqKey)
                    .reqCount(reqCount)
                    .subReqKey(subReqKey)
                    .subReqCount(subReqCount)
                    .assigneeKey(assigneeKey)
                    .assigneeCount(assigneeCount)
                    .displayNameKey(displayNameKey)
                    .displayNameCount(displayNameCount)
                    .build();
            costVOList.add(costVO);
        }


        return costVOList;
    }


    @Override
    public List<CostVO> aggregationByAssigneeAccountId(CostAggrDTO dto) {

        DocumentAggregations aggregations = esCommonRepositoryWrapper.aggregateRecent(
                aggregation(
                        AggregationRequestDTO.builder()
                                .mainField("assignee.assignee_accountId.keyword")
                                .mainFieldAlias("accountId")
                                .size(10000)
                                .build()
                )
                        .andTermQueryMust("pdServiceId", dto.getPdServiceLink())
                        .andTermsQueryFilter("pdServiceVersions", dto.getPdServiceVersionLinks())
                        .andExistsQueryFilter("assignee")
        );

        List<DocumentBucket> documentBuckets = aggregations.docBuckets();

        List<CostVO> costVOList = new ArrayList<>();

        for (DocumentBucket documentBucket : documentBuckets) {
            costVOList.add(CostVO.builder()
                    .assigneeKey(documentBucket.valueByName("accountId"))
                    .assigneeCount(documentBucket.countByName("accountId"))
                    .build());
        }

        return costVOList;
    }

    @Override
    public List<CostVO> aggregationByReqLinkAndAssigneeAccountId(CostAggrDTO dto) {

        DocumentAggregations aggregations = esCommonRepositoryWrapper.aggregateRecent(
                aggregation(
                        AggregationRequestDTO.builder()
                                .mainField("cReqLink")
                                .mainFieldAlias("cReqLink")
                                .size(10000)
                                .addGroup(
                                        SubGroupFieldDTO.builder()
                                                .subFieldAlias("assignees")
                                                .subField("assignee.assignee_accountId.keyword")
                                                .size(10000)
                                                .isAscending(false)
                                                .build()
                                )
                                .build()
                )
                        .andTermQueryMust("pdServiceId", dto.getPdServiceLink())
                        .andTermsQueryFilter("pdServiceVersions", dto.getPdServiceVersionLinks())
                        .andTermQueryMust("isReq", dto.getIsReq())
        );

        List<DocumentBucket> documentBuckets = aggregations.deepestList();

        List<CostVO> costVOList = new ArrayList<>();

        for (DocumentBucket documentBucket : documentBuckets) {
            String cReqLinkKey = documentBucket.valueByName("cReqLink");
            Long cReqLinkCount = documentBucket.countByName("cReqLink");
            String assigneeKey = documentBucket.valueByName("assignees");
            Long assigneeCount = documentBucket.countByName("assignees");

            CostVO costVO = CostVO.builder()
                    .reqLinkKey(cReqLinkKey)
                    .reqLinkCount(cReqLinkCount)
                    .assigneeKey(assigneeKey)
                    .assigneeCount(assigneeCount)
                    .build();
            costVOList.add(costVO);
        }

        return costVOList;
    }

    private Map<String, Double> dailyCostMapByAccountId(List<SalaryVO> salaryList) {
        return salaryList.stream()
                .collect(Collectors.toMap(
                        SalaryVO::getC_key,
                        salaryVO -> {
                            String annualIncomeStr = salaryVO.getC_annual_income();

                            if (annualIncomeStr == null || annualIncomeStr.isEmpty()) {
                                return 0.0; // null 또는 빈 값이면 0
                            }

                            try {
                                double annualIncome = Double.parseDouble(annualIncomeStr);
                                if (annualIncome == 0.0) { // c_annual_income이 0이면 그대로 0
                                    return 0.0;
                                }
                                return Math.round((annualIncome / 365.0) * 100.0) / 100.0;
                            } catch (NumberFormatException e) {
                                return 0.0;
                            }
                        }
                ));

    }


    @Override
    public List<AssigneeTimeDiffVO> calculateWorkdayByAccountId(CostRequestDTO costRequestDTO) {
        // Assignee

        Map<String, UniqueAssigneeVO> assigneeMap = uniqueAssigneeMap(costRequestDTO);

        List<String> accountIdList = assigneeMap.keySet().stream().toList();

        List<AssigneeTimeDiffVO> result = new ArrayList<>();

        for (String accountId : accountIdList) {
            TimeDiffVO timeDiff = getAlmIssueListByAccountId(costRequestDTO, accountId);
            UniqueAssigneeVO uniqueAssigneeVO = assigneeMap.get(accountId);
            result.add(
                    AssigneeTimeDiffVO.builder()
                            .accountId(accountId)
                            .assigneeName(uniqueAssigneeVO.getName())
                            .emailAddress(uniqueAssigneeVO.getEmailAddress())
                            .daysDiff(timeDiff.getDaysDiff())
                            .hoursDiff(timeDiff.getHoursDiff())
                            .build()
            );
        }
        return result;
    }

    private TimeDiffVO getAlmIssueListByAccountId(CostRequestDTO costRequestDTO, String accountId) {

        DocumentResultWrapper<AlmIssueEntity> hits = esCommonRepositoryWrapper.findRecentHits(
                simpleQueryForSearchIssueByAccountId(costRequestDTO, accountId, null)
        );

        long totalDays = 0L;
        long totalHours = 0L;

        if (hits.getTotalHits() > 0) {
            List<AlmIssueEntity> docs = hits.toDocs();
            List<ZonedDateRange> ranges = new ArrayList<>();

            for (AlmIssueEntity doc : docs) {
                ZonedDateTime start = parseDate(doc.stringValueOfCreatedDate()).toLocalDate().atStartOfDay(ZoneId.systemDefault());
                ZonedDateTime end = parseDate(doc.stringValueOfResolutionDate()).toLocalDate().atStartOfDay(ZoneId.systemDefault());
                ranges.add(new ZonedDateRange(start, end));
            }

            List<ZonedDateRange> mergedRanges = mergeDateRanges(ranges);

            totalDays = mergedRanges.stream()
                    .mapToLong(r -> ChronoUnit.DAYS.between(r.getStart().toLocalDate(), r.getEnd().toLocalDate()) + 1)
                    .sum();

            totalHours = mergedRanges.stream()
                    .mapToLong(r -> Duration.between(r.getStart(), r.getEnd()).toHours())
                    .sum();

        }

        return TimeDiffVO.builder().daysDiff(totalDays).hoursDiff(totalHours).build();
    }

    private List<ZonedDateRange> mergeDateRanges(List<ZonedDateRange> ranges) {
        if (ranges.isEmpty()) return Collections.emptyList();

        ranges.sort(Comparator.comparing(ZonedDateRange::getStart));
        List<ZonedDateRange> merged = new ArrayList<>();
        ZonedDateRange prev = ranges.get(0);

        for (int i = 1; i < ranges.size(); i++) {
            ZonedDateRange curr = ranges.get(i);

            if (!curr.getStart().isAfter(prev.getEnd().plusDays(1))) {
                ZonedDateTime newEnd = prev.getEnd().isAfter(curr.getEnd()) ? prev.getEnd() : curr.getEnd();
                prev = new ZonedDateRange(prev.getStart(), newEnd);
            } else {
                merged.add(prev);
                prev = curr;
            }
        }
        merged.add(prev);
        return merged;
    }

    private SimpleQuery<SearchDocDTO> simpleQueryForSearchIssueByAccountId(CostRequestDTO costRequestDTO, String accountId, Boolean isReq) {

        PdServiceAndIsReqDTO pdServiceAndIsReq = costRequestDTO.getPdServiceAndIsReq();

        return SimpleQuery.termQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
                .andTermQueryMust("assignee.assignee_accountId.keyword", accountId)
                .andTermQueryMust("isReq", isReq)
                .andRangeQueryFilter(RangeQueryFilter.of("updated").betweenDate(costRequestDTO.getStartDate(), costRequestDTO.getEndDate()))
                .andExistsQueryFilter("resolutiondate");
    }


    private Map<String, UniqueAssigneeVO> uniqueAssigneeMap(CostRequestDTO costRequestDTO) {

        PdServiceAndIsReqDTO pdServiceAndIsReq = costRequestDTO.getPdServiceAndIsReq();

        DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecent(
                aggregation(
                        AggregationRequestDTO.builder()
                                .mainField("assignee.assignee_accountId.keyword")
                                .mainFieldAlias("accountId")
                                .addGroup(
                                        SubGroupFieldDTO.builder()
                                                .subFieldAlias("assigneeName")
                                                .subField("assignee.assignee_displayName.keyword")
                                                .build(),
                                        SubGroupFieldDTO.builder()
                                                .subFieldAlias("assigneeEmail")
                                                .subField("assignee.assignee_emailAddress.keyword")
                                                .build()
                                )
                                .build()
                )
                        .andTermQueryMust("pdServiceId", pdServiceAndIsReq.getPdServiceId())
                        .andTermsQueryFilter("pdServiceVersions", pdServiceAndIsReq.getPdServiceVersions())
                        .andTermQueryMust("isReq", null)
                        .andRangeQueryFilter(RangeQueryFilter.of("updated").betweenDate(costRequestDTO.getStartDate(), costRequestDTO.getEndDate()))
                        .andExistsQueryFilter("assignee")
                        .andTermsQueryFilter("assignee.assignee_accountId.keyword", costRequestDTO.getAccounts())
        );
        List<DocumentBucket> deepestList = documentAggregations.deepestList();

        Map<String, UniqueAssigneeVO> accountIdAssigneeMap = new HashMap<>();

        for (DocumentBucket documentBucket : deepestList) {
            String accountId = documentBucket.valueByName("accountId");
            String name = documentBucket.valueByName("assigneeName");
            String email = documentBucket.valueByName("assigneeEmail");

            accountIdAssigneeMap.compute(accountId, (key, existingAssignee) -> {
                if (existingAssignee == null) {
                    return UniqueAssigneeVO.builder()
                            .accountId(accountId)
                            .name(name)
                            .emailAddress(Optional.ofNullable(email).orElse(""))
                            .build();
                } else {
                    // 기존 객체의 name과 email을 업데이트
                    String updatedName = mergeNames(existingAssignee.getName(), name);
                    String updatedEmail = mergeEmails(existingAssignee.getEmailAddress(), email);

                    return UniqueAssigneeVO.builder()
                            .accountId(existingAssignee.getAccountId())
                            .name(updatedName)
                            .emailAddress(updatedEmail)
                            .build();
                }
            });
        }

        return accountIdAssigneeMap;
    }

    private String mergeNames(String existingName, String newName) {
        if (existingName == null || existingName.isEmpty()) {
            return newName;
        }
        if (newName == null || newName.isEmpty()) {
            return existingName;
        }
        if (existingName.contains(newName)) {
            return existingName; // 중복 방지
        }
        return existingName + ", " + newName;
    }

    private String mergeEmails(String existingEmail, String newEmail) {
        if (existingEmail == null || existingEmail.isEmpty()) {
            return newEmail;
        }
        if (newEmail == null || newEmail.isEmpty()) {
            return existingEmail;
        }
        if (existingEmail.contains(newEmail)) {
            return existingEmail; // 중복 방지
        }
        return existingEmail + ", " + newEmail;
    }


    // 날짜 문자열을 동적으로 파싱(ZonedDateTime)
    private ZonedDateTime parseDate(String date) {
        DateTimeFormatter formatter = determineFormatter(date);
        return ZonedDateTime.parse(date, formatter);
    }

    // 날짜 차이 계산
    public long calculateDifference(String created, String resolutionDate, String mode) {
        ZonedDateTime createdDate = parseDate(created);
        ZonedDateTime resolvedDate = parseDate(resolutionDate);

        long millisDiff = Duration.between(createdDate, resolvedDate).toMillis();
        long daysDiff = ChronoUnit.DAYS.between(createdDate.toLocalDate(), resolvedDate.toLocalDate());

        if ("millisDiff".equals(mode)) {
            return millisDiff;
        } else {
            // 뺀 결과가 0이면 당일이므로 하루 더 하여 계산
            return daysDiff + 1;
        }
    }

    // 날짜 포멧 체크하여 Formatter 선택
    private DateTimeFormatter determineFormatter(String date) {
        // 시간대 오프셋이 +0900이나 -0900인 경우
        if (date.matches(".*[+-]\\d{4}$")) {
            return JIRA_FORMATTER;  // Jira (yyyy-MM-dd'T'HH:mm:ss.SSSZ)
        } else {
            return ISO_FORMATTER;   // ISO 8601
        }
    }

    public List<CostVO> calculationCost(CostDTO costDTO) {

        // request의 salary 정보로 accountId 별 연봉 정보 맵 생성
        // salaryMap: key = accountId, value = account 정보(SalaryVO)
        Map<String, AssigneeSalaryDTO> assigneeSalaryDTOMap = costDTO.getAccounts();

        // 제품과 버전에 관련 이슈 조회
        List<AlmIssueEntity> relatedIssues = this.getRelatedIssues(costDTO);

        /*
            TODO :: ARMS 요구사항 & 버전 & 담당자 성과 데이터 계산 로직 구현 필요
             1. AlmIssueEntity 순회하며 reqLink(REQADD의 C_ID) 기준 계산된 비용 누적
             2. versions 데이터 순회하며 계산된 버전(pdServiceVersions의 C_ID) 비용 누적
             3. 완료된(resolutiondate)가 있는 데이터는 담당자 성과(assignee key)에 비용 누적
         */
        Map<Long, ReqCostVO> reqCostVOMap = new HashMap<>();
        Map<Long, VersionCostVO> versionCostVOMap = new HashMap<>();
        Map<String, AssigneePerformanceVO> assigneePerformanceVOMap = new HashMap<>();
        List<CostVO> costVOList = new ArrayList<>();

        for (AlmIssueEntity almIssueEntity : relatedIssues) {

            AlmIssueEntity.Assignee assignee = almIssueEntity.getAssignee();
            if (assignee == null) {
                // 담당자가 없는 경우 비용 계산 스킵
                continue;
            }

            String accountId = assignee.getAccountId();
            String displayName = assignee.getDisplayName();

            AssigneeSalaryDTO assigneeSalaryDTO = assigneeSalaryDTOMap.get(accountId);
            if (assigneeSalaryDTO == null) {
                // 연봉 정보가 없는 경우 패스
                continue;
            }

            // ARMS 요구사항
            Long armsReqCId = almIssueEntity.getCReqLink();
            if (armsReqCId == null || armsReqCId <= 0L) {
                // C_ID가 없는 경우 패스(어떤 요구사항의 이슈인지 알 수 없음)
                continue;
            }

            // 생성일(이슈 시작일)
            Date created = almIssueEntity.getCreated();
            if (created == null) {
                // 없을 시 패스
                continue;
            }

            ResolvedInfo resolvedInfo = this.resolveResolutionDates(almIssueEntity);

            double priorityWeight = this.getPriorityWeight(almIssueEntity);
            long salary = Optional.ofNullable(assigneeSalaryDTO.getSalary()).orElse(0L);

            long roundedCost = this.calculateIssueCost(created, resolvedInfo.resolutionDate(), salary, priorityWeight);

            /*
                1. 요구사항(REQADD의 C_ID) 기준 계산된 비용 누적
            */
            reqCostVOMap.compute(armsReqCId, (id, existing) -> {
                long newCost = (existing == null ? 0 : existing.getCost()) + roundedCost;
                return ReqCostVO.builder()
                        .requirementId(armsReqCId)
                        .requirementName(almIssueEntity.getSummary())
                        .issueKey(almIssueEntity.getKey())
                        .cost(newCost)
                        .build();
            });

            // 버전 비용 계산을 위한 pdServiceVersions 조회
            List<Long> pdServiceVersions = almIssueEntity.getLinkedIssuePdServiceVersions();
            /*
                 2. pdServiceVersions 순회하며 각 버전별 비용 누적
            */
            for (Long  versionId : pdServiceVersions) {
                VersionCostVO versionCostVO = versionCostVOMap.get(versionId);

                if (versionCostVO == null) {
                    versionCostVO = VersionCostVO.builder()
                            .versionId(versionId)
                            .cost(roundedCost)
                            .build();
                }
                else {
                    // 비용만 누적해서 새 객체 생성
                    versionCostVO = VersionCostVO.builder()
                            .versionId(versionId)
                            .cost(versionCostVO.getCost() + roundedCost)
                            .build();
                }

                versionCostVOMap.put(versionId, versionCostVO);
            }

            /*
                 3. 완료될 경우 accountId 별로 비용 누적
            */
            if (resolvedInfo.isResolved()) {
                AssigneePerformanceVO assigneePerformanceVO = assigneePerformanceVOMap.get(accountId);
                if (assigneePerformanceVO == null) {
                    assigneePerformanceVO = AssigneePerformanceVO.builder()
                            .assigneeId(accountId)
                            .performance(roundedCost)
                            .salary(salary)
                            .build();
                } else {
                    // 비용만 누적해서 새 객체 생성
                    assigneePerformanceVO = AssigneePerformanceVO.builder()
                            .assigneeId(assigneePerformanceVO.getAssigneeId())
                            .performance(assigneePerformanceVO.getPerformance() + roundedCost)
                            .salary(assigneePerformanceVO.getSalary())
                            .build();
                }

                assigneePerformanceVOMap.put(accountId, assigneePerformanceVO);
            }

            CostVO costVO = CostVO.builder()
                    .assigneeKey(accountId)
                    .displayNameKey(displayName)
                    .reqKey(almIssueEntity.getKey())
                    .reqLinkKey(almIssueEntity.getCReqLink().toString())
                    .versionKey(almIssueEntity.getLinkedIssuePdServiceVersions().toString())
                    .build();

            costVOList.add(costVO);
        }

        return costVOList;
    }

    private List<AlmIssueEntity> getRelatedIssues(CostDTO costDTO) {

        PdServiceAndIsReqDTO pdServiceAndIsReq = costDTO.getPdServiceAndIsReq();

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

        return esCommonRepositoryWrapper.findRecentHits(
                SimpleQuery.termsQueryFilter("linkedIssuePdServiceIds", List.of(pdServiceId))
                        .andTermsQueryFilter("linkedIssuePdServiceVersions", pdServiceVersions)
                        .andRangeQueryFilter(RangeQueryFilter.of("updated")
                                .betweenDate(costDTO.getStartDate(), costDTO.getEndDate()))
        ).toDocs();
    }

    private long calculateIssueCost(Date createdDate, Date resolutionDate, long salary, double priorityWeight) {

        long cost = this.calculateCostByAssignee(createdDate, resolutionDate, salary);
        return (long) Math.ceil(cost * priorityWeight);
    }

    private long calculateCostByAssignee(Date createdDate, Date resolutionDate, long salary) {

        LocalDate start = createdDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate end = resolutionDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

        long workdays = calculateWorkdays(start, end);
        long dailyPay = Math.round((double) salary / 365);
        return workdays * dailyPay;
    }

    private long calculateWorkdays(LocalDate start, LocalDate end) {


        long workdays = 1;
        LocalDate current = start;

        while (!current.isAfter(end)) {
            // 주말 제외
            if (current.getDayOfWeek() != DayOfWeek.SATURDAY
                    && current.getDayOfWeek() != DayOfWeek.SUNDAY) {
            /*
                TODO: 시작일과 종료일 사이의 일수 계산하면서 연휴를 뺄 수 있도록 처리 필요
            */
//                if (!isHoliday(current)) {
//                    workdays++;
//                }
            }

            current = current.plusDays(1);
        }

        return workdays;
    }

    private ResolvedInfo resolveResolutionDates(AlmIssueEntity issue) {

        Date resolutionDate = issue.getResolutiondate();

        // 진행중 이슈
        if (resolutionDate == null) {
            return new ResolvedInfo(new Date(), false);
        }

//        Date updated = issue.getUpdated();
//        if (updated != null && resolutionDate.before(updated)) {
//            // 다시 open 된 것으로 간주할지 처리 But 이름이 바뀌어도 updated 날짜는 바뀔것임.
//            return new ResolvedInfo(new Date(), false);
//        }

        return new ResolvedInfo(resolutionDate, true);
    }

    private double getPriorityWeight(AlmIssueEntity almIssueEntity) {
        AlmIssueEntity.CReqProperty cReqProperty = almIssueEntity.getCReqProperty();
        if (cReqProperty == null) return 1.0;

        // TODO :: 우선순위에 따른 가중치 적용 로직 구현 필요
        Long armsReqPriorityId = cReqProperty.getCReqPriorityLink();
        String armsReqPriorityName = cReqProperty.getCReqPriorityName();

        return 1.0;
    }
}
