package com.eactive.eai.adapter.http.client.impl;

import java.io.IOException;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;

import org.apache.hc.client5.http.ConnectTimeoutException;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;

import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.common.util.Logger;


public class DeadlineAwareRetryExecutor {

	protected static Logger log = Logger.getLogger(Logger.LOGGER_ADAPTER);
	
	private final CloseableHttpClient httpClient;
    private final int                connectTimeoutMs;
    private final int                responseTimeoutMs;

    public DeadlineAwareRetryExecutor(CloseableHttpClient httpClient,
    		int connectTimeoutMs,
    		int responseTimeoutMs) {
        this.httpClient        = httpClient;
        this.connectTimeoutMs  = connectTimeoutMs;
        this.responseTimeoutMs = responseTimeoutMs;
    }

    public CloseableHttpResponse execute(HttpUriRequestBase request, HttpContext context, RetryPolicy policy)
            throws IOException {

        // retry 비활성 → 단순 실행
        if (policy.isDisabled()) {
            applyTimeout(request, connectTimeoutMs, responseTimeoutMs);
            return httpClient.execute(request, context);
        }

        long totalDeadlineMs = connectTimeoutMs + responseTimeoutMs;
        long startTime       = System.currentTimeMillis();

        IOException lastException = null;

        for (int attempt = 0; attempt <= policy.getMaxRetries(); attempt++) {

            // ① 남은 시간 계산
            long elapsed     = System.currentTimeMillis() - startTime;
            long remainingMs = totalDeadlineMs - elapsed;

            if (remainingMs <= 0) {
                throw new IOException(
                    String.format("전체 deadline 초과 [elapsed=%dms, deadline=%dms]",
                                  elapsed, totalDeadlineMs),
                    lastException
                );
            }

            // ② 이번 시도의 effective timeout = min(원래 설정, 남은 시간)
            long effectiveConnect  = Math.min(connectTimeoutMs,  remainingMs);
            long effectiveResponse = Math.min(responseTimeoutMs, remainingMs);
            applyTimeout(request, effectiveConnect, effectiveResponse);

            log.debug("attempt={}, remainingMs={}, effectiveConnect={}, effectiveResponse={}",
                      attempt + 1, remainingMs, effectiveConnect, effectiveResponse);

            // ③ 요청 실행
            CloseableHttpResponse response = null;
            boolean doRetry = false;

            try {
				if (attempt > 0) {
					Integer logProcessNo = (Integer) context.getAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
					if (logProcessNo == null || logProcessNo <= 0) {
						logProcessNo = 200;
					}
					logProcessNo++;
					
					context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo);
				}
				
                response = httpClient.execute(request, context);
                int status = response.getCode();

                if (!policy.shouldRetryOnStatus(request, status)) {
                    // 성공 또는 재시도 불필요 → 호출부에서 close 책임
                    log.info("non-retryable response status={}, attempt={}", status, attempt + 1);
                    return response;
                }

                // ④ 마지막 시도였으면 종료
                if (attempt >= policy.getMaxRetries()) {
                    // 최대 재처리에 도달하여, 재시도하지 않고, 응답 객체를 리턴한다.
                    log.info("최대 재시도 횟수 도달 response status={}, attempt={}", status, attempt + 1);
                    return response;
                }
                	
                // 재시도 대상
                log.warn("retryable status={}, attempt={}/{}",
                         status, attempt + 1, policy.getMaxRetries());
                lastException = new IOException("재시도 가능 상태코드: " + status);
                doRetry = true;

            } catch (ConnectTimeoutException e) {
                if (!policy.shouldRetryOnException(e)) throw e;
                log.warn("{} attempt={}/{}", e.getClass().getSimpleName(),
                         attempt + 1, policy.getMaxRetries());
                lastException = e;
                doRetry = true;

            } catch (SocketTimeoutException e) {
                if (!policy.shouldRetryOnException(e)) throw e;
                log.warn("{} attempt={}/{}", e.getClass().getSimpleName(),
                         attempt + 1, policy.getMaxRetries());
                lastException = e;
                doRetry = true;

            } catch (NoRouteToHostException e) {
                if (!policy.shouldRetryOnException(e)) throw e;
                log.warn("{} attempt={}/{}", e.getClass().getSimpleName(),
                         attempt + 1, policy.getMaxRetries());
                lastException = e;
                doRetry = true;

            } finally {
                // 재시도 대상인 경우에만 여기서 close
                // return된 response는 호출부에서 close 책임이므로 제외
                if (doRetry && response != null) {
                    closeQuietly(response);
                }
            }

            // ⑤ 백오프 대기 전 남은 시간 재확인
            long remainingBeforeWait = totalDeadlineMs - (System.currentTimeMillis() - startTime);
            if (remainingBeforeWait <= policy.getBackoffMs()) {
                throw new IOException(
                    String.format("백오프 대기 시간 부족 [remaining=%dms, backoff=%dms]",
                                  remainingBeforeWait, policy.getBackoffMs()),
                    lastException
                );
            }

            try {
                log.debug("backoff sleep {}ms before attempt={}", policy.getBackoffMs(), attempt + 2);
                Thread.sleep(policy.getBackoffMs());
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new IOException("retry 대기 중 interrupt 발생", ie);
            }
        }

        throw new IOException(
            String.format("최대 재시도 횟수 초과 [maxRetries=%d]", policy.getMaxRetries()),
            lastException
        );
    }

    // ----------------------------------------------------------------
    // private helpers
    // ----------------------------------------------------------------
	private void applyTimeout(HttpUriRequestBase request, long connectTimeoutMs, long responseTimeoutMs) {
		RequestConfig existing = request.getConfig(); // configureHttpClient()에서 설정한 값

		RequestConfig.Builder builder = (existing != null) ? RequestConfig.copy(existing) // HttpVersion 등 기존 설정 보존
				: RequestConfig.custom();

		RequestConfig config = builder.setConnectTimeout(connectTimeoutMs, TimeUnit.MILLISECONDS)
				.setResponseTimeout(responseTimeoutMs, TimeUnit.MILLISECONDS).build();

		request.setConfig(config);
		log.info("RequestConfig = {}", config);
	}

    private void closeQuietly(CloseableHttpResponse response) {
        try {
            response.close();
        } catch (IOException e) {
            log.debug("response close 실패 (무시)", e);
        }
    }
}
