package com.arms.api.report.fulldata.service;

import com.arms.api.issue.almapi.model.entity.AlmIssueEntity;
import com.arms.api.report.fulldata.model.vo.AssigneeIssueCountVO;
import com.arms.api.report.fulldata.model.dto.FullDataRequestDTO;
import com.arms.api.report.fulldata.model.dto.FullDataResponseDTO;
import com.arms.api.report.fulldata.model.vo.작업자_정보;
import com.arms.api.serverinfo.service.ServerInfoService;
import com.arms.egovframework.javaservice.esframework.esquery.filter.RangeQueryFilter;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.CompositeGroupDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.esquery.CompositeSourceDTO;
import com.arms.egovframework.javaservice.esframework.model.dto.request.CompositeRequestDTO;
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.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service("리포트_서비스")
@AllArgsConstructor
public class ReportServiceImpl implements ReportService {

    private final EsCommonRepositoryWrapper<AlmIssueEntity> esCommonRepositoryWrapper;

    private ServerInfoService serverInfoService;

    @Override
    public List<작업자_정보> 작업자_목록_가져오기() {

        Map<String, String> serverIdToTypeMap = serverInfoService.getServerIdToTypeMap();

        DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecent(
                SimpleQuery.aggregation(
                    AggregationRequestDTO.builder()
                        .mainField("assignee.assignee_emailAddress.keyword")
                        .mainFieldAlias("emailAddress")
                        .addGroup(
                            SubGroupFieldDTO.builder()
                                    .subFieldAlias("jiraServerId")
                                    .subField("jira_server_id")
                                    .build()
                            ,
                            SubGroupFieldDTO.builder()
                                    .subFieldAlias("displayName")
                                    .subField("assignee.assignee_displayName.keyword")
                                    .build()
                            ,
                            SubGroupFieldDTO.builder()
                                    .subFieldAlias("accountId")
                                    .subField("assignee.assignee_accountId.keyword")
                                    .build()
                        )
                    .build()
                ).andExistsQueryFilter("assignee")
        );

        List<DocumentBucket> deepestList = documentAggregations.deepestList();

        Set<작업자_정보> 작업자_목록 = new HashSet<>();

        if (documentAggregations.getTotalHits() == 0) {
            log.info("[ 리포트_서비스_프로세스 :: 작업자_목록_가져오기 ] :: 작업자 정보가 없습니다. 사이즈 => 0");
        }else{

            for (DocumentBucket documentBucket : deepestList) {

                작업자_정보 작업자 = new 작업자_정보();

                작업자.setEmailAddress(documentBucket.valueByName("emailAddress"));

                작업자.setServerType(serverIdToTypeMap.get(documentBucket.valueByName("jiraServerId")));

                작업자.setDisplayName(documentBucket.valueByName("displayName"));

                작업자.setAccountId(documentBucket.valueByName("accountId"));

                작업자_목록.add(작업자);
            }

        }

        if(작업자_목록.isEmpty()) {
            return new ArrayList<>(Collections.emptyList());
        } else {
            return new ArrayList<>(작업자_목록);
        }
    }

    private SimpleQuery<CompositeGroupDTO> 빌더에_넣을_공통_쿼리_목록_세팅_컴포지트_집계_처리(SimpleQuery<CompositeGroupDTO> simpleQuery, FullDataRequestDTO fullDataRequestDTO) {

        if (fullDataRequestDTO.getPdServiceId() != null) {
            simpleQuery.andTermQueryFilter("pdServiceId", fullDataRequestDTO.getPdServiceId());
        }

        if (fullDataRequestDTO.getPdServiceVersionIds() != null && !fullDataRequestDTO.getPdServiceVersionIds().isEmpty()) {
            simpleQuery.andTermsQueryFilter("pdServiceVersions", fullDataRequestDTO.getPdServiceVersionIds());
        }

        if (fullDataRequestDTO.getAlmProjectUrls() != null && !fullDataRequestDTO.getAlmProjectUrls().isEmpty()) {
            simpleQuery.andTermsQueryFilter("project.project_self.keyword", fullDataRequestDTO.getAlmProjectUrls());
        }

        // 작업자
        if (fullDataRequestDTO.getEmailAddress() != null && !fullDataRequestDTO.getEmailAddress().isEmpty()) {
            simpleQuery.andTermsQueryFilter("assignee.assignee_emailAddress.keyword", fullDataRequestDTO.getEmailAddress());
        }

        if (fullDataRequestDTO.getStartDate() != null && fullDataRequestDTO.getEndDate() == null) {
            simpleQuery.andRangeQueryFilter(RangeQueryFilter.of("updated").gte(fullDataRequestDTO.getStartDate()));
        }
        else if (fullDataRequestDTO.getStartDate() == null && fullDataRequestDTO.getEndDate() != null) {
            simpleQuery.andRangeQueryFilter(RangeQueryFilter.of("updated").lte(fullDataRequestDTO.getStartDate()));
        }
        else if (fullDataRequestDTO.getStartDate() != null && fullDataRequestDTO.getEndDate() != null) {
            simpleQuery.andRangeQueryFilter(RangeQueryFilter.of("updated").betweenDate(fullDataRequestDTO.getStartDate(),fullDataRequestDTO.getEndDate()));
        }
        return simpleQuery;
    }

    @Override
    public FullDataResponseDTO retrieveFullDataIssueList(FullDataRequestDTO fullDataRequestDTO) {
        List<AlmIssueEntity> 모든_이슈_목록 = new ArrayList<>();

        CompositeRequestDTO searchRequestDTO = CompositeRequestDTO.builder()
                .addSource(
                        CompositeSourceDTO.builder()
                                .fieldAlias("update_date")
                                .field("updated")
                                .isAscending(false)
                                .build(),
                        CompositeSourceDTO.builder()
                                .fieldAlias("recent_id")
                                .field("recent_id")
                                .isAscending(false)
                                .build()
                )
                .sortField("@timestamp")
                .includeFields("isReq",
                        "key",
                        "project.project_name.keyword",
                        "cReqProperty.cReqStateName.keyword",
                        "created",
                        "updated",
                        "resolutiondate",
                        "summary.keyword",
                        "pdServiceId",
                        "pdServiceVersions",
                        "cReqLink",
                        "upperKey",
                        "parentReqKey",
                        "issueID.keyword",
                        "recent_id",
                        "assignee.assignee_displayName.keyword",
                        "assignee.assignee_emailAddress.keyword",
                        "status.status_name.keyword",
                        "linkedIssues",
                        "etc")
                .build();

        SimpleQuery<CompositeGroupDTO> compositeQuery
                = 빌더에_넣을_공통_쿼리_목록_세팅_컴포지트_집계_처리(SimpleQuery.composite(searchRequestDTO), fullDataRequestDTO);
        List<AlmIssueEntity> fetchedIssues = esCommonRepositoryWrapper.compositeRecentAllHits(compositeQuery).toDocs();

        // 조회된 이슈에서 요구사항 이슈의 key Set 추출
        Set<String> 요구사항_키_Set = fetchedIssues.stream()
                .filter(issue -> Boolean.TRUE.equals(issue.getIsReq()))
                .map(AlmIssueEntity::getKey)
                .collect(Collectors.toSet());

        // 조회된 이슈의 recentId Set (중복 조회 방지용)
        Set<String> 조회된_recentId_Set = fetchedIssues.stream()
                .map(AlmIssueEntity::getRecentId)
                .collect(Collectors.toSet());

        // 하위이슈 중 최상위 요구사항 이슈가 조회되지 않은 경우의 parentReqKey 추출
        List<String> 누락된_요구사항_키_목록 = fetchedIssues.stream()
                .filter(issue -> Boolean.FALSE.equals(issue.getIsReq()))
                .map(AlmIssueEntity::getParentReqKey)
                .filter(Objects::nonNull)
                .filter(parentReqKey -> !요구사항_키_Set.contains(parentReqKey))
                .distinct()
                .collect(Collectors.toList());

        // 누락된 요구사항 이슈 추가 조회
        List<AlmIssueEntity> 추가_요구사항_목록 = new ArrayList<>();
        if (!누락된_요구사항_키_목록.isEmpty()) {
            추가_요구사항_목록 = esCommonRepositoryWrapper.compositeRecentAllHits(
                    SimpleQuery.composite(searchRequestDTO)
                            .andTermQueryMust("isReq", true)
                            .andTermsQueryFilter("key", 누락된_요구사항_키_목록)
            ).toDocs();
            log.info("[ReportServiceImpl :: retrieveFullDataIssueList] :: 추가 조회된 요구사항 이슈 => {}", 추가_요구사항_목록.size());
        }

        // 연결이슈 처리: 조회된 이슈들의 linkedIssues 중 아직 조회되지 않은 이슈 추가 조회
        Set<String> linkedIssues_recentId_Set = fetchedIssues.stream()
                .map(AlmIssueEntity::getLinkedIssues)
                .filter(Objects::nonNull)
                .flatMap(List::stream)
                .collect(Collectors.toSet());

        // 추가 요구사항 목록의 recentId도 조회된 Set에 추가
        추가_요구사항_목록.forEach(issue -> 조회된_recentId_Set.add(issue.getRecentId()));

        // 이미 조회된 recentId 제외
        linkedIssues_recentId_Set.removeAll(조회된_recentId_Set);

        List<AlmIssueEntity> 추가_연결이슈_목록 = new ArrayList<>();
        if (!linkedIssues_recentId_Set.isEmpty()) {
            List<String> linkedIssuesDocIdList = new ArrayList<>(linkedIssues_recentId_Set);
            추가_연결이슈_목록 = esCommonRepositoryWrapper.compositeRecentAllHits(
                    SimpleQuery.composite(searchRequestDTO)
                            .andTermsQueryFilter("recent_id", linkedIssuesDocIdList)
            ).toDocs();
            log.info("[ReportServiceImpl :: retrieveFullDataIssueList] :: 추가 조회된 연결이슈 => {}", 추가_연결이슈_목록.size());
        }

        // 전체 이슈 목록 구성
        모든_이슈_목록.addAll(fetchedIssues);
        모든_이슈_목록.addAll(추가_요구사항_목록);
        모든_이슈_목록.addAll(추가_연결이슈_목록);

        FullDataResponseDTO 결과 = new FullDataResponseDTO();
        결과.setTotalHits((long) 모든_이슈_목록.size());
        결과.setIssueEntityList(모든_이슈_목록);
        log.info("[ReportServiceImpl :: retrieveFullDataIssueList] :: 최초 조회 이슈 => {}, 전체 이슈 => {}", fetchedIssues.size(), 모든_이슈_목록.size());
        return 결과;
    }

    @Override
    public FullDataResponseDTO retrieveCalendarData(FullDataRequestDTO fullDataRequestDTO) {
        List<AlmIssueEntity> 모든_이슈_목록 = new ArrayList<>();

        CompositeRequestDTO searchRequestDTO = CompositeRequestDTO.builder()
                .addSource(
                        CompositeSourceDTO.builder()
                                .fieldAlias("update_date")
                                .field("updated")
                                .isAscending(false)
                                .build(),
                        CompositeSourceDTO.builder()
                                .fieldAlias("recent_id")
                                .field("recent_id")
                                .isAscending(false)
                                .build()
                )
                .sortField("@timestamp")
                .includeFields("isReq",
                        "key",
                        "project.project_name.keyword",
                        "cReqProperty.cReqStateName.keyword",
                        "created",
                        "updated",
                        "resolutiondate",
                        "summary.keyword",
                        "pdServiceId",
                        "pdServiceVersions",
                        "cReqLink",
                        "upperKey",
                        "parentReqKey",
                        "issueID.keyword",
                        "recent_id",
                        "assignee.assignee_displayName.keyword",
                        "assignee.assignee_emailAddress.keyword",
                        "status.status_name.keyword",
                        "linkedIssues",
                        "etc")
                .build();

        SimpleQuery<CompositeGroupDTO> compositeQuery
                = 빌더에_넣을_공통_쿼리_목록_세팅_컴포지트_집계_처리(SimpleQuery.composite(searchRequestDTO), fullDataRequestDTO);
        List<AlmIssueEntity> fetchedIssues = esCommonRepositoryWrapper.compositeAllHits(compositeQuery).toDocs();

        // 조회된 이슈에서 요구사항 이슈의 key Set 추출
        Set<String> 요구사항_키_Set = fetchedIssues.stream()
                .filter(issue -> Boolean.TRUE.equals(issue.getIsReq()))
                .map(AlmIssueEntity::getKey)
                .collect(Collectors.toSet());

        // 조회된 이슈의 recentId Set (중복 조회 방지용)
        Set<String> 조회된_recentId_Set = fetchedIssues.stream()
                .map(AlmIssueEntity::getRecentId)
                .collect(Collectors.toSet());

        // 하위이슈 중 최상위 요구사항 이슈가 조회되지 않은 경우의 parentReqKey 추출
        List<String> 누락된_요구사항_키_목록 = fetchedIssues.stream()
                .filter(issue -> Boolean.FALSE.equals(issue.getIsReq()))
                .map(AlmIssueEntity::getParentReqKey)
                .filter(Objects::nonNull)
                .filter(parentReqKey -> !요구사항_키_Set.contains(parentReqKey))
                .distinct()
                .collect(Collectors.toList());

        // 누락된 요구사항 이슈 추가 조회
        List<AlmIssueEntity> 추가_요구사항_목록 = new ArrayList<>();
        if (!누락된_요구사항_키_목록.isEmpty()) {
            추가_요구사항_목록 = esCommonRepositoryWrapper.compositeAllHits(
                    SimpleQuery.composite(searchRequestDTO)
                            .andTermQueryMust("isReq", true)
                            .andTermsQueryFilter("key", 누락된_요구사항_키_목록)
            ).toDocs();
            log.info("[ReportServiceImpl :: retrieveCalendarData] :: 추가 조회된 요구사항 이슈 => {}", 추가_요구사항_목록.size());
        }

        // 연결이슈 처리: 조회된 이슈들의 linkedIssues 중 아직 조회되지 않은 이슈 추가 조회
        Set<String> linkedIssues_recentId_Set = fetchedIssues.stream()
                .map(AlmIssueEntity::getLinkedIssues)
                .filter(Objects::nonNull)
                .flatMap(List::stream)
                .collect(Collectors.toSet());

        // 추가 요구사항 목록의 recentId도 조회된 Set에 추가
        추가_요구사항_목록.forEach(issue -> 조회된_recentId_Set.add(issue.getRecentId()));

        // 이미 조회된 recentId 제외
        linkedIssues_recentId_Set.removeAll(조회된_recentId_Set);

        List<AlmIssueEntity> 추가_연결이슈_목록 = new ArrayList<>();
        if (!linkedIssues_recentId_Set.isEmpty()) {
            List<String> linkedIssuesDocIdList = new ArrayList<>(linkedIssues_recentId_Set);
            추가_연결이슈_목록 = esCommonRepositoryWrapper.compositeAllHits(
                    SimpleQuery.composite(searchRequestDTO)
                            .andTermsQueryFilter("recent_id", linkedIssuesDocIdList)
            ).toDocs();
            log.info("[ReportServiceImpl :: retrieveCalendarData] :: 추가 조회된 연결이슈 => {}", 추가_연결이슈_목록.size());
        }

        // 전체 이슈 목록 구성
        모든_이슈_목록.addAll(fetchedIssues);
        모든_이슈_목록.addAll(추가_요구사항_목록);
        모든_이슈_목록.addAll(추가_연결이슈_목록);

        // key + updated 기준 중복 제거
        Map<String, AlmIssueEntity> uniqueIssues = 모든_이슈_목록.stream()
                .collect(Collectors.toMap(
                        issue -> issue.getKey() + "::" + issue.getUpdated(),
                        Function.identity(),
                        (existing, newer) -> newer,
                        LinkedHashMap::new
                ));
        List<AlmIssueEntity> distinctIssues = new ArrayList<>(uniqueIssues.values());

        FullDataResponseDTO 결과 = new FullDataResponseDTO();
        결과.setTotalHits((long) distinctIssues.size());
        결과.setIssueEntityList(distinctIssues);
        log.info("[ReportServiceImpl :: retrieveCalendarData] :: 최초 조회 이슈 => {}, 중복 제거 후 => {}", 모든_이슈_목록.size(), distinctIssues.size());
        return 결과;
    }


    @Override
    public FullDataResponseDTO getReqIssueList(FullDataRequestDTO fullDataRequestDTO){
        CompositeRequestDTO searchRequestDTO = CompositeRequestDTO.builder()
                .addSource(
                        CompositeSourceDTO.builder()
                                .fieldAlias("update_date")
                                .field("updated")
                                .isAscending(false)
                                .build(),
                        CompositeSourceDTO.builder()
                                .fieldAlias("recent_id")
                                .field("recent_id")
                                .isAscending(false)
                                .build()
                )
                .sortField("@timestamp")
                .includeFields("isReq",
                        "key",
                        "project.project_name.keyword",
                        "cReqProperty.cReqStateName.keyword",
                        "created",
                        "updated",
                        "resolutiondate",
                        "summary.keyword",
                        "pdServiceId",
                        "pdServiceVersions",
                        "cReqLink",
                        "upperKey",
                        "parentReqKey",
                        "issueID.keyword",
                        "recent_id",
                        "assignee.assignee_displayName.keyword",
                        "assignee.assignee_emailAddress.keyword",
                        "status.status_name.keyword",
                        "linkedIssues",
                        "etc")
                .build();

        SimpleQuery<CompositeGroupDTO> composite = 빌더에_넣을_공통_쿼리_목록_세팅_컴포지트_집계_처리(SimpleQuery.composite(searchRequestDTO), fullDataRequestDTO);
        composite.andTermsQueryFilter("pdServiceId", fullDataRequestDTO.getPdServiceIds())
                .andTermQueryMust("isReq", true);

        List<AlmIssueEntity> reqList = esCommonRepositoryWrapper.compositeAllHits(composite).toDocs();

        FullDataResponseDTO result = new FullDataResponseDTO();
        result.setTotalHits((long) reqList.size());
        result.setIssueEntityList(reqList);
        return result;
    }

    @Override
    public AssigneeIssueCountVO getAssigneeAggregationList(FullDataRequestDTO fullDataRequestDTO) {

        DocumentAggregations documentAggregations = esCommonRepositoryWrapper.aggregateRecent(
                SimpleQuery.aggregation(
                    AggregationRequestDTO.builder()
                        .mainField("assignee.assignee_emailAddress.keyword")
                        .mainFieldAlias("assigneeEmail")
                        .size(fullDataRequestDTO.getSize()).build())
                        .andTermsQueryFilter("pdServiceId", fullDataRequestDTO.getPdServiceIds())
                        .andTermsQueryFilter("pdServiceVersions", fullDataRequestDTO.getPdServiceVersionIds())
                        .andExistsQueryFilter("assignee"));

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

        long totalIssueCount = documentAggregations.getTotalHits();
        long totalAssigneeCount = documentBuckets.size();

        Map<String, Long> assigneeCounts = new HashMap<>();
        for (DocumentBucket documentBucket : documentBuckets) {
            assigneeCounts.put(documentBucket.valueByName("assigneeEmail"), documentBucket.countByName("assigneeEmail"));
        }

        double avgIssuesPerAssignee = totalAssigneeCount > 0
                ? (double) totalIssueCount / totalAssigneeCount
                : 0.0;

        long maxIssues = assigneeCounts.values().stream()
                .mapToLong(Long::longValue)
                .max()
                .orElse(0L);

        long minIssues = assigneeCounts.values().stream()
                .mapToLong(Long::longValue)
                .min()
                .orElse(0L);

        return new AssigneeIssueCountVO(
                totalIssueCount,
                totalAssigneeCount,
                avgIssuesPerAssignee,
                maxIssues,
                minIssues,
                assigneeCounts
        );
    }


}
