package server

import (
	"net"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"warpbox/lib/security"
)

func (app *App) clientIP(ctx *gin.Context) string {
	if ctx == nil || ctx.Request == nil {
		return ""
	}
	remoteIP := remoteAddrIP(ctx.Request)
	trusted, err := security.ParseCIDRList(app.config.TrustedProxyCIDRs)
	if err != nil {
		return remoteIP
	}
	if !remoteIsTrusted(remoteIP, trusted) {
		return remoteIP
	}
	for _, candidate := range headerIPs(ctx.Request.Header) {
		if isPublicIP(candidate) {
			return candidate
		}
	}
	candidates := headerIPs(ctx.Request.Header)
	if len(candidates) > 0 {
		return candidates[0]
	}
	return remoteIP
}

func remoteIsTrusted(remoteIP string, trusted []net.IPNet) bool {
	ip := net.ParseIP(strings.TrimSpace(remoteIP))
	if ip == nil {
		return false
	}
	for _, prefix := range trusted {
		if prefix.Contains(ip) {
			return true
		}
	}
	return false
}

func headerIPs(header http.Header) []string {
	keys := []string{
		"X-Forwarded-For",
		"X-Real-Ip",
		"CF-Connecting-IP",
		"X-Envoy-External-Address",
		"Fly-Client-IP",
	}
	out := make([]string, 0, 4)
	seen := map[string]bool{}
	for _, key := range keys {
		raw := strings.TrimSpace(header.Get(key))
		if raw == "" {
			continue
		}
		for _, part := range strings.Split(raw, ",") {
			ip := normalizeIP(strings.TrimSpace(part))
			if ip == "" || seen[ip] {
				continue
			}
			seen[ip] = true
			out = append(out, ip)
		}
	}
	return out
}

func remoteAddrIP(request *http.Request) string {
	host, _, err := net.SplitHostPort(strings.TrimSpace(request.RemoteAddr))
	if err != nil {
		return normalizeIP(strings.TrimSpace(request.RemoteAddr))
	}
	return normalizeIP(host)
}

func normalizeIP(raw string) string {
	ip := net.ParseIP(strings.TrimSpace(raw))
	if ip == nil {
		return ""
	}
	return ip.String()
}

func isPublicIP(value string) bool {
	ip := net.ParseIP(value)
	if ip == nil || !ip.IsGlobalUnicast() {
		return false
	}
	return !isPrivateOrLoopback(value)
}

func isPrivateOrLoopback(value string) bool {
	ip := net.ParseIP(value)
	if ip == nil {
		return true
	}
	return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
}
