package httpmiddleware

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

// ClientIP returns the best-known client IP for a request.
//
// Forwarded headers are only trusted when the immediate remote address belongs
// to an explicitly trusted proxy listed in TRUST_PROXY_IPS and/or
// TRUST_PROXY_CIDRS.
func ClientIP(r *http.Request) string {
	remoteIP := parseRemoteIP(r.RemoteAddr)
	if remoteIP == "" {
		remoteIP = strings.TrimSpace(r.RemoteAddr)
	}

	if !isTrustedProxy(remoteIP) {
		return remoteIP
	}

	if forwarded := forwardedClientIP(r); forwarded != "" {
		return forwarded
	}

	return remoteIP
}

func parseRemoteIP(raw string) string {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return ""
	}

	if host, _, err := net.SplitHostPort(trimmed); err == nil {
		return host
	}

	if ip := net.ParseIP(trimmed); ip != nil {
		return ip.String()
	}

	return trimmed
}

func forwardedClientIP(r *http.Request) string {
	for _, header := range []string{"X-Forwarded-For", "X-Real-IP"} {
		raw := strings.TrimSpace(r.Header.Get(header))
		if raw == "" {
			continue
		}

		if header == "X-Forwarded-For" {
			for _, part := range strings.Split(raw, ",") {
				candidate := strings.TrimSpace(part)
				if ip := net.ParseIP(candidate); ip != nil {
					return ip.String()
				}
			}
			continue
		}

		if ip := net.ParseIP(raw); ip != nil {
			return ip.String()
		}
	}

	return ""
}

func isTrustedProxy(remoteIP string) bool {
	ip := net.ParseIP(strings.TrimSpace(remoteIP))
	if ip == nil {
		return false
	}

	for _, exact := range splitEnvList("TRUST_PROXY_IPS") {
		if trusted := net.ParseIP(exact); trusted != nil && trusted.Equal(ip) {
			return true
		}
	}

	for _, cidr := range splitEnvList("TRUST_PROXY_CIDRS") {
		_, network, err := net.ParseCIDR(cidr)
		if err == nil && network.Contains(ip) {
			return true
		}
	}

	return false
}

func splitEnvList(key string) []string {
	raw := strings.TrimSpace(os.Getenv(key))
	if raw == "" {
		return nil
	}

	parts := strings.Split(raw, ",")
	values := make([]string, 0, len(parts))
	for _, part := range parts {
		trimmed := strings.TrimSpace(part)
		if trimmed != "" {
			values = append(values, trimmed)
		}
	}
	return values
}
