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

import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.apache.hc.client5.http.ConnectTimeoutException;
import org.apache.hc.core5.http.HttpRequest;

import com.eactive.eai.common.util.Logger;

import lombok.Getter;

@Getter
public class RetryPolicy {
	public static final String RETRY_MAX_RETRIES = "RETRY_MAX_RETRIES";
	public static final String RETRY_BACKOFF_MS = "RETRY_BACKOFF_MS";
	public static final String RETRY_STATUS_CODES = "RETRY_STATUS_CODES";
	public static final String RETRY_ON_CONNECT_FAIL = "RETRY_ON_CONNECT_FAIL";
	public static final String RETRY_ON_TIMEOUT = "RETRY_ON_TIMEOUT";
	public static final String RETRY_IDEMPOTENT_ONLY = "RETRY_IDEMPOTENT_ONLY";

	protected static Logger log = Logger.getLogger(Logger.LOGGER_ADAPTER);
	private int maxRetries;
	private long backoffMs;
	private Set<Integer> retryCodes;
	private boolean retryOnConnectFail;
	private boolean retryOnTimeout;
	private boolean idempotentOnly;
	private static Set<String> IDEMPOTENT_METHODS;
	static {
		Set<String> methods = new HashSet<>();
		methods.add("GET");
		methods.add("HEAD");
		methods.add("OPTIONS");
//		methods.add("PUT");
//		methods.add("DELETE");
		IDEMPOTENT_METHODS = Collections.unmodifiableSet(methods);
	}

	private RetryPolicy(int maxRetries, long backoffMs, Set<Integer> retryCodes, boolean retryOnConnectFail,
			boolean retryOnTimeout, boolean idempotentOnly) {
		this.maxRetries = maxRetries;
		this.backoffMs = backoffMs;
		this.retryCodes = Collections.unmodifiableSet(retryCodes);
		this.retryOnConnectFail = retryOnConnectFail;
		this.retryOnTimeout = retryOnTimeout;
		this.idempotentOnly = idempotentOnly;
	}

	public static RetryPolicy from(Properties props) {
		Set<Integer> codes = new HashSet<>();
		String statusCodes = props.getProperty(RETRY_STATUS_CODES);
		if (statusCodes != null && !statusCodes.trim().isEmpty()) {
			for (String code : statusCodes.split(",")) {
				String trimmed = code.trim();
				if (!trimmed.isEmpty()) {
					codes.add(Integer.parseInt(trimmed));
				}
			}
		}

		return new RetryPolicy(getInt(props, RETRY_MAX_RETRIES, 0), getLong(props, RETRY_BACKOFF_MS, 1000L), codes,
				getBoolean(props, RETRY_ON_CONNECT_FAIL, false), getBoolean(props, RETRY_ON_TIMEOUT, false),
				getBoolean(props, RETRY_IDEMPOTENT_ONLY, false));
	}

	// ----------------------------------------------------------------
	// Properties 파싱 헬퍼 (키 누락 / 타입 오류 방어)
	// ----------------------------------------------------------------

	private static int getInt(Properties props, String key, int defaultValue) {
		String value = props.getProperty(key);
		if (value == null || value.trim().isEmpty())
			return defaultValue;
		try {
			return Integer.parseInt(value.trim());
		} catch (NumberFormatException e) {
			log.warn("RetryPolicy 설정값 파싱 실패 [key={}, value={}] → 기본값({}) 사용", key, value, defaultValue);
			return defaultValue;
		}
	}

	private static long getLong(Properties props, String key, long defaultValue) {
		String value = props.getProperty(key);
		if (value == null || value.trim().isEmpty())
			return defaultValue;
		try {
			return Long.parseLong(value.trim());
		} catch (NumberFormatException e) {
			log.warn("RetryPolicy 설정값 파싱 실패 [key={}, value={}] → 기본값({}) 사용", key, value, defaultValue);
			return defaultValue;
		}
	}

	// "Y"/"y" → true, 그 외 → false
	private static boolean getBoolean(Properties props, String key, boolean defaultValue) {
		String value = props.getProperty(key);
		if (value == null || value.trim().isEmpty())
			return defaultValue;
		return "Y".equalsIgnoreCase(value.trim());
	}

	public boolean isDisabled() {
		return maxRetries <= 0;
	}

	public boolean shouldRetryOnStatus(HttpRequest request, int status) {
	    if (idempotentOnly && !isIdempotent(request)) return false;
	    return retryCodes.contains(status);
	}

	public boolean shouldRetryOnException(Exception e) {
	    if (e instanceof ConnectTimeoutException) return retryOnConnectFail;
	    if (e instanceof SocketTimeoutException)  return retryOnTimeout;
	    if (e instanceof NoRouteToHostException)  return retryOnConnectFail;
	    return false;
	}

	private boolean isIdempotent(HttpRequest request) {
		return IDEMPOTENT_METHODS.contains(request.getMethod());
	}
}
