package com.honey.honey.util;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * Utility for IP address handling with reverse proxy support.
 */
@Slf4j
public class IpUtils {

    /**
     * Extracts client IP address from request, handling reverse proxies.
     * Priority:
     * 1. X-Forwarded-For header (first IP in chain)
     * 2. Forwarded header (RFC 7239)
     * 3. X-Real-IP header
     * 4. Remote address (fallback)
     * 
     * @param request HTTP request
     * @return Client IP address as string (normalized), or null if not available
     */
    public static String getClientIp(HttpServletRequest request) {
        // 1) Standard proxy header (most common)
        String xff = request.getHeader("X-Forwarded-For");
        if (xff != null && !xff.isBlank() && !"unknown".equalsIgnoreCase(xff)) {
            // Format: "client, proxy1, proxy2" - take first IP
            String first = xff.split(",")[0].trim();
            if (!first.isEmpty()) {
                return normalize(first);
            }
        }

        // 2) RFC 7239 Forwarded header (optional, more complex)
        String forwarded = request.getHeader("Forwarded");
        if (forwarded != null && !forwarded.isBlank()) {
            String ip = extractFromForwardedHeader(forwarded);
            if (ip != null) {
                return normalize(ip);
            }
        }

        // 3) X-Real-IP header (some proxies use this)
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isBlank() && !"unknown".equalsIgnoreCase(xRealIp)) {
            return normalize(xRealIp);
        }

        // 4) Fallback to remote address
        return normalize(request.getRemoteAddr());
    }

    /**
     * Extracts IP from RFC 7239 Forwarded header.
     * Format: "for=192.0.2.60;proto=http;by=203.0.113.43"
     */
    private static String extractFromForwardedHeader(String forwarded) {
        try {
            // Look for "for=" pattern
            int forIndex = forwarded.toLowerCase().indexOf("for=");
            if (forIndex == -1) {
                return null;
            }

            // Extract value after "for="
            String afterFor = forwarded.substring(forIndex + 4);
            // Value can be quoted or unquoted, and may contain port
            // Stop at semicolon, comma, quote, or space
            StringBuilder ipBuilder = new StringBuilder();
            for (int i = 0; i < afterFor.length(); i++) {
                char c = afterFor.charAt(i);
                if (c == ';' || c == ',' || c == ' ' || c == '"') {
                    break;
                }
                ipBuilder.append(c);
            }

            String candidate = ipBuilder.toString().trim();
            return candidate.isEmpty() ? null : candidate;
        } catch (Exception e) {
            log.debug("Failed to parse Forwarded header: {}", forwarded, e);
            return null;
        }
    }

    /**
     * Normalizes IP address string:
     * - Removes brackets from IPv6 (e.g., "[2001:db8::1]" -> "2001:db8::1")
     * - Removes port number if present (e.g., "192.168.1.1:8080" -> "192.168.1.1")
     * - Trims whitespace
     * 
     * @param ip IP address string (may contain brackets, port, etc.)
     * @return Normalized IP address, or null if input is null
     */
    private static String normalize(String ip) {
        if (ip == null) {
            return null;
        }

        ip = ip.trim();
        if (ip.isEmpty()) {
            return null;
        }

        // Handle IPv6 in brackets, e.g., "[2001:db8::1]"
        if (ip.startsWith("[") && ip.endsWith("]")) {
            ip = ip.substring(1, ip.length() - 1);
        }

        // Handle "ip:port" formats
        // For IPv4: "192.168.1.1:8080" -> "192.168.1.1"
        // For IPv6: "2001:db8::1:8080" is ambiguous, but we try to detect port
        int lastColon = ip.lastIndexOf(':');
        if (lastColon > 0) {
            // Check if part after last colon looks like a port number
            String afterColon = ip.substring(lastColon + 1);
            if (afterColon.chars().allMatch(Character::isDigit)) {
                // Likely a port number
                String beforeColon = ip.substring(0, lastColon);
                // For IPv4, we can safely remove port
                if (beforeColon.contains(".") && !beforeColon.contains("::")) {
                    ip = beforeColon;
                }
                // For IPv6, we need to be more careful - only remove if it's clearly a port
                // IPv6 addresses can contain colons, so we check if it's a valid IPv6 format
                // For simplicity, if it contains "::" or more than 2 colons, assume it's IPv6 and don't remove
            }
        }

        return ip;
    }
    
    /**
     * Converts IP address string to varbinary(16) format for database storage.
     * Supports both IPv4 and IPv6.
     * 
     * @param ipAddress IP address as string
     * @return IP address as byte array (4 bytes for IPv4, 16 bytes for IPv6), or null if invalid
     */
    public static byte[] ipToBytes(String ipAddress) {
        if (ipAddress == null || ipAddress.isEmpty()) {
            return null;
        }
        
        try {
            InetAddress inetAddress = InetAddress.getByName(ipAddress);
            return inetAddress.getAddress();
        } catch (UnknownHostException e) {
            log.warn("Failed to parse IP address: {}", ipAddress, e);
            return null;
        }
    }
    
    /**
     * Converts IP address from varbinary(16) format to string.
     * 
     * @param ipBytes IP address as byte array
     * @return IP address as string, or null if invalid
     */
    public static String bytesToIp(byte[] ipBytes) {
        if (ipBytes == null || ipBytes.length == 0) {
            return null;
        }
        
        try {
            InetAddress inetAddress = InetAddress.getByAddress(ipBytes);
            return inetAddress.getHostAddress();
        } catch (UnknownHostException e) {
            log.warn("Failed to convert IP bytes to string", e);
            return null;
        }
    }
}

