package httpx

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

// clientInfo is the resolved view of the requester after applying the
// trusted-proxy policy. It feeds rate limiting, sessions, and the audit log.
type clientInfo struct {
	IP    netip.Addr
	Proto string // effective scheme: "http" or "https"
	Host  string // effective host header
}

func parsePeer(remoteAddr string) netip.Addr {
	if ap, err := netip.ParseAddrPort(remoteAddr); err == nil {
		return ap.Addr().Unmap()
	}
	if a, err := netip.ParseAddr(remoteAddr); err == nil {
		return a.Unmap()
	}
	return netip.Addr{}
}

func isTrusted(a netip.Addr, trusted []netip.Prefix) bool {
	if !a.IsValid() {
		return false
	}
	for _, p := range trusted {
		if p.Contains(a) {
			return true
		}
	}
	return false
}

// resolveClient applies §12: X-Forwarded-For / X-Forwarded-Proto /
// X-Forwarded-Host are honored only when the direct peer is inside
// TRUSTED_PROXY_CIDRS; otherwise the headers are ignored entirely.
//
// The client IP is found by walking the X-Forwarded-For chain from the
// rightmost entry (appended by our own proxy) towards the left, skipping
// addresses that are themselves trusted proxies; the first untrusted address
// is the real client. A malformed entry stops the walk (everything further
// left is attacker-controllable).
func resolveClient(r *http.Request, trusted []netip.Prefix, defaultProto string) clientInfo {
	peer := parsePeer(r.RemoteAddr)
	info := clientInfo{IP: peer, Proto: defaultProto, Host: r.Host}
	if len(trusted) == 0 || !isTrusted(peer, trusted) {
		return info
	}

	var chain []string
	for _, h := range r.Header.Values("X-Forwarded-For") {
		for part := range strings.SplitSeq(h, ",") {
			if part = strings.TrimSpace(part); part != "" {
				chain = append(chain, part)
			}
		}
	}
	for i := len(chain) - 1; i >= 0; i-- {
		a := parsePeer(chain[i])
		if !a.IsValid() {
			break
		}
		info.IP = a
		if !isTrusted(a, trusted) {
			break
		}
		// a is itself a trusted proxy: keep walking left. If the whole chain
		// is trusted, the leftmost entry stands as the best available answer.
	}

	switch p := strings.ToLower(strings.TrimSpace(r.Header.Get("X-Forwarded-Proto"))); p {
	case "http", "https":
		info.Proto = p
	}
	if h := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")); h != "" {
		info.Host = h
	}
	return info
}
