package kr.xit.core.spring.util;

import java.io.*;
import java.net.*;
import java.net.http.HttpRequest;
import java.net.http.*;
import java.nio.charset.*;
import java.time.*;
import java.util.*;

import org.apache.commons.lang3.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.*;
import org.springframework.stereotype.*;

import com.fasterxml.jackson.core.type.*;
import com.fasterxml.jackson.databind.*;

import kr.xit.biz.ens.model.cmm.*;
import kr.xit.core.consts.*;
import kr.xit.core.exception.*;
import kr.xit.core.spring.config.support.*;
import kr.xit.core.support.utils.*;
import lombok.*;
import lombok.extern.slf4j.*;

/**
 * <pre>
 * description : react Restfull Config
 * packageName : kr.xit.core.spring.support
 * fileName    : ApiWebClientUtil
 * author      : julim
 * date        : 2023-09-06
 * ======================================================================
 * 변경일         변경자        변경 내용
 * ----------------------------------------------------------------------
 * 2023-09-06    julim       최초 생성
 *
 * </pre>
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ApiHttpClientUtil {
    private static final String AUTH_TYPE_BEARER = "bearer";
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final HttpClient httpClient = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .followRedirects(HttpClient.Redirect.NORMAL)
        .connectTimeout(Duration.ofSeconds(10))
        .build();
    
    /**
     * kakao WebClient 호출 처리
     * 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
     * -> {@link WebClientConfig responseFilter} 에서 처리
     * @param url String
     * @param method HttpMethod
     * @param body Object
     * @param rtnClzz Class<T>
     * @param ensDTO CmmEnsRlaybsnmDTO
     * @return rtnClzz<T>
     */
    public <T> T exchangeKkopay(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {

        Map<String, String> map = new HashMap<>();
        map.put(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
        map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
        map.put(HttpHeaders.AUTHORIZATION,
            String.format("%s %s", Constants.JwtToken.GRANT_TYPE.getCode(), ensDTO.getKakaoAccessToken()));
        map.put(Constants.HeaderName.UUID.getCode(), ensDTO.getKakaoContractUuid());

        return exchange(url, method, body, rtnClzz, map);
    }

    public <T> T exchangeKkopayAsIsCan(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, Map<String, String> headerMap) {
        return exchange(url, method, body, rtnClzz, headerMap);
    }

    /**
     * kakaotalk WebClient 호출 처리
     * 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
     * -> {@link WebClientConfig responseFilter} 에서 처리
     * @param url String
     * @param method HttpMethod
     * @param body Object
     * @param rtnClzz Class<T>
     * @param ensDTO CmmEnsRlaybsnmDTO
     * @return rtnClzz<T>
     */
    public <T> T exchangeKkotalk(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {

        Map<String, String> map = new HashMap<>();
        map.put(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
        map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
        map.put(HttpHeaders.AUTHORIZATION, String.format("KakaoAK %s", ensDTO.getKakaoDealerRestApiKey()));
        map.put(Constants.JwtToken.PARTNER_REST_API_KEY.getCode(), String.format("KakaoAK %s", ensDTO.getKakaoPartnerRestApiKey()));
        map.put(Constants.JwtToken.SETTLE_ID.getCode(), ensDTO.getKakaoSettleId());

        return exchange(url, method, body, rtnClzz, map);
    }

    /**
     * KT-BC WebClient 호출 처리
     * 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
     * -> {@link WebClientConfig responseFilter} 에서 처리
     * @param url String
     * @param method HttpMethod
     * @param body Object
     * @param rtnClzz Class<T>
     * @param ensDTO CmmEnsRlaybsnmDTO
     * @return rtnClzz<T>
     */
    public <T> T exchangeKt(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
        final Map<String,String> headerMap = new HashMap<>();
        headerMap.put(HttpHeaders.AUTHORIZATION, String.format("%s %s", AUTH_TYPE_BEARER, ensDTO.getKtAccessToken()));
        headerMap.put("client-id", ensDTO.getKtClientId());
        headerMap.put("client-tp", ensDTO.getKtClientTp());

        return exchange(url, method, body, rtnClzz, headerMap);
    }

    /**
     * KT-GIBIS WebClient 호출 처리
     * 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
     * -> {@link WebClientConfig responseFilter} 에서 처리
     * @param url String
     * @param method HttpMethod
     * @param body Object
     * @param rtnClzz Class<T>
     * @param ensDTO CmmEnsRlaybsnmDTO
     * @return rtnClzz<T>
     */
    public <T> T exchangeKtGbs(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
        final Map<String,String> headerMap = new HashMap<>();
        headerMap.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        headerMap.put(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", ensDTO.getKtAccessToken()));

        return exchange(url, method, body, rtnClzz, headerMap);
    }

    /**
     * <pre>
     * WebClient 호출 처리
     * GET 요청시 url에 파라메터를 포함해야 함(?key=value&key2=value2)
     * 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
     * -> {@link WebClientConfig responseFilter} 에서 처리
     * @param url call url
     * @param method POST|GET
     * @param body JSON String type
     * @param rtnClzz rtnClzz return type class
     *        (ex: new KkopayDocDTO.DocStatusResponse().getClass())
     * @return T rtnClzz return DTO
     * </pre>
     */
    public <T> T exchange(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final Map<String,String> headerMap) {
        
        if(HttpMethod.POST.equals(method)) {
            final HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .POST(HttpRequest.BodyPublishers.ofString(ObjectUtils.isEmpty(body)? StringUtils.EMPTY: String.valueOf(body)))
                .headers(convertHeadersToVarArgs(headerMap))
                .build();
            
            try {
                final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
                validateSuccess(response);
                return JsonUtils.toObject(response.body(), new TypeReference<T>() {});
                
            } catch (InterruptedException e) {
                throw BizRuntimeException.create(e);
            } catch (IOException e) {
                throw BizRuntimeException.create(e);
            }
            
        }else{
            final HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .GET()
                .headers(convertHeadersToVarArgs(headerMap))
                .build();

            try {
                final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

                validateSuccess(response);
                
                return JsonUtils.toObject(response.body(), new TypeReference<T>() {});
                
            } catch (InterruptedException e) {
                throw BizRuntimeException.create(e);
            } catch (IOException e) {
                throw BizRuntimeException.create(e);
            }
            
        }
    }

    private <T> void validateSuccess(final HttpResponse<T> response) {
        final HttpStatus status = HttpStatus.resolve(response.statusCode());
        if (status == null || status.isError()) {
            //log.warn("URI: {}, STATUS: {}", response.uri(), response.statusCode());
            //throw BizRuntimeException.create("처리실패");
        }
        log.info("URI: {}, STATUS: {}, BODY : {}, ", response.uri(), response.statusCode(), response.body());
    }

    private String[] convertHeadersToVarArgs(Map<String, String> headers) {
        if(ObjectUtils.isEmpty(headers)) return new String[]{};
        List<String> headerList = new ArrayList<>();
        for(Map.Entry<String, String> e : headers.entrySet()){
            headerList.add(e.getKey());
            headerList.add(e.getValue());
        }
        return headerList.toArray(new String[0]);
    }
}

    