package com.arms.api.issue.almapi.strategy;

import com.arms.api.issue.almapi.model.dto.*;
import com.arms.api.issue.almapi.model.entity.AlmIssueEntity;
import com.arms.api.issue.almapi.model.vo.*;
import com.arms.api.issue.priority.model.IssuePriorityDTO;
import com.arms.api.issue.status.model.dto.IssueStatusDTO;
import com.arms.api.issue.type.model.dto.IssueTypeDTO;
import com.arms.api.almproject.model.dto.ProjectDTO;
import com.arms.api.serverinfo.model.vo.ServerInfoVO;
import com.arms.api.serverinfo.service.ServerInfoService;
import com.arms.api.util.LRUMap;
import com.arms.api.util.alm.RedmineApi;
import com.arms.api.util.alm.RedmineUtil;
import com.arms.api.util.errors.ErrorCode;
import com.arms.api.util.errors.ErrorLogUtil;
import com.arms.api.issue.almapi.service.CategoryMappingService;
import com.arms.egovframework.javaservice.esframework.esquery.SimpleQuery;
import com.arms.egovframework.javaservice.esframework.repository.common.EsCommonRepositoryWrapper;
import com.taskadapter.redmineapi.*;
import com.taskadapter.redmineapi.bean.*;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Component;

import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Stream;


@Slf4j
@Component("REDMINE_ON_PREMISS")
@AllArgsConstructor
public class OnPremiseRedmineIssueStrategy implements IssueStrategy {

    private final RedmineUtil redmineUtil;

    private final RedmineApi redmineApi;

    private final CategoryMappingService categoryMappingService;

    private final EsCommonRepositoryWrapper<AlmIssueEntity> esCommonRepositoryWrapper;

    private final ServerInfoService serverInfoService;

    private final IssueSaveTemplate issueSaveTemplate;

    @Override
    public AlmIssueVO createIssue(AlmIssueDTO almIssueDTO) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueDTO.getServerId());

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(serverInfoVO.getUri()
                , serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        IssueCreationFieldsDTO fieldsDTO = almIssueDTO.getFields();
        if (fieldsDTO == null) {
            String errorMessage = String.format("%s[%s] :: 이슈 생성 필드 데이터가 존재 하지 않습니다.",
                    serverInfoVO.getType(), serverInfoVO.getUri());
            log.error(errorMessage);
            throw new IllegalArgumentException(ErrorCode.REQUEST_BODY_ERROR_CHECK.getErrorMsg() + " :: " + errorMessage);
        }

        String projectKeyOrId = null;
        String issueTypeId = null;

        if (fieldsDTO.getProject()!= null && StringUtils.isNotEmpty(fieldsDTO.getProject().getId())) {
            projectKeyOrId = fieldsDTO.getProject().getId();
        }

        if (fieldsDTO.getIssuetype() != null
                && StringUtils.isNotEmpty(fieldsDTO.getIssuetype().getId())) {
            issueTypeId = fieldsDTO.getIssuetype().getId();
        }

        if (StringUtils.isEmpty(projectKeyOrId) || StringUtils.isEmpty(issueTypeId)) {
            String errorMessage = String.format("%s :: %s 이슈 생성 필드 확인에 필요한 프로젝트 아이디, 이슈유형 아이디가 존재하지 않습니다.",
                    serverInfoVO.getType(), serverInfoVO.getUri());
            throw new IllegalArgumentException(errorMessage);
        }

        Tracker issueType = new Tracker()
                .setId(Integer.parseInt(issueTypeId));

        Issue issue = new Issue(redmineManager.getTransport(), Integer.parseInt(projectKeyOrId))
                .setTracker(issueType);

        if (StringUtils.isNotEmpty(fieldsDTO.getSummary())) {
            issue.setSubject(fieldsDTO.getSummary());
        }

        if (StringUtils.isNotEmpty(fieldsDTO.getDescription())) {
            issue.setDescription(fieldsDTO.getDescription());
        }

        if (fieldsDTO.getPriority() != null && StringUtils.isNotEmpty(fieldsDTO.getPriority().getId())) {
            issue.setPriorityId(Integer.parseInt(fieldsDTO.getPriority().getId()));
        }

        if (fieldsDTO.getStartDate() != null) {
            issue.setStartDate(fieldsDTO.getStartDate());
        }

        if (fieldsDTO.getDueDate() != null) {
            issue.setDueDate(fieldsDTO.getDueDate());
        }

        try {
            issue = issue.create();
        }
        catch (RedmineException e) {
            String errorMessage = ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    String.format("%s[%s],  프로젝트[%s], 이슈유형[%s], 생성 필드 :: 이슈 생성하기 중 오류",
                            serverInfoVO.getType(), serverInfoVO.getUri(), projectKeyOrId, issueTypeId, issue.toString()));
            throw new IllegalArgumentException(ErrorCode.ISSUE_CREATION_ERROR.getErrorMsg() + " :: " + errorMessage);
        }

        User user = userList(new HashSet<>(), issue.getAssigneeId(), redmineManager);

        OnPremissRedmineIssueVO onPremissRedmineIssueVO = OnPremissRedmineIssueVO.builder()
                .issue(issue)
                .user(user)
                .serverInfoVO(serverInfoVO).build();

        return convertOnRedmineIssueToAlmIssue(onPremissRedmineIssueVO);
    }

    @Override
    public Map<String, Object> updateIssue(AlmIssueDTO almIssueDTO) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueDTO.getServerId());

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(serverInfoVO.getUri()
                , serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        Map<String, Object> result = new HashMap<>();
        IssueCreationFieldsDTO fieldsDTO = almIssueDTO.getFields();
        String summary = fieldsDTO.getSummary();
        String description = fieldsDTO.getDescription();
        IssueStatusDTO issueStatusDTO = fieldsDTO.getStatus();

        try {
            Issue updatedIssue = redmineManager.getIssueManager().getIssueById(Integer.parseInt(almIssueDTO.getIssueKeyOrId()));

            if (summary != null && !summary.isEmpty()) {
                updatedIssue.setSubject(summary);
            }

            if (description != null) {
                updatedIssue.setDescription(description);
            }

            updatedIssue.update();
            if (issueStatusDTO != null && !StringUtils.isEmpty(issueStatusDTO.getId())) {
                this.updateIssueStatus(almIssueDTO);
            }

            result.put("success", true);
            result.put("message", "이슈 수정 성공");
        }
        catch (RedmineException e) {
            String errorMessage = ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    "레드마인_온프레미스 ["+ serverInfoVO.getUri() +"] :: 이슈_키_또는_아이디 :: "
                            + almIssueDTO.getIssueKeyOrId()+ " :: 수정데이터 :: " + fieldsDTO.toString() + "이슈_수정하기 오류");
            log.error(errorMessage);

            result.put("success", false);
            result.put("message", errorMessage);
        }

        return result;
    }

    @Override
    public Map<String, Object> updateIssueStatus(AlmIssueDTO almIssueDTO) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueDTO.getServerId());

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(serverInfoVO.getUri(), serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        Map<String, Object> result = new HashMap<>();
        String issueKeyOrId = almIssueDTO.getIssueKeyOrId();
        String statusId = almIssueDTO.getStatusId();

        try {

            Issue updatedIssue = redmineManager.getIssueManager().getIssueById(Integer.parseInt(issueKeyOrId));

            updatedIssue.setStatusId(Integer.parseInt(statusId));
            updatedIssue.update();

            boolean checkStatusChange = verifyIssueStatus(almIssueDTO);

            if (checkStatusChange) {
                result.put("success", true);
                result.put("message", "이슈 상태 변경 성공");
            }
            else {
                String errorMessage = "레드마인_온프레미스 [" + serverInfoVO.getUri() + "] :: 이슈 키[" + issueKeyOrId + "] :: 상태 아이디[" + statusId + "] :: 해당 업무 흐름으로 변경이 불가능 합니다.";
                log.error(errorMessage);

                result.put("success", false);
                result.put("message", "변경할 이슈 상태가 존재하지 않습니다.");
            }

        }
        catch (RedmineException e) {
            String errorMessage = ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    "레드마인_온프레미스 [" + serverInfoVO.getUri() + "] :: 이슈_키_또는_아이디 :: "
                            + issueKeyOrId + " :: 수정데이터 :: " + statusId + "이슈_상태_변경하기 오류");
            log.error(errorMessage);

            result.put("success", false);
            result.put("message", errorMessage);
        }

        return result;
    }

    public boolean verifyIssueStatus(AlmIssueDTO almIssueDTO) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueDTO.getServerId());

        String issueKeyOrId = almIssueDTO.getIssueKeyOrId();

        String statusId = almIssueDTO.getStatusId();

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(serverInfoVO.getUri(), serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        try {
            Issue updatedIssue = redmineManager.getIssueManager().getIssueById(Integer.parseInt(issueKeyOrId));

            return Optional.ofNullable(updatedIssue)
                    .map(Issue::getStatusId)
                    .map(currentStatus -> StringUtils.equals(statusId, String.valueOf(currentStatus)))
                    .orElse(false);
        }
        catch (RedmineException e) {
            ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    "레드마인_온프레미스 ["+ serverInfoVO.getUri() +"] :: 이슈_키_또는_아이디 :: "
                            + issueKeyOrId + " :: 이슈_상태_검증하기 오류");
            return false;
        }
    }

    @Override
    public Map<String, Object> deleteIssue(AlmIssueDTO almIssueDTO) {

        Map<String, Object> result = new HashMap<>();

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueDTO.getServerId());

        String issueKeyOrId = almIssueDTO.getIssueKeyOrId();

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(serverInfoVO.getUri(), serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        try {
            redmineManager.getIssueManager().deleteIssue(Integer.valueOf(issueKeyOrId));

            result.put("success", true);
            result.put("message", "이슈 삭제 성공");
        }
        catch (RedmineException e) {
            String errorMessage = ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    "레드마인_온프레미스 ["+ serverInfoVO.getUri() +"] :: 이슈_키_또는_아이디 :: "
                            + issueKeyOrId+ " :: 이슈_삭제하기 오류");
            log.error(errorMessage);

            result.put("success", false);
            result.put("message", errorMessage);
        }

        return result;
    }

    @Override
    public boolean isExistIssue(AlmIssueDTO almIssueDTO)  {
        try {
            AlmIssueVO issue = this.getIssueVO(almIssueDTO, false);
            return issue != null && issue.getKey().equals(almIssueDTO.getIssueKeyOrId());
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public AlmIssueVO getIssueVO(AlmIssueDTO almIssueDTO)  {
        return this.getIssueVO(almIssueDTO, true);
    }

    private AlmIssueVO getIssueVO(AlmIssueDTO almIssueDTO, boolean convertFlag) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueDTO.getServerId());

        String issueKeyOrId = almIssueDTO.getIssueKeyOrId();

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(serverInfoVO.getUri(), serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        try {
            Issue issue = redmineManager.getIssueManager().getIssueById(Integer.parseInt(issueKeyOrId));

            OnPremissRedmineIssueVO onPremissRedmineIssueVO = OnPremissRedmineIssueVO.builder()
                    .issue(issue)
                    .serverInfoVO(serverInfoVO).build();

            if(convertFlag){
                return convertOnRedmineIssueToAlmIssue(onPremissRedmineIssueVO);
            }

            return AlmIssueVO.builder().key(String.valueOf(issue.getId())).build();
        }
        catch (Exception e) {
            ErrorLogUtil.exceptionLogging(e, this.getClass().getName(),
                    "온프레미스 지라(" + serverInfoVO.getUri() + ") :: 이슈 키(" + issueKeyOrId + ") :: 이슈_상세정보_가져오기에 실패하였습니다.");
            throw new IllegalArgumentException("이슈_상세정보_가져오기에 실패하였습니다.");
        }
    }

    @Override
    public List<AlmIssueEntity> discoveryIssueAndGetReqEntities(AlmIssueIncrementDTO almIssueIncrementDTO) {

        Map<String,Issue> lruMap = new LRUMap<>(10000);

        return issueSaveTemplate.discoveryIncrementALmIssueAndGetReqAlmIssueEntities(
                almIssueIncrementDTO, (dto)-> this.discoveryIssueAndGetReqEntities(dto, lruMap));
    }

    private AlmIssueVOCollection discoveryIssueAndGetReqEntities(AlmIssueIncrementDTO almIssueIncrementDTO, Map<String,Issue> lruMap) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueIncrementDTO.getServerId());

        RedmineManager redmineManager = redmineUtil.createRedmineOnPremiseCommunicator(
                serverInfoVO.getUri()+"?v[updated_on][]="+almIssueIncrementDTO.getStartDate()
                , serverInfoService.getDecryptPasswordOrToken(serverInfoVO.getPasswordOrToken()));

        try {

            List<Issue> fetchedIssues = fetchIssuesFromDate(almIssueIncrementDTO, redmineManager);

            List<Issue> issuesFromRedmine = exploreRelatedIssuesByFetchedIssues(fetchedIssues, lruMap, redmineManager);

            List<AlmIssueEntity> almIssueEntities
                = esCommonRepositoryWrapper.findRecentHits(
                    SimpleQuery.termsQueryFilter("key", OnPremissRedmineVOCollection
                                                                    .fromIssues(issuesFromRedmine).keys())
                            .andTermQueryFilter("jira_server_id", serverInfoVO.getConnectId())
                ).toDocs();

            List<AlmIssueEntity> rootIssues = Stream.concat(
                    almIssueEntities.stream()
                            .filter(AlmIssueEntity::izReqTrue),
                    esCommonRepositoryWrapper.findRecentHits(
                            SimpleQuery
                                    .termsQueryFilter("key",
                                            almIssueEntities.stream()
                                                    .filter(AlmIssueEntity::izReqFalse)
                                                    .map(AlmIssueEntity::getParentReqKey)
                                                    .filter(parentReqKey -> !org.springframework.util.ObjectUtils.isEmpty(parentReqKey))
                                                    .toList()
                                    )
                                    .andTermQueryFilter("jira_server_id", serverInfoVO.getConnectId())
                    ).toDocs().stream()
            ).distinct().toList();

            Set<String> visited = new HashSet<>();

            for (AlmIssueEntity almIssueEntity : rootIssues) {

                Queue<AlmIssueEntity> queue = new LinkedList<>();

                queue.add(almIssueEntity);

                while (!queue.isEmpty()) {

                    AlmIssueEntity queueValue = queue.poll();

                    String key = queueValue.getKey();

                    if (!visited.contains(key) && !ObjectUtils.isEmpty(key)) {

                        try {
                            IssueDTO issueDTO = IssueDTO.builder().key(key).build();

                            Issue issue = issueList(lruMap, issueDTO, redmineManager);

                            lruMap.put(String.valueOf(issue.getId()), issue);

                        } catch (NotFoundException e) {

                            log.info("onPremise redmine not found key : {} ", key);

                            List<SearchHit<AlmIssueEntity>> hitDocs = esCommonRepositoryWrapper.findRecentHits(
                                    SimpleQuery.termQueryFilter("recent_id", queueValue.getRecentId())
                            ).toHitDocs();

                            for (SearchHit<AlmIssueEntity> hitDoc : hitDocs) {
                                AlmIssueEntity content = hitDoc.getContent();
                                content.setRecent(false);
                                log.info("delete:key:{}", content.getKey());
                                esCommonRepositoryWrapper.modifyWithIndexName(content, hitDoc.getIndex());
                            }
                        }

                        List<AlmIssueEntity> docsByParentReqKey = esCommonRepositoryWrapper.findRecentHits(
                                SimpleQuery.termQueryFilter("parentReqKey", key)
                                        .andTermQueryFilter("jira_server_id", serverInfoVO.getConnectId())
                        ).toDocs();

                        queue.addAll(docsByParentReqKey);

                        List<AlmIssueEntity> docsByLinkedIssues = esCommonRepositoryWrapper.findRecentHits(
                            SimpleQuery.termQueryFilter("linkedIssues", queueValue.getRecentId())
                        ).toDocs();

                        queue.addAll(docsByLinkedIssues);
                    }
                    visited.add(key);

                    Optional.ofNullable(lruMap.get(key)).ifPresent(issuesFromRedmine::add);
                }

            }

            List<OnPremissRedmineIssueVO> onPremissRedmineIssueVOS
                = issuesFromRedmine.stream().distinct()
                    .flatMap(redmineIssue
                            -> traceAndMapToRequirement(lruMap, redmineIssue, serverInfoVO, redmineManager).stream())
                    .toList();

            OnPremissRedmineVOCollection onPremissRedmineVOCollection = OnPremissRedmineVOCollection.of(
                onPremissRedmineIssueVOS
                    .stream()
                    .map(onPremissRedmineIssueVO -> {
                        if (!issuesFromRedmine.contains(onPremissRedmineIssueVO.getIssue())) {
                            return onPremissRedmineIssueVO.markAsExcludedFromSave();
                        } else {
                            return onPremissRedmineIssueVO;
                        }
                    }).toList());

            List<OnPremissRedmineIssueVO> onPremissRedmineIssueVOs = onPremissRedmineVOCollection.appliedLinkedIssuePdServiceVO();

            return this.convertIncrementOnRedmineIssueToAlmIssues(onPremissRedmineIssueVOs);

        } catch (Exception e) {
            log.error("증분 데이터 수집에 실패 하였습니다.", e);

            ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    "온프레미스 레드마인(" + serverInfoVO.getUri() + ") 증분 이슈 수집 중 오류");

            return new AlmIssueVOCollection(new ArrayList<>());

        }
    }

    private AlmIssueVOCollection convertIncrementOnRedmineIssueToAlmIssues(List<OnPremissRedmineIssueVO> onPremissRedmineIssueVOs){
        return new AlmIssueVOCollection(onPremissRedmineIssueVOs
                .stream()
                .map(this::convertOnRedmineIssueToAlmIssue)
                .toList());
    }

    private AlmIssueVO convertOnRedmineIssueToAlmIssue(OnPremissRedmineIssueVO onPremissRedmineIssueVO) {

        ServerInfoVO serverInfoVO = onPremissRedmineIssueVO.getServerInfoVO();
        Issue issue = onPremissRedmineIssueVO.getIssue();

        String defaultPath = redmineUtil.checkServerInfoPath(serverInfoVO.getUri());
        AlmIssueVO.AlmIssueVOBuilder almIssueVOBuilder = AlmIssueVO.builder();

        almIssueVOBuilder.excludeFromSave(onPremissRedmineIssueVO.isExcludeFromSave());

        Optional.ofNullable(onPremissRedmineIssueVO.relationRecentIds())
                .ifPresent(almIssueVOBuilder::linkedIssue);

        almIssueVOBuilder.almIssueWithRequirementVO(onPremissRedmineIssueVO.getAlmIssueWithRequirementVO());

        Optional.ofNullable(issue.getId())
                .map(String::valueOf)
                .ifPresent(아이디 -> {
                    almIssueVOBuilder
                            .id(아이디)
                            .key(아이디)
                            .self(defaultPath + redmineApi.replaceID(redmineApi.getEndpoint().getIssue(), 아이디));
                });


        Optional.ofNullable(issue.getParentId())
                .ifPresent(a->{
                    almIssueVOBuilder.upperKey(String.valueOf(a));
                });

        Optional<String> rawData = redmineUtil.toJson(issue);
        rawData.ifPresent(almIssueVOBuilder::rawData);

        IssueFieldData IssueFieldData = 레드마인이슈_지라이슈필드_데이터_매핑(issue, defaultPath);

        User user = onPremissRedmineIssueVO.getUser();

        if (user != null) {
            if (!ObjectUtils.isEmpty(issue.getAssigneeId())
                    || !ObjectUtils.isEmpty(issue.getAssigneeName())
                    || !ObjectUtils.isEmpty(user.getMail())) {
                IssueFieldData.setAssignee(
                        UserData.builder()
                                .accountId(Optional.ofNullable(issue.getAssigneeId()).map(String::valueOf).orElse(null))
                                .emailAddress(Optional.ofNullable(user.getMail()).map(String::valueOf).orElse(null))
                                .displayName(issue.getAssigneeName())
                                .build());
            }
        }

        almIssueVOBuilder
                .fields(IssueFieldData)
                .armsStateCategory(getMappingCategory(serverInfoVO, IssueFieldData))
                .linkedIssuePdServiceIds(onPremissRedmineIssueVO.getLinkedIssuePdServiceIds())
                .linkedIssuePdServiceVersions(onPremissRedmineIssueVO.getLinkedIssuePdServiceVersions());

        return almIssueVOBuilder.build();

    }

    private List<Issue> fetchIssuesFromDate(AlmIssueIncrementDTO almIssueIncrementDTO, RedmineManager redmineManager) {

        String endDate = almIssueIncrementDTO.getEndDate();

        String projectKey = almIssueIncrementDTO.getProjectKey();

        int page = 1;

        int maxResults = 25;

        try {

            boolean hasMore = true;

            List<Issue> issueResult = new ArrayList<>();

            while (hasMore) {

                Params params = new Params()
                        .add("f[]", "updated_on")
                        .add("v[updated_on][]", endDate)
                        .add("op[updated_on]", "><")
                        .add("page", String.valueOf(page))
                        .add("project_id", projectKey);

                List<Issue> results = redmineManager.getIssueManager()
                        .getIssues(params)
                        .getResults();

                List<Issue> resultsById = results.stream().map(a -> {
                    try {
                        return redmineManager.getIssueManager()
                                .getIssueById(a.getId(), Include.relations, Include.children, Include.journals);
                    } catch (RedmineException e) {
                        throw new RuntimeException(e);
                    }
                }).toList();

                issueResult.addAll(resultsById);

                syncRenamedIssueKeys(almIssueIncrementDTO, resultsById);

                int fetchCount = results.size();

                if (fetchCount < maxResults) {
                    hasMore = false;
                } else {
                    page++;
                }
            }

            return issueResult;

        } catch (RedmineException e) {
            throw new RuntimeException(e);
        }
    }

    private void syncRenamedIssueKeys(AlmIssueIncrementDTO almIssueIncrementDTO, List<Issue> results) {

        ServerInfoVO serverInfoVO = serverInfoService.verifyServerInfo(almIssueIncrementDTO.getServerId());


        for (Issue issue : results) {

            List<SearchHit<AlmIssueEntity>> hitDocs = esCommonRepositoryWrapper.findRecentHits(
                    SimpleQuery
                            .termQueryFilter("jira_server_id", serverInfoVO.getConnectId())
                            .andTermQueryFilter("key", issue.getId())
            ).toHitDocs();

            List<AlmIssueEntity> almIssueRecentFalseList = new ArrayList<>();

            for (SearchHit<AlmIssueEntity> hitDoc : hitDocs) {

                AlmIssueEntity almIssueEntity = hitDoc.getContent();

                if (!almIssueEntity.getKey().equals(String.valueOf(issue.getId()))
                        || !almIssueEntity.getProject().getKey().equals(String.valueOf(issue.getProjectId()))
                ) {
                    almIssueEntity.setRecent(false);
                    esCommonRepositoryWrapper.modifyWithIndexName(almIssueEntity,hitDoc.getIndex());
                    almIssueRecentFalseList.add(almIssueEntity);
                }
            }

            almIssueRecentFalseList
                    .stream()
                    .filter(a -> a.getRecentId() != null)
                    .findFirst()
                    .ifPresent(almIssueEntity -> {
                        almIssueEntity.modifyProjectKeyAndKey(String.valueOf(issue.getProjectId()), String.valueOf(issue.getId()));
                        esCommonRepositoryWrapper.save(almIssueEntity);
                    });
        }
    }

    private List<OnPremissRedmineIssueVO> traceAndMapToRequirement(
            Map<String,Issue> lruMap, Issue currentIssue,
            ServerInfoVO serverInfoVO, RedmineManager redmineManager) {

        Queue<IssueDTO> queue = new LinkedList<>();
        queue.add(createIssueDTO(currentIssue));
        lruMap.put(String.valueOf(currentIssue.getId()), currentIssue);

        Set<String> recentIds = new HashSet<>();
        Set<String> visited = new HashSet<>();

        while (!queue.isEmpty()) {
            try {
                IssueDTO issueDTO = queue.poll();
                Issue issue = issueList(lruMap, issueDTO, redmineManager);

                if (issue.getId() != null) {
                    lruMap.put(String.valueOf(issue.getId()), issue);
                    String recentId = serverInfoVO.getConnectId() + "_" + issue.getProjectId() + "_" + issue.getId();
                    recentIds.add(recentId);
                }

                if (!visited.contains(String.valueOf(issue.getId()))) {
                    queue.add(createIssueParentTraceDTO(issue));

                    List<Issue> childList = issue.getChildren().stream().toList();
                    for (Issue childIssue : childList) {
                        queue.add(this.createIssueDTO(childIssue));
                    }

                    linkedIssueQueueRegister(issue, queue, visited);
                    visited.add(issueDTO.getKey());
                }
            } catch (RedmineException e) {
                throw new RuntimeException(e);
            }
        }

        return esCommonRepositoryWrapper.findRecentHits(
                SimpleQuery.termsQueryFilter("recent_id", recentIds.stream().toList())
                        .andTermQueryFilter("isReq", true)
                ).toDocs().stream().map(reqIssueEntity -> {

                    String rootParentKey = findRootParentKey(lruMap, String.valueOf(currentIssue.getId()), redmineManager);

                    User assigneeUser = null;
                    Integer assigneeId = currentIssue.getAssigneeId();
                    if (assigneeId != null) {
                        try {
                            assigneeUser = redmineManager.getUserManager().getUserById(assigneeId);
                        } catch (RedmineException e) {
                            log.warn("담당자 조회 실패: {}", assigneeId, e);
                        }
                    }

            return OnPremissRedmineIssueVO.builder()
                    .issue(currentIssue)
                    .user(assigneeUser)
                    .almIssueWithRequirementVO(Optional.ofNullable(isSelfCreatedIssue(reqIssueEntity,rootParentKey)).orElseGet(()->{
                        if(rootParentKey==null){
                            AlmIssueEntity almIssueEntity = esCommonRepositoryWrapper.findRecentHits(
                                    SimpleQuery
                                            .termQueryFilter("key", String.valueOf(currentIssue.getId()))
                                            .andTermQueryFilter("jira_server_id", serverInfoVO.getConnectId())
                                            .andTermQueryFilter("isReq", true)
                            ).fetchOnlyOne();
                            if(almIssueEntity.getKey()!=null){
                                return new AlmIssueWithRequirementVO(almIssueEntity);
                            }
                        }
                        if(rootParentKey!=null){
                            AlmIssueEntity almIssueEntity = esCommonRepositoryWrapper.findRecentHits(
                                    SimpleQuery
                                            .termQueryFilter("key", rootParentKey)
                                            .andTermQueryFilter("jira_server_id", serverInfoVO.getConnectId())
                                            .andTermQueryFilter("isReq", true)
                            ).fetchOnlyOne();
                            if(almIssueEntity.getKey()!=null){
                                return new AlmIssueWithRequirementVO(almIssueEntity);
                            }
                        }

                        return new AlmIssueWithRequirementVO(reqIssueEntity);

                    }))
                    .serverInfoVO(serverInfoVO)
                    .build();
                }).filter(a -> a.getAlmIssueWithRequirementVO() != null).toList();
    }

    private AlmIssueWithRequirementVO isSelfCreatedIssue(AlmIssueEntity almIssueEntity, String rootParentKey) {
        AlmIssueWithRequirementVO reqDTO = new AlmIssueWithRequirementVO(almIssueEntity, rootParentKey);
        AlmIssueEntity recentDocByRecentId = esCommonRepositoryWrapper.findRecentDocByRecentId(reqDTO.recentId());
        if (rootParentKey != null && (recentDocByRecentId.getKey() == null || recentDocByRecentId.izReqFalse())) {
            return reqDTO;
        }
        return null;
    }

    private String findRootParentKey(Map<String, Issue> lruMap, String startKey, RedmineManager redmineManager) {
        String findKey = startKey;
        String parentKey = null;

        while (findKey != null) {
            try {
                Issue issue = issueList(lruMap, IssueDTO.builder().key(findKey).build(), redmineManager);
                Integer currentParentId = issue.getParentId();
                if (currentParentId != null) {
                    parentKey = String.valueOf(currentParentId);
                    findKey = parentKey;
                } else {
                    break;
                }
            } catch (RedmineException e) {
                break;
            }
        }
        return parentKey;
    }

    private IssueDTO createIssueDTO(Issue issue) {
        return IssueDTO.builder()
                .key(String.valueOf(issue.getId()))
                .fromKey(String.valueOf(issue.getId()))
                .build();
    }

    private IssueDTO createIssueParentTraceDTO(Issue issue) {
        return IssueDTO.builder()
                .key(String.valueOf(Optional.ofNullable(issue.getParentId()).orElseGet(issue::getId)))
                .fromKey(String.valueOf(issue.getId()))
                .build();
    }

    private void linkedIssueQueueRegister(Issue issue, Queue<IssueDTO> queue, Set<String> visited) {

        List<IssueRelation> issueRelations = new ArrayList<>(issue.getRelations());

        for (IssueRelation relation : issueRelations) {
            String issueIdKey = String.valueOf(relation.getIssueId());
            if (!visited.contains(issueIdKey)) {
                queue.add(IssueDTO.builder()
                        .key(issueIdKey)
                        .fromKey(String.valueOf(relation.getIssueToId()))
                        .build());
            }
            String issueToIdKey = String.valueOf(relation.getIssueToId());
            if (!visited.contains(issueToIdKey)) {
                queue.add(IssueDTO.builder()
                        .key(issueToIdKey)
                        .fromKey(String.valueOf(relation.getIssueId()))
                        .build());
            }
        }
    }

    private Issue issueList(Map<String,Issue> lruMap, IssueDTO issueDTO, RedmineManager redmineManager) throws RedmineException {

        log.info("key cache:key=={},{}",issueDTO.getKey(),lruMap.get(issueDTO.getKey())!=null);

        Issue issue = lruMap.get(issueDTO.getKey());

        if(issue !=null){
            return issue;
        }else{
            return redmineManager.getIssueManager()
                    .getIssueById(Integer.parseInt(Objects.requireNonNull(issueDTO.getKey())), Include.relations, Include.children , Include.journals);
        }

    }


    private List<Issue> exploreRelatedIssuesByFetchedIssues(List<Issue> issuesFromRedmine, Map<String, Issue> lruMap, RedmineManager redmineManager) {
        Set<String> visited = new HashSet<>();
        Queue<String> queue = new LinkedList<>();
        List<Issue> result = new ArrayList<>(issuesFromRedmine);

        for (Issue issue : issuesFromRedmine) {
            String key = String.valueOf(issue.getId());
            queue.add(key);
            lruMap.put(key, issue);
        }

        while (!queue.isEmpty()) {
            String key = queue.poll();

            if (visited.contains(key) || ObjectUtils.isEmpty(key)) {
                continue;
            }
            visited.add(key);

            try {
                IssueDTO issueDTO = IssueDTO.builder().key(key).build();
                Issue issue = issueList(lruMap, issueDTO, redmineManager);
                lruMap.put(String.valueOf(issue.getId()), issue);

                if (!result.contains(issue)) {
                    result.add(issue);
                }

                Integer parentId = issue.getParentId();
                if (parentId != null && !visited.contains(String.valueOf(parentId))) {
                    queue.add(String.valueOf(parentId));
                }

                if (issue.getChildren() != null) {
                    for (Issue child : issue.getChildren()) {
                        String childKey = String.valueOf(child.getId());
                        if (!visited.contains(childKey)) {
                            queue.add(childKey);
                        }
                    }
                }

                if (issue.getRelations() != null) {
                    for (IssueRelation relation : issue.getRelations()) {
                        String issueIdKey = String.valueOf(relation.getIssueId());
                        String issueToIdKey = String.valueOf(relation.getIssueToId());

                        if (!visited.contains(issueIdKey)) {
                            queue.add(issueIdKey);
                        }
                        if (!visited.contains(issueToIdKey)) {
                            queue.add(issueToIdKey);
                        }
                    }
                }

            } catch (NotFoundException e) {
                log.info("exploreRelatedIssuesAndCache - not found key: {}", key);
            } catch (RedmineException e) {
                log.warn("exploreRelatedIssuesAndCache - error fetching issue key: {}", key, e);
            }
        }
        return result;
    }

    private User userList(Set<User> userSet, Integer assigneeId, RedmineManager redmineManager) {

        Optional<User> optionalUser = userSet.stream()
                .filter(a -> a.getId().equals(assigneeId))
                .findFirst();

        if (optionalUser.isPresent()) {
            return optionalUser.get();
        } else {
            return Optional.ofNullable(assigneeId)
                    .map(a -> redmineUtil.getUserInfo(redmineManager, String.valueOf(a))).orElse(null);
        }

    }

    private IssueFieldData 레드마인이슈_지라이슈필드_데이터_매핑(Issue 이슈, String 기본경로) {

        IssueFieldData issueFieldData = new IssueFieldData();

        Optional.ofNullable(이슈.getProjectId())
                .map(아이디 -> String.valueOf(아이디))
                .ifPresent(아이디 -> {
                    String 프로젝트_경로 = 기본경로 + redmineApi.replaceID(redmineApi.getEndpoint().getProject(), 아이디);
                    issueFieldData.setProject(new ProjectDTO(프로젝트_경로, 아이디, 아이디, 이슈.getProjectName()));
                });

        Optional.ofNullable(이슈.getTracker().getId())
                .map(아이디 -> String.valueOf(아이디))
                .ifPresent(아이디 -> {
                    String 이슈유형_경로 = 기본경로 + redmineApi.replaceID(redmineApi.getEndpoint().getIssuetype(), 아이디);
                    issueFieldData.setIssuetype(new IssueTypeDTO(이슈유형_경로, 아이디, 이슈.getTracker().getName()));
                });

        Optional.ofNullable(이슈.getPriorityId())
                .map(아이디 -> String.valueOf(아이디))
                .ifPresent(아이디 -> {
                    String 우선순위_경로 = 기본경로 + redmineApi.replaceID(redmineApi.getEndpoint().getPriority(), 아이디);
                    issueFieldData.setPriority(new IssuePriorityDTO(우선순위_경로, 아이디, 이슈.getPriorityText()));
                });

        Optional.ofNullable(이슈.getStatusId())
                .map(아이디 -> String.valueOf(아이디))
                .ifPresent(아이디 -> {
                    String 이슈상태_경로 = 기본경로 + redmineApi.replaceID(redmineApi.getEndpoint().getIssuestatus(), 아이디);
                    issueFieldData.setStatus(new IssueStatusDTO(이슈상태_경로, 아이디, 이슈.getStatusName()));
                });

        Optional.ofNullable(이슈.getAuthorId())
                .map(아이디 -> String.valueOf(아이디))
                .ifPresent(아이디 -> {
                    UserData 사용자_데이터 =
                            UserData.builder()
                                    .accountId(아이디)
                                    .displayName(이슈.getAuthorName())
                                    .build();
                    issueFieldData.setCreator(사용자_데이터);
                    issueFieldData.setReporter(사용자_데이터);
                });

        Optional.ofNullable(이슈.getCreatedOn())
                .map(this::날짜변환)
                .ifPresent(issueFieldData::setCreated);

        Optional.ofNullable(이슈.getUpdatedOn())
                .map(this::날짜변환)
                .ifPresent(issueFieldData::setUpdated);

        Optional.ofNullable(이슈.getClosedOn())
                .map(this::날짜변환)
                .ifPresent(issueFieldData::setResolutiondate);

        issueFieldData.setSummary(이슈.getSubject());

        return issueFieldData;
    }

    private String 날짜변환(Date 원본_날짜) {
        if (원본_날짜 == null) {
            return null;
        }
        return DateTimeFormatter.ISO_INSTANT.format(원본_날짜.toInstant());
    }

    private String getMappingCategory(ServerInfoVO serverInfoVO, IssueFieldData IssueFieldData) {
        return categoryMappingService.getMappingCategory(serverInfoVO, IssueFieldData.getAlmStatusId());
    }
}
