package services

import (
	"context"
	"net"
	"net/http"
	"strings"
)

type clientIPContextKey struct{}

func WithClientIP(r *http.Request, ip string) *http.Request {
	return r.WithContext(context.WithValue(r.Context(), clientIPContextKey{}, ip))
}

func ClientIPFromContext(r *http.Request) (string, bool) {
	ip, ok := r.Context().Value(clientIPContextKey{}).(string)
	return ip, ok && ip != ""
}

// ClientIP resolves the effective client IP. When trustedProxies is empty,
// forwarded headers are trusted for easy reverse-proxy/container defaults.
func ClientIP(remoteAddr, forwardedFor, realIP string, trustedProxies []string) string {
	remoteIP := IPOnly(remoteAddr)
	if len(trustedProxies) == 0 || remoteTrusted(remoteIP, trustedProxies) {
		if ip := firstForwardedIP(forwardedFor); ip != "" {
			return IPOnly(ip)
		}
		if ip := strings.TrimSpace(realIP); ip != "" {
			return IPOnly(ip)
		}
	}
	return remoteIP
}

func IPOnly(remoteAddr string) string {
	host := strings.TrimSpace(remoteAddr)
	if splitHost, _, err := net.SplitHostPort(remoteAddr); err == nil {
		host = splitHost
	}
	return strings.Trim(host, "[]")
}

func IsProtectedProxyIP(ip string, trustedProxies []string) bool {
	parsed := net.ParseIP(IPOnly(ip))
	if parsed == nil {
		return false
	}
	if parsed.IsLoopback() {
		return true
	}
	return remoteTrusted(parsed.String(), trustedProxies)
}

func ProtectedBanTarget(target string, trustedProxies []string) bool {
	normalized, err := NormalizeBanTarget(target)
	if err != nil {
		return false
	}
	if !strings.Contains(normalized, "/") {
		return IsProtectedProxyIP(normalized, trustedProxies)
	}
	_, targetNet, err := net.ParseCIDR(normalized)
	if err != nil {
		return false
	}
	if targetNet.Contains(net.ParseIP("127.0.0.1")) || targetNet.Contains(net.ParseIP("::1")) {
		return true
	}
	for _, trusted := range trustedProxies {
		trusted = strings.TrimSpace(trusted)
		if trusted == "" {
			continue
		}
		if strings.Contains(trusted, "/") {
			if _, trustedNet, err := net.ParseCIDR(trusted); err == nil && networksOverlap(targetNet, trustedNet) {
				return true
			}
			continue
		}
		if ip := net.ParseIP(IPOnly(trusted)); ip != nil && targetNet.Contains(ip) {
			return true
		}
	}
	return false
}

func firstForwardedIP(forwardedFor string) string {
	var fallback string
	for _, part := range strings.Split(forwardedFor, ",") {
		ip := IPOnly(part)
		if net.ParseIP(ip) == nil {
			continue
		}
		if fallback == "" {
			fallback = ip
		}
		if isExternalIP(ip) {
			return ip
		}
	}
	return fallback
}

func remoteTrusted(remoteIP string, trustedProxies []string) bool {
	parsed := net.ParseIP(remoteIP)
	if parsed == nil {
		return false
	}
	for _, trusted := range trustedProxies {
		trusted = strings.TrimSpace(trusted)
		if trusted == "" {
			continue
		}
		if strings.Contains(trusted, "/") {
			if _, network, err := net.ParseCIDR(trusted); err == nil && network.Contains(parsed) {
				return true
			}
			continue
		}
		if ip := net.ParseIP(trusted); ip != nil && ip.Equal(parsed) {
			return true
		}
	}
	return false
}

func isExternalIP(ip string) bool {
	parsed := net.ParseIP(IPOnly(ip))
	return parsed != nil &&
		!parsed.IsLoopback() &&
		!parsed.IsPrivate() &&
		!parsed.IsLinkLocalUnicast() &&
		!parsed.IsLinkLocalMulticast() &&
		!parsed.IsUnspecified()
}

func networksOverlap(a, b *net.IPNet) bool {
	return a.Contains(b.IP) || b.Contains(a.IP)
}
