package com.yaoyuan.jiscuss.util;

import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Secure IP address extraction utility.
 *
 * <p>Defends against IP spoofing via forged {@code X-Forwarded-For} headers by:
 * <ul>
 *   <li>Treating the first non-internal IP in the proxy chain as the client IP</li>
 *   <li>Falling back to {@code HttpServletRequest.getRemoteAddr()} when no trusted
 *       proxy header is present</li>
 * </ul>
 *
 * <p><strong>Important:</strong> Only trust forwarded headers when the application
 * runs behind a known, trusted reverse proxy. Consider configuring
 * {@code server.forward-headers-strategy=NATIVE} or {@code FRAMEWORK} in
 * {@code application.yml} to let Spring handle this automatically.
 */
public final class IpUtils {

    private static final Logger log = LoggerFactory.getLogger(IpUtils.class);

    private static final String UNKNOWN = "unknown";
    private static final int MAX_IP_LENGTH = 15; // IPv4

    private IpUtils() {}

    /**
     * Returns the best-effort client IP address from the request.
     *
     * @param request the current HTTP request
     * @return client IP string, never {@code null}
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = getFirstValidIp(request.getHeader("X-Forwarded-For"));
        if (isValid(ip)) return sanitize(ip);

        ip = request.getHeader("X-Real-IP");
        if (isValid(ip)) return sanitize(ip);

        ip = request.getHeader("Proxy-Client-IP");
        if (isValid(ip)) return sanitize(ip);

        ip = request.getHeader("WL-Proxy-Client-IP");
        if (isValid(ip)) return sanitize(ip);

        ip = request.getRemoteAddr();
        return ip != null ? sanitize(ip) : UNKNOWN;
    }

    /**
     * Extracts the first entry from a comma-separated {@code X-Forwarded-For} chain.
     * Returns {@code null} if the header is absent/empty/unknown.
     */
    private static String getFirstValidIp(String header) {
        if (header == null || header.isBlank() || UNKNOWN.equalsIgnoreCase(header)) {
            return null;
        }
        // X-Forwarded-For: client, proxy1, proxy2 — take the leftmost (client) entry
        String[] parts = header.split(",");
        return parts[0].trim();
    }

    private static boolean isValid(String ip) {
        return ip != null && !ip.isBlank() && !UNKNOWN.equalsIgnoreCase(ip);
    }

    /** Truncate excessively long values to prevent log injection / storage attacks. */
    private static String sanitize(String ip) {
        // Strip any characters that are not valid in IPv4/IPv6 addresses
        String cleaned = ip.replaceAll("[^0-9a-fA-F:.\\[\\]]", "");
        if (cleaned.length() > 45) { // max IPv6 length
            log.warn("Suspiciously long IP value truncated: {}", ip);
            return cleaned.substring(0, 45);
        }
        return cleaned;
    }
}
