package com.arms.api.util.alm;

import com.arms.api.issue.almapi.model.vo.cloudjiraspec.CloudJiraIssueRawDataVO;
import com.arms.api.issue.almapi.model.vo.CloudJiraIssues;
import com.arms.api.util.alm.dto.CloudJiraIssueCreationFieldMetadata;
import com.arms.api.util.errors.ErrorLogUtil;
import com.arms.api.util.exception.Retryable429Exception;
import com.arms.api.util.response.CommonResponse;
import com.arms.config.WebClientBuilder;
import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@AllArgsConstructor
public class JiraUtil {

    private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);

    private final JiraApi jiraApi;

    private final WebClientBuilder webClientBuilder;

    public WebClient createJiraCloudCommunicator(String uri, String email, String apiToken) {

        return webClientBuilder.builder()
                .baseUrl(uri)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader("Authorization", "Basic " + getBase64Credentials(email, apiToken))
                .build();
    }

    public JiraRestClient createJiraOnPremiseCommunicator(String jiraUrl, String jiraID, String jiraPass) {

        final AsynchronousJiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();

        try {
            return factory.createWithBasicHttpAuthentication(new URI(jiraUrl), jiraID, jiraPass);
        }
        catch (URISyntaxException e) {
            String errorMessage = ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(),
                    String.format("%s :: [%s] :: 온프레미스_통신기_생성 중 오류", jiraUrl, jiraID));
            throw new IllegalArgumentException(errorMessage);
        }
    }

    private String getBase64Credentials(String jiraID, String jiraPass) {
        String credentials = jiraID + ":" + jiraPass;
        return new String(Base64.getEncoder().encode(credentials.getBytes()));
    }

    public <T> Mono<T> get(WebClient webClient, String uri, Class<T> responseType) {

        return webClient.get()
                .uri(uri)
                .retrieve()
                .bodyToMono(responseType);
    }

    public <T> Mono<T> get(WebClient webClient, String uri, ParameterizedTypeReference<T> elementTypeRef) {

        return webClient.get()
                .uri(uri)
                .retrieve()
                .bodyToMono(elementTypeRef);
    }

    public <T> T getWithRawData(WebClient webClient, String uri, Class<T> responseType) {

        try {
            log.info("cloud request uri:{}", uri);

            String rawData = webClient.get()
                    .uri(uri)
                    .exchangeToMono(response -> {
                        if (response.statusCode().value() == 429) {
                            String retryAfter = response.headers().asHttpHeaders().getFirst("Retry-After");
                            long delay = retryAfter != null ? Long.parseLong(retryAfter) : 3;
                            log.warn("429 error, retry after {} seconds", delay);
                            return Mono.error(new Retryable429Exception(delay));
                        }
                        return response.bodyToMono(String.class);
                    })
                    .retryWhen(
                            Retry.max(5)
                                .filter(ex -> ex instanceof Retryable429Exception)
                                .doBeforeRetry(signal -> {
                                    long delay = ((Retryable429Exception) signal.failure()).getDelaySeconds();
                                    try { Thread.sleep(delay * 1000); } catch (InterruptedException ignored) {}
                                })
                    )
                    .block(DEFAULT_TIMEOUT);

            if (rawData == null) {
                throw new RuntimeException("WebClient 호출 결과가 null입니다.");
            }
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            T mappedData = objectMapper.readValue(rawData, responseType);

            if (mappedData instanceof CloudJiraIssueRawDataVO) {
                ((CloudJiraIssueRawDataVO) mappedData).applyRawData(rawData);
            } else if (mappedData instanceof CloudJiraIssues) {
                CloudJiraIssues 조회_데이터 = (CloudJiraIssues) mappedData;

                JsonNode rootNode = objectMapper.readTree(rawData);
                JsonNode issuesNode = rootNode.get("issues");

                if (issuesNode != null && 조회_데이터.getIssues() != null) {
                    for (int i = 0; i < 조회_데이터.getIssues().size(); i++) {
                        CloudJiraIssueRawDataVO issue = 조회_데이터.getIssues().get(i);
                        String issueRawData = issuesNode.get(i).toString();
                        issue.applyRawData(issueRawData);
                    }
                }
            }

            return mappedData;
        } catch (Exception e) {
            log.error("ROW DATA JSON 매핑 중 오류 발생: {}", e.getMessage(), e);
            throw new RuntimeException("ROW DATA JSON 매핑 중 오류 발생", e);
        }
    }


    public <T> Mono<T> post(WebClient webClient, String uri, Object requestBody, Class<T> responseType) {

        return webClient.post()
                .uri(uri)
                .body(BodyInserters.fromValue(requestBody))
                .retrieve()
                .bodyToMono(responseType);
    }

    public <T> Mono<T> put(WebClient webClient, String uri, Object requestBody, Class<T> responseType) {

        return webClient.put()
                .uri(uri)
                .body(BodyInserters.fromValue(requestBody))
                .retrieve()
                .bodyToMono(responseType);
    }

    public <T> Mono<T> delete(WebClient webClient, String uri, Class<T> responseType) {

        return webClient.delete()
                .uri(uri)
                .retrieve()
                .bodyToMono(responseType);
    }

    public CommonResponse.ApiResult<?> executePost(WebClient webClient, String uri, Object requestBody) {

        Mono<ResponseEntity<Void>> response = webClient.post()
                .uri(uri)
                .body(BodyInserters.fromValue(requestBody))
                .retrieve()
                .toEntity(Void.class);

        return response.map(entity -> {
                    if (entity.getStatusCode() == HttpStatus.NO_CONTENT) {
                        return CommonResponse.success("OK");
                    } else {
                        return CommonResponse.error("Fail", HttpStatus.BAD_REQUEST);
                    }
                })
                .onErrorResume(WebClientResponseException.class, e -> Mono.just(CommonResponse.error(e, e.getStatusCode())))
                .onErrorResume(Exception.class, e -> Mono.just(CommonResponse.error(e, HttpStatus.BAD_REQUEST)))
                .block(DEFAULT_TIMEOUT);
    }

    public CommonResponse.ApiResult<?> executePut(WebClient webClient, String uri, Object requestBody) {

        Mono<ResponseEntity<Void>> response = webClient.put()
                .uri(uri)
                .body(BodyInserters.fromValue(requestBody))
                .retrieve()
                .toEntity(Void.class);

        return response.map(entity -> {
                    if (entity.getStatusCode() == HttpStatus.NO_CONTENT) {
                        return CommonResponse.success("OK");
                    } else {
                        return CommonResponse.error("Fail", HttpStatus.BAD_REQUEST);
                    }
                })
                .onErrorResume(WebClientResponseException.class, e -> Mono.just(CommonResponse.error(e, e.getStatusCode())))
                .onErrorResume(Exception.class, e -> Mono.just(CommonResponse.error(e, HttpStatus.BAD_REQUEST)))
                .block(DEFAULT_TIMEOUT);
    }

    public CommonResponse.ApiResult<?> executeDelete(WebClient webClient, String uri) {

        Mono<ResponseEntity<Void>> response = webClient.delete()
                .uri(uri)
                .retrieve()
                .toEntity(Void.class);

        return response.map(entity -> {
                    if (entity.getStatusCode() == HttpStatus.NO_CONTENT) {
                        return CommonResponse.success("OK");
                    } else {
                        return CommonResponse.error("Fail", HttpStatus.BAD_REQUEST);
                    }
                })
                .onErrorResume(WebClientResponseException.class, e -> Mono.just(CommonResponse.error(e, e.getStatusCode())))
                .onErrorResume(Exception.class, e -> Mono.just(CommonResponse.error(e, HttpStatus.BAD_REQUEST)))
                .block(DEFAULT_TIMEOUT);
    }

    public Map<String, CloudJiraIssueCreationFieldMetadata.FieldMetadata> checkFieldMetadata(WebClient webClient, String 프로젝트_아이디, String 이슈유형_아이디) {
        String 필드확인endpoint = jiraApi.프로젝트키_대체하기(
                    jiraApi.getEndpoint().getIssue().getCreatemeta(), 프로젝트_아이디);
        필드확인endpoint = jiraApi.이슈유형키_대체하기(필드확인endpoint, 이슈유형_아이디);

        int 검색_시작_지점 = 0;
        int 최대_검색수 = jiraApi.getParameter().getMaxResults();
        boolean isLast = false;

        List<CloudJiraIssueCreationFieldMetadata.FieldMetadata> 메타데이터_목록 = new ArrayList<>(); // 이슈 저장

        CloudJiraIssueCreationFieldMetadata cloudJiraIssueCreationFieldMetadata;
        try {
            while (!isLast) {
                String endpoint = 필드확인endpoint +
                        "?startAt=" + 검색_시작_지점 + "&maxResults=" + 최대_검색수;

                cloudJiraIssueCreationFieldMetadata
                        = this.get(webClient, endpoint, CloudJiraIssueCreationFieldMetadata.class).block(DEFAULT_TIMEOUT);

                if (cloudJiraIssueCreationFieldMetadata == null) {
                    log.info("클라우드 지라 클라우드 프로젝트 : {}, 이슈유형 : {}, 이슈생성필드_메타데이터 목록이 없습니다."
                            , 프로젝트_아이디, 이슈유형_아이디);
                    return null;
                }
                else if (cloudJiraIssueCreationFieldMetadata.getFields() == null || cloudJiraIssueCreationFieldMetadata.getFields().isEmpty()) {
                    log.info("클라우드 지라 클라우드 프로젝트 : {}, 이슈유형 : {}, " +
                        "이슈생성필드_메타데이터 목록이 없습니다.", 프로젝트_아이디, 이슈유형_아이디);
                    return null;
                }

                메타데이터_목록.addAll(cloudJiraIssueCreationFieldMetadata.getFields());

                if (cloudJiraIssueCreationFieldMetadata.getTotal() == 메타데이터_목록.size()) {
                    isLast = true;
                } else {
                    검색_시작_지점 += 최대_검색수;
                }
            }
        }
        catch (Exception e) {
            log.error("클라우드 지라 프로젝트 : {}, 이슈유형 : {}, 이슈생성필드_메타데이터 확인하기 중 오류"
                    , 프로젝트_아이디, 이슈유형_아이디);
            String 에러로그 = ErrorLogUtil.exceptionLoggingAndReturn(e, this.getClass().getName(), "필드_메타데이터 확인하기");
            throw new IllegalArgumentException(에러로그 + "\n필드 메타데이터 조회 중 오류 :: 프로젝트 :: " + 프로젝트_아이디 +
                                                    " :: 이슈 유형 :: " + 이슈유형_아이디 + " :: 에러 메세지 :: " + 에러로그);
        }

        Map<String, CloudJiraIssueCreationFieldMetadata.FieldMetadata> 필드맵 = 메타데이터_목록.stream()
                .collect(Collectors.toMap(CloudJiraIssueCreationFieldMetadata.FieldMetadata::getFieldId, field -> field));

        return 필드맵;
    }

    public  LocalDate getNextDate(String dateString) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        LocalDate date = LocalDate.parse(dateString, formatter);

        return date.plusDays(1);
    }

}
