package membership

import (
	"net/http"
	"testing"
)

// TestClientIPTrustedProxy covers the rate-limit key extraction behind a reverse
// proxy: forwarding headers are believed ONLY when the immediate peer is a
// configured trusted proxy, and never otherwise. This is what keeps the per-IP
// rate limit per-client once the control plane runs behind the same-origin Caddy
// proxy, without opening a quota-fanning hole for a direct attacker.
func TestClientIPTrustedProxy(t *testing.T) {
	const caddy = "135.125.201.30"

	cases := []struct {
		name    string
		proxies []string
		remote  string
		xff     string
		xRealIP string
		want    string
	}{
		{
			name:   "no trusted proxies ignores XFF",
			remote: "203.0.113.7:5000",
			xff:    "1.2.3.4",
			want:   "203.0.113.7",
		},
		{
			name:    "trusted proxy honors XFF client",
			proxies: []string{caddy},
			remote:  caddy + ":4451",
			xff:     "198.51.100.23",
			want:    "198.51.100.23",
		},
		{
			name:    "loopback proxy honors XFF (magnus-local hop)",
			proxies: []string{"127.0.0.1/32", "::1/128"},
			remote:  "127.0.0.1:33344",
			xff:     "198.51.100.99",
			want:    "198.51.100.99",
		},
		{
			name:    "untrusted peer cannot spoof XFF",
			proxies: []string{caddy},
			remote:  "203.0.113.7:5000",
			xff:     "10.0.0.1",
			want:    "203.0.113.7",
		},
		{
			name:    "XFF read right-to-left, trusted hops skipped",
			proxies: []string{caddy},
			remote:  caddy + ":4451",
			xff:     "198.51.100.23, " + caddy,
			want:    "198.51.100.23",
		},
		{
			name:    "client-prepended forgery is skipped, real appended wins",
			proxies: []string{caddy},
			remote:  caddy + ":4451",
			xff:     "9.9.9.9, 198.51.100.23",
			want:    "198.51.100.23",
		},
		{
			name:    "X-Real-IP fallback when no XFF",
			proxies: []string{caddy},
			remote:  caddy + ":4451",
			xRealIP: "198.51.100.77",
			want:    "198.51.100.77",
		},
		{
			name:    "trusted peer but no forwarding header falls back to peer",
			proxies: []string{caddy},
			remote:  caddy + ":4451",
			want:    caddy,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			s := &Server{}
			if len(tc.proxies) > 0 {
				if err := s.SetTrustedProxies(tc.proxies); err != nil {
					t.Fatalf("SetTrustedProxies(%v): %v", tc.proxies, err)
				}
			}
			r, _ := http.NewRequest(http.MethodGet, "/rooms", nil)
			r.RemoteAddr = tc.remote
			if tc.xff != "" {
				r.Header.Set("X-Forwarded-For", tc.xff)
			}
			if tc.xRealIP != "" {
				r.Header.Set("X-Real-IP", tc.xRealIP)
			}
			if got := s.clientIP(r); got != tc.want {
				t.Fatalf("clientIP = %q, want %q", got, tc.want)
			}
		})
	}
}

// TestParseTrustedProxiesRejectsGarbage proves a malformed entry is a hard error
// (the command turns it into a startup failure) rather than a silently ignored
// misconfiguration that would leave the rate limit collapsed behind the proxy.
func TestParseTrustedProxiesRejectsGarbage(t *testing.T) {
	if _, err := parseTrustedProxies([]string{"not-an-ip"}); err == nil {
		t.Fatal("expected error for non-IP/CIDR entry, got nil")
	}
	if _, err := parseTrustedProxies([]string{"10.0.0.0/8", "127.0.0.1"}); err != nil {
		t.Fatalf("valid entries rejected: %v", err)
	}
}
