package utils

import (
	"net"
	"strings"
)

// ResolveClientIP selects the best client IP from proxy headers and the remote address.
// It prefers a public IP from X-Forwarded-For, then X-Real-IP, and finally the remote IP.
func ResolveClientIP(forwardedFor, realIP, remoteIP string) string {
	forwardedCandidates := splitClientIPs(forwardedFor)
	if ip := firstPublicIP(forwardedCandidates); ip != "" {
		return ip
	}
	if ip := normalizeIP(realIP); ip != "" && !IsPrivateOrReservedIP(ip) {
		return ip
	}
	if ip := normalizeIP(remoteIP); ip != "" && !IsPrivateOrReservedIP(ip) {
		return ip
	}
	if len(forwardedCandidates) > 0 {
		return forwardedCandidates[0]
	}
	if ip := normalizeIP(realIP); ip != "" {
		return ip
	}
	return normalizeIP(remoteIP)
}

// IsPrivateOrReservedIP reports whether the IP is private or from a non-public network range.
func IsPrivateOrReservedIP(raw string) bool {
	ip := net.ParseIP(strings.TrimSpace(raw))
	if ip == nil {
		return false
	}
	if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
		return true
	}
	for _, cidr := range []string{
		"100.64.0.0/10",
		"fc00::/7",
	} {
		_, network, err := net.ParseCIDR(cidr)
		if err == nil && network.Contains(ip) {
			return true
		}
	}
	return false
}

func splitClientIPs(forwardedFor string) []string {
	if strings.TrimSpace(forwardedFor) == "" {
		return nil
	}
	parts := strings.Split(forwardedFor, ",")
	result := make([]string, 0, len(parts))
	for _, part := range parts {
		if ip := normalizeIP(part); ip != "" {
			result = append(result, ip)
		}
	}
	return result
}

func firstPublicIP(candidates []string) string {
	for _, candidate := range candidates {
		if !IsPrivateOrReservedIP(candidate) {
			return candidate
		}
	}
	return ""
}

func normalizeIP(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return ""
	}
	if host, _, err := net.SplitHostPort(raw); err == nil {
		raw = host
	}
	ip := net.ParseIP(raw)
	if ip == nil {
		return ""
	}
	return ip.String()
}
