package tool

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

// httpTool, verilen config ile kind=http tool tanımı üretir.
func httpTool(name string, cfg map[string]any) *Tool {
	return &Tool{Name: name, Kind: KindHTTP, Status: StatusEnabled, Config: cfg}
}

func parseIP(s string) net.IP { return net.ParseIP(s) }

func TestHTTP_Success(t *testing.T) {
	var gotPath, gotMethod, gotHeader, gotBody string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath, gotMethod = r.URL.Path, r.Method
		gotHeader = r.Header.Get("X-Api-Key")
		b := make([]byte, r.ContentLength)
		_, _ = r.Body.Read(b)
		gotBody = string(b)
		_, _ = w.Write([]byte(`{"ok":true,"id":"P-1"}`))
	}))
	defer srv.Close()

	tl := httpTool("catia.create_plane", map[string]any{
		"url":          srv.URL + "/api/{{kind}}",
		"method":       "post",
		"headers":      map[string]any{"X-Api-Key": "abc-{{token}}"},
		"bodyTemplate": `{"name":"{{name}}"}`,
	})
	// AllowPrivate=true: httptest sunucusu loopback'ta dinler; koruma kendi testinde ayrı.
	h, err := NewHTTPHandler(tl, HTTPPolicy{Enabled: true, AllowPrivate: true})
	if err != nil {
		t.Fatal(err)
	}
	out, err := h(context.Background(), tl, map[string]any{"kind": "plane", "name": "Test Plane", "token": "T1"})
	if err != nil {
		t.Fatalf("hata: %v", err)
	}
	if gotMethod != http.MethodPost {
		t.Errorf("method: %q", gotMethod)
	}
	if gotPath != "/api/plane" {
		t.Errorf("şablonlu yol: %q", gotPath)
	}
	if gotHeader != "abc-T1" {
		t.Errorf("şablonlu başlık: %q", gotHeader)
	}
	if gotBody != `{"name":"Test Plane"}` {
		t.Errorf("şablonlu gövde: %q", gotBody)
	}
	if out["status"] != http.StatusOK {
		t.Errorf("status: %v", out["status"])
	}
	parsed, ok := out["json"].(map[string]any)
	if !ok || parsed["id"] != "P-1" {
		t.Errorf("JSON ayrıştırılmadı: %+v", out["json"])
	}
}

func TestHTTP_BlocksPrivateTargets(t *testing.T) {
	// SSRF korumasının çekirdeği: iç ağ/kaynak sunucusu hedefleri reddedilir.
	cases := []struct{ name, url string }{
		{"loopback", "http://127.0.0.1:8080/x"},
		{"loopback-ad", "http://localhost:8080/x"},
		{"cloud-metadata", "http://169.254.169.254/latest/meta-data/"},
		{"privat-10", "http://10.0.0.5/x"},
		{"privat-192", "http://192.168.1.10/x"},
		{"cgnat", "http://100.64.0.1/x"},
		{"ipv6-loopback", "http://[::1]:8080/x"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			tl := httpTool("t."+tc.name, map[string]any{"url": tc.url})
			h, err := NewHTTPHandler(tl, HTTPPolicy{Enabled: true}) // AllowPrivate=false
			if err != nil {
				t.Fatal(err)
			}
			if _, err := h(context.Background(), tl, map[string]any{}); !errors.Is(err, ErrHTTPBlocked) {
				t.Errorf("engellenmeliydi, gelen: %v", err)
			}
		})
	}
}

func TestHTTP_BlocksNonHTTPScheme(t *testing.T) {
	for _, raw := range []string{"file:///etc/passwd", "gopher://x", "ftp://x/y"} {
		tl := httpTool("t.bad", map[string]any{"url": raw})
		h, err := NewHTTPHandler(tl, HTTPPolicy{Enabled: true})
		if err != nil {
			continue // tanım aşamasında reddedilmesi de doğru davranıştır
		}
		if _, err := h(context.Background(), tl, map[string]any{}); err == nil {
			t.Errorf("%s engellenmeliydi", raw)
		}
	}
}

func TestHTTP_ErrorClassification(t *testing.T) {
	// 4xx kalıcı, 5xx geçici: Executor'ın retry kararı buna dayanır.
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/ok":
			_, _ = w.Write([]byte("ok"))
		case "/notfound":
			w.WriteHeader(http.StatusNotFound)
		case "/boom":
			w.WriteHeader(http.StatusInternalServerError)
		}
	}))
	defer srv.Close()

	run := func(path string) error {
		tl := httpTool("t"+path, map[string]any{"url": srv.URL + path})
		h, err := NewHTTPHandler(tl, HTTPPolicy{Enabled: true, AllowPrivate: true})
		if err != nil {
			t.Fatal(err)
		}
		_, err = h(context.Background(), tl, map[string]any{})
		return err
	}
	if err := run("/ok"); err != nil {
		t.Errorf("200 hata vermemeli: %v", err)
	}
	if err := run("/notfound"); !errors.Is(err, ErrHTTPRefused) {
		t.Errorf("404 kalıcı hata olmalı: %v", err)
	}
	boomErr := run("/boom")
	if !errors.Is(boomErr, ErrHTTPUnavailable) {
		t.Errorf("5xx geçici hata olmalı: %v", boomErr)
	}
	// 5xx tekrar denenecek (geçici), 4xx denenmeyecek (kalıcı).
	if !isRetryable(boomErr) {
		t.Error("5xx için Executor tekrar denemeliydi")
	}
	if isRetryable(run("/notfound")) {
		t.Error("4xx tekrar denenmemeliydi")
	}
}

// isRetryable, hata "geçici" protokolünü bildiriyor mu?
func isRetryable(err error) bool {
	var r interface{ Retryable() bool }
	return errors.As(err, &r) && r.Retryable()
}

func TestHTTP_TruncatesBigResponse(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(strings.Repeat("x", 5000)))
	}))
	defer srv.Close()

	tl := httpTool("t.big", map[string]any{"url": srv.URL, "result": "text"})
	h, err := NewHTTPHandler(tl, HTTPPolicy{Enabled: true, AllowPrivate: true, MaxBytes: 100})
	if err != nil {
		t.Fatal(err)
	}
	out, err := h(context.Background(), tl, map[string]any{})
	if err != nil {
		t.Fatal(err)
	}
	if out["truncated"] != true {
		t.Errorf("kısaltma işaretlenmeli: %+v", out)
	}
	if len(out["body"].(string)) != 100 {
		t.Errorf("gövde sınırlanmalıydı: %d", len(out["body"].(string)))
	}
}

func TestHTTP_RejectsHeaderInjection(t *testing.T) {
	tl := httpTool("t.crlf", map[string]any{
		"url":     "http://example.invalid/x",
		"headers": map[string]any{"X-A": "deger\r\nX-Kotu: 1"},
	})
	h, err := NewHTTPHandler(tl, HTTPPolicy{Enabled: true})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := h(context.Background(), tl, map[string]any{}); err == nil {
		t.Error("CRLF enjeksiyonu reddedilmeliydi")
	}
}

func TestHTTP_MissingURL(t *testing.T) {
	if _, err := NewHTTPHandler(httpTool("t.noUrl", map[string]any{}), HTTPPolicy{Enabled: true}); err == nil {
		t.Error("url yoksa tanım reddedilmeli")
	}
}

func TestIsBlockedIP(t *testing.T) {
	blocked := []string{"127.0.0.1", "10.1.2.3", "192.168.0.1", "169.254.169.254", "172.16.0.1", "100.64.0.1", "0.0.0.0", "::1"}
	for _, s := range blocked {
		if !isBlockedIP(parseIP(s)) {
			t.Errorf("%s engellenmeliydi", s)
		}
	}
	allowed := []string{"93.184.216.34", "8.8.8.8", "2001:4860:4860::8888"}
	for _, s := range allowed {
		if isBlockedIP(parseIP(s)) {
			t.Errorf("%s engellenmemeliydi", s)
		}
	}
}

func TestResolve_HTTPDisabledByPolicy(t *testing.T) {
	reg := NewRegistry(NewMemoryRepo(), nil)
	if _, err := reg.Register(context.Background(), Tool{
		Name: "catia.ping", Description: "uzak", Kind: KindHTTP,
		Permissions: []string{"execute"}, Risk: RiskLow,
		Config: map[string]any{"url": "http://example.invalid/x"},
	}); err != nil {
		t.Fatal(err)
	}
	// Genel kapı kapalı: tool var ama çalıştırılamaz.
	if _, _, err := reg.Resolve(context.Background(), "catia.ping"); !errors.Is(err, ErrHTTPDisabled) {
		t.Errorf("kapı kapalıyken ErrHTTPDisabled bekleniyordu: %v", err)
	}
	// Kapı açık: handler üretilir.
	reg.SetHTTPPolicy(HTTPPolicy{Enabled: true})
	if _, h, err := reg.Resolve(context.Background(), "catia.ping"); err != nil || h == nil {
		t.Errorf("kapı açıkken handler üretilmeliydi: %v", err)
	}
}
