package com.eactive.ext.kjb.obp;

import com.eactive.ext.kjb.common.KjbProperty;
import com.eactive.ext.kjb.common.KjbPropertyHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.Timeout;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.*;


@Slf4j
public class ObpClient {
    private static volatile ObpClient instance;

    private ObpClient() {
        // 싱글톤
    }

    public static ObpClient getInstance() {
        if (instance == null) {
            synchronized (ObpClient.class) {
                if (instance == null) {
                    instance = new ObpClient();
                }
            }
        }
        return instance;
    }

    private static KjbProperty getProperty() {
        return KjbPropertyHolder.get();
    }

    /**
     * 실제 모드 여부 (URL이 설정되어 있는지 확인)
     */
    public boolean isRealMode() {
        return StringUtils.isNotEmpty(getProperty().getObpUrl());
    }

    private RequestConfig createRequestConfig() {
        KjbProperty prop = getProperty();
        return RequestConfig.custom()
                .setConnectTimeout(Timeout.ofSeconds(prop.getObpConnectionTimeout()))
                .setResponseTimeout(Timeout.ofSeconds(prop.getObpResponseTimeout()))
                .build();
    }

    /**
     * GET 요청
     * @param path API 경로 (예: /api/customer/...)
     * @return ObpResponse
     */
    public ObpResponse get(String path) {
        return get(path, null);
    }

    /**
     * GET 요청 (추가 헤더 포함)
     * @param path API 경로
     * @param additionalHeaders 추가 헤더
     * @return ObpResponse
     */
    public ObpResponse get(String path, Map<String, String> additionalHeaders) {
        String url = getProperty().getObpUrl() + path;
        HttpGet httpGet = new HttpGet(url);
        applyDefaultHeaders(httpGet);
        applyAdditionalHeaders(httpGet, additionalHeaders);
        httpGet.setConfig(createRequestConfig());

        return execute(httpGet);
    }

    /**
     * POST 요청 (JSON body)
     * @param path API 경로
     * @param jsonBody JSON 문자열
     * @return ObpResponse
     */
    public ObpResponse post(String path, String jsonBody) {
        return post(path, jsonBody, null);
    }

    /**
     * POST 요청 (JSON body, 추가 헤더 포함)
     * @param path API 경로
     * @param jsonBody JSON 문자열
     * @param additionalHeaders 추가 헤더
     * @return ObpResponse
     */
    public ObpResponse post(String path, String jsonBody, Map<String, String> additionalHeaders) {
        String url = getProperty().getObpUrl() + path;
        HttpPost httpPost = new HttpPost(url);
        applyDefaultHeaders(httpPost);
        applyAdditionalHeaders(httpPost, additionalHeaders);
        httpPost.setConfig(createRequestConfig());

        if (StringUtils.isNotEmpty(jsonBody)) {
            httpPost.setEntity(EntityBuilder.create()
                    .setText(jsonBody)
                    .setContentType(ContentType.APPLICATION_JSON)
                    .build());
        }

        return execute(httpPost);
    }

    private void applyDefaultHeaders(HttpUriRequestBase request) {
        KjbProperty prop = getProperty();
        request.setHeader("Content-Type", "application/json; charset=utf-8");
        request.setHeader(prop.getObpTrustSystemKey(), prop.getObpTrustSystemValue());
        request.setHeader(prop.getObpPartnerCodeKey(), prop.getObpPartnerCodeValue());
    }

    private void applyAdditionalHeaders(HttpUriRequestBase request, Map<String, String> additionalHeaders) {
        if (additionalHeaders != null) {
            additionalHeaders.forEach(request::setHeader);
        }
    }

    private ObpResponse execute(HttpUriRequestBase request) {
        if (!isRealMode()) {
            log.warn("[OBP] URL 미설정으로 실제 호출하지 않음. 요청: {} {}", request.getMethod(), request.getRequestUri());
            ObpResponse mockResponse = new ObpResponse();
            mockResponse.setStatus(200);
            mockResponse.setBody("{}");
            return mockResponse;
        }

        ExecutorService executor = Executors.newSingleThreadExecutor();

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            log.debug("[OBP] 요청: {} {}", request.getMethod(), request.getRequestUri());

            Future<ObpResponse> future = executor.submit(() ->
                    httpClient.execute(request, response -> {
                        ObpResponse obpResponse = new ObpResponse();
                        obpResponse.setStatus(response.getCode());
                        obpResponse.setBody(EntityUtils.toString(response.getEntity(), "UTF-8"));

                        Arrays.asList(response.getHeaders()).forEach(header ->
                                obpResponse.getHeaders().put(header.getName(), header.getValue()));

                        return obpResponse;
                    })
            );

            ObpResponse response = future.get(getProperty().getObpTimeout(), TimeUnit.SECONDS);

            if (log.isDebugEnabled()) {
                log.debug("[OBP] 응답: status={}, body={}", response.getStatus(), response.getBody());
            }

            return response;

        } catch (InterruptedException | ExecutionException | TimeoutException | java.io.IOException e) {
            log.error("[OBP] 호출 실패: {} {}", request.getMethod(), request.getRequestUri(), e);
            ObpResponse errorResponse = new ObpResponse();
            errorResponse.setStatus(-1);
            errorResponse.setBody(e.getMessage());
            return errorResponse;
        } finally {
            executor.shutdown();
        }
    }
}
