package tool

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

// PinnedHost, adresten dial için "host:port" üretir; port yoksa şemaya göre tamamlar.
func TestPinnedHost(t *testing.T) {
	cases := []struct{ in, want string }{
		{"http://127.0.0.1:8181", "127.0.0.1:8181"},
		{"http://localhost:8181/", "localhost:8181"},
		{"https://searx.example.com", "searx.example.com:443"},
		{"http://searx.example.com", "searx.example.com:80"},
		{"HTTP://LocalHost:8181", "localhost:8181"},
		{"", ""},
		{"http://", ""},
	}
	for _, c := range cases {
		if got := PinnedHost(c.in); got != c.want {
			t.Errorf("PinnedHost(%q) = %q, beklenen %q", c.in, got, c.want)
		}
	}
}

// TestNewPinnedClient_IzinliHostSerbest: izin listesindeki adrese bağlanabilmeli.
func TestNewPinnedClient_IzinliHostSerbest(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte("ok"))
	}))
	defer srv.Close()

	c := NewPinnedClient([]string{srv.Listener.Addr().String()}, HTTPPolicy{Timeout: 5 * time.Second})
	resp, err := c.Get(srv.URL) // nolint:bodyclose
	if err != nil {
		t.Fatalf("izinli host'a bağlanılamadı: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d, beklenen 200", resp.StatusCode)
	}
}

// TestNewPinnedClient_IzinDisiEngellenir: izin listesinde olmayan host reddedilmeli.
// Bu, web.fetch'in SSRF korumasının gevşetilMEDİĞinin güvencesidir.
func TestNewPinnedClient_IzinDisiEngellenir(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte("ic ag verisi"))
	}))
	defer srv.Close()

	c := NewPinnedClient([]string{"127.0.0.1:1"}, HTTPPolicy{Timeout: 5 * time.Second})
	_, err := c.Get(srv.URL) // nolint:bodyclose
	if err == nil {
		t.Fatal("izin listesinde olmayan hosta bağlanıldı — SSRF koruması bozuk")
	}
	if !errors.Is(err, ErrHTTPBlocked) {
		t.Fatalf("hata = %v, beklenen ErrHTTPBlocked", err)
	}
}

// TestNewPinnedClient_BosListeHepsiEngellenir: boş izin listesi her şeyi reddetmeli.
func TestNewPinnedClient_BosListeHepsiEngellenir(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte("ok"))
	}))
	defer srv.Close()

	c := NewPinnedClient(nil, HTTPPolicy{Timeout: 5 * time.Second})
	if _, err := c.Get(srv.URL); err == nil { // nolint:bodyclose
		t.Fatal("boş izin listesiyle bağlanıldı — herkes engellenmeliydi")
	}
}

// TestNewPinnedClient_YerelLoopbackIzinVerir: searxng senaryosunun asıl amacı.
// Varsayılan NewSafeClient loopback'i reddeder; pinned istemci İZİNLİ olduğu için
// reddetmemelidir.
func TestNewPinnedClient_YerelLoopbackIzinVerir(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte(`{"results":[]}`))
	}))
	defer srv.Close()

	// Varsayılan güvenli istemci: loopback reddi (koruma çalışıyor).
	safe := NewSafeClient(HTTPPolicy{Timeout: 5 * time.Second})
	if _, err := safe.Get(srv.URL); err == nil { // nolint:bodyclose
		t.Fatal("NewSafeClient loopback'e izin verdi — SSRF koruması bozuk")
	}

	// Pinned istemci: aynı adres yapılandırılmış olduğu için izinli.
	pinned := NewPinnedClient([]string{srv.Listener.Addr().String()}, HTTPPolicy{Timeout: 5 * time.Second})
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil)
	resp, err := pinned.Do(req)
	if err != nil {
		t.Fatalf("pinned istemci izinli loopback adresine bağlanamadı: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d, beklenen 200", resp.StatusCode)
	}
}
