package com.caliverse.admin.domain.service;

import com.caliverse.admin.domain.entity.web3.ResponseErrorCode;
import com.caliverse.admin.domain.response.Web3Response;
import com.caliverse.admin.global.common.code.CommonCode;
import com.caliverse.admin.global.common.code.ErrorCode;
import com.caliverse.admin.global.common.exception.RestApiException;
import com.caliverse.admin.global.configuration.RestConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Arrays;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class Web3Service {

    private final RestTemplate restTemplate;
    private final RestConfig restConfig;
    private final ObjectMapper objectMapper;
    private final RetryTemplate retryTemplate;

    public <T> Web3Response<T> get(String endpoint, Map<String, String> params, Class<T> responseType){
        String urlParams = addQueryParams(endpoint, params);
        return callWeb3Api(urlParams, HttpMethod.GET, null, responseType);
    }

    public <T> Web3Response<T> post(String endpoint, Object request, Class<T> responseType){
        return callWeb3Api(endpoint, HttpMethod.POST, request, responseType);
    }

    public <T> Web3Response<T> callWeb3Api(String endpoint, HttpMethod method, Object requestBody, Class<T> responseType){
        try{
            return retryTemplate.execute(context -> {
               try{
                   String url = UriComponentsBuilder.fromHttpUrl(restConfig.getUrl() + endpoint).build().toUriString();
                   log.info("callWeb3Api Call URL: {}, Param: {}, body: {}", restConfig.getUrl(), endpoint, requestBody);

                   HttpHeaders headers = new HttpHeaders();
                   headers.setContentType(MediaType.APPLICATION_JSON);

                   HttpEntity<?> entity = requestBody != null ?
                           new HttpEntity<>(requestBody, headers) :
                           new HttpEntity<>(headers);

                   ResponseEntity<JsonNode> response = restTemplate.exchange(url, method, entity, JsonNode.class);

                   log.info("callWeb3Api Call response: {}", response);
                   Web3Response<T> web3Response = parseResponse(response.getBody(), responseType);

                   if(!web3Response.isSuccess() && isRetryableError(web3Response.getCode())){
                       throw new RestApiException(
                               CommonCode.ERROR.getHttpStatus(),
                               "Retriable error: " + web3Response.getCode()
                       );
                   }

                   return web3Response;
               }catch (Exception e) {
                   log.warn("API call failed, retry {} of {}: {}",
                           context.getRetryCount() + 1, restConfig.getMaxRetry(), e.getMessage());

                   // RestApiException이 아닌 경우 변환
                   if (!(e instanceof RestApiException)) {
                       throw new RestApiException(
                               CommonCode.ERROR.getHttpStatus(),
                               ErrorCode.ERROR_API_CALL.getMessage()
                       );
                   }
                   throw e;
               }
            }, context -> {
                log.error("All retry attempts failed for endpoint: {}", endpoint);
                throw new RestApiException(CommonCode.ERROR.getHttpStatus(),
                        ErrorCode.ERROR_API_CALL.getMessage());
            });

        }catch(Exception e){
            log.error("Web3 API Call failed: endpoint={}, method={}", endpoint, method, e);
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_API_CALL.getMessage());
        }
    }

    private <T> Web3Response<T> parseResponse(JsonNode root, Class<T> responseType){
        try {
            Web3Response<T> web3Response = new Web3Response<>();
            boolean success = root.has("success") && root.get("success").asBoolean();
            web3Response.setSuccess(success);

            if (root.has("code")) {
                web3Response.setCode(root.get("code").asText());
            }

            if (success && root.has("data")) {
                JsonNode dataNode = root.get("data");
                if (!dataNode.isNull()) {
                    T data = objectMapper.treeToValue(dataNode, responseType);
                    web3Response.setData(data);
                }
            }
            log.info("parseResponse Set response Code: {}, Data: {}", web3Response.getCode(), web3Response.getData());

            return web3Response;
        } catch (JsonProcessingException e) {
            log.error("API Response Failed to parse response", e);
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_API_CALL.getMessage());
        }
    }

    private String addQueryParams(String endpoint, Map<String, String> queryParams) {
        if (queryParams == null || queryParams.isEmpty()) {
            return endpoint;
        }

        UriComponentsBuilder builder = UriComponentsBuilder.fromPath(endpoint);
        queryParams.forEach(builder::queryParam);
        return builder.build().toString();
    }

    private boolean isRetryableError(String errorCode) {
        ResponseErrorCode responseErrorCode = ResponseErrorCode.fromCode(errorCode).get();
        // 재시도할 에러 코드 정의
        return Arrays.asList(
                ResponseErrorCode.RET_PWRH_001
        ).contains(responseErrorCode);
    }
}
