package builtin

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"fiko-ai/internal/tool"
)

// testReg geçici bir kökle Registry + Executor kurar (her izin verilmiş: bu testler
// tool davranışını sınar, İZİNLER değil — izin testleri tool paketindedir).
func testReg(t *testing.T, o Options) (*tool.Registry, *tool.Executor) {
	t.Helper()
	if o.FSRoot == "" {
		o.FSRoot = t.TempDir()
	}
	reg := tool.NewRegistry(tool.NewMemoryRepo(), nil)
	if err := Register(context.Background(), reg, o); err != nil {
		t.Fatalf("Register: %v", err)
	}
	grants := tool.NewGrantStore(tool.Policy{
		Grants:        tool.PermAll,
		AllowHighRisk: true,
		AllowCritical: true,
	})
	return reg, tool.NewExecutor(reg, grants, nil, nil, tool.ExecutorOptions{})
}

func run(t *testing.T, ex *tool.Executor, ref string, in map[string]any) map[string]any {
	t.Helper()
	res, err := ex.Execute(context.Background(), tool.Request{Ref: ref, Input: in, Actor: "user"})
	if err != nil {
		t.Fatalf("Execute(%s): %v", ref, err)
	}
	return res.Output
}

func TestFilesystemRoundTrip(t *testing.T) {
	_, ex := testReg(t, Options{})

	wrote := run(t, ex, "fs.write_text", map[string]any{"path": "notlar/plan.txt", "content": "30 mm geriye"})
	if wrote["path"] != "notlar/plan.txt" {
		t.Errorf("yazma yanıtı: %v", wrote)
	}
	read := run(t, ex, "fs.read_text", map[string]any{"path": "notlar/plan.txt"})
	if read["content"] != "30 mm geriye" {
		t.Errorf("okunan içerik: %v", read["content"])
	}

	// append
	run(t, ex, "fs.write_text", map[string]any{"path": "notlar/plan.txt", "content": " + 10 mm", "append": true})
	if got := run(t, ex, "fs.read_text", map[string]any{"path": "notlar/plan.txt"}); got["content"] != "30 mm geriye + 10 mm" {
		t.Errorf("append sonrası: %v", got["content"])
	}

	// sil
	if got := run(t, ex, "fs.delete", map[string]any{"path": "notlar/plan.txt"}); got["deleted"] != true {
		t.Errorf("silme yanıtı: %v", got)
	}
	if _, err := ex.Execute(context.Background(), tool.Request{Ref: "fs.read_text", Input: map[string]any{"path": "notlar/plan.txt"}}); err == nil {
		t.Error("silinen dosya okunabilmemeli")
	}
}

func TestFilesystemSandboxBlocksEscape(t *testing.T) {
	root := t.TempDir()
	_, ex := testReg(t, Options{FSRoot: root})
	outside := filepath.Join(filepath.Dir(root), "gizli.txt")
	if err := os.WriteFile(outside, []byte("GİZLİ"), 0o600); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = os.Remove(outside) })

	for _, path := range []string{
		"../gizli.txt",
		"notlar/../../gizli.txt",
		outside, // mutlak yol
		"",
	} {
		_, err := ex.Execute(context.Background(), tool.Request{Ref: "fs.read_text", Input: map[string]any{"path": path}})
		if err == nil {
			t.Errorf("kök dışı yol reddedilmeliydi: %q", path)
		}
	}
	// Yazma ve silme de kaçışı engellemeli.
	if _, err := ex.Execute(context.Background(), tool.Request{
		Ref: "fs.write_text", Input: map[string]any{"path": "../kacis.txt", "content": "x"},
	}); err == nil {
		t.Error("kök dışına yazma engellenmeliydi")
	}
	if _, err := ex.Execute(context.Background(), tool.Request{
		Ref: "fs.delete", Input: map[string]any{"path": "../gizli.txt", "recursive": true},
	}); err == nil {
		t.Error("kök dışından silme engellenmeliydi")
	}
	// Dışarıdaki dosya hâlâ durmalı.
	if _, err := os.Stat(outside); err != nil {
		t.Errorf("dış dosya etkilenmiş: %v", err)
	}
	// Kökün kendisi silinemez.
	if _, err := ex.Execute(context.Background(), tool.Request{
		Ref: "fs.delete", Input: map[string]any{"path": ".", "recursive": true},
	}); err == nil {
		t.Error("kök dizin silinmemeliydi")
	}
}

func TestFilesystemLimits(t *testing.T) {
	_, ex := testReg(t, Options{MaxReadBytes: 8, MaxWriteBytes: 8})
	if _, err := ex.Execute(context.Background(), tool.Request{
		Ref: "fs.write_text", Input: map[string]any{"path": "buyuk.txt", "content": strings.Repeat("x", 100)},
	}); err == nil {
		t.Error("yazma sınırı uygulanmalıydı")
	}
	run(t, ex, "fs.write_text", map[string]any{"path": "kucuk.txt", "content": "12345678"})
	if _, err := ex.Execute(context.Background(), tool.Request{
		Ref: "fs.read_text", Input: map[string]any{"path": "kucuk.txt"},
	}); err != nil {
		t.Errorf("sınır içi okuma başarısız: %v", err)
	}
}

func TestExecRequiresAllowList(t *testing.T) {
	// İzin listesi boşken sys.exec HİÇ OLUŞMAMALI (görünmez bile olmamalı).
	_, ex := testReg(t, Options{ExecAllowed: nil})
	if _, err := ex.Execute(context.Background(), tool.Request{Ref: "sys.exec"}); err == nil {
		t.Error("izin listesi yokken sys.exec çalışmamalı")
	}

	// Program izin listesinde değilse reddedilir.
	_, ex2 := testReg(t, Options{ExecAllowed: []string{filepath.Join(t.TempDir(), "yok.exe")}})
	if _, err := ex2.Execute(context.Background(), tool.Request{
		Ref: "sys.exec", Input: map[string]any{"program": "cmd.exe", "args": []any{"/c", "echo", "zararli"}},
	}); err == nil {
		t.Error("liste dışı program reddedilmeliydi")
	}
}

func TestEchoAndNow(t *testing.T) {
	_, ex := testReg(t, Options{})
	if got := run(t, ex, "fiko.echo", map[string]any{"message": "merhaba"}); got["message"] != "merhaba" {
		t.Errorf("echo: %v", got)
	}
	now := run(t, ex, "fiko.now", nil)
	if _, ok := now["iso"].(string); !ok {
		t.Errorf("now: %v", now)
	}
	// Geçersiz saat dilimi reddedilir.
	if _, err := ex.Execute(context.Background(), tool.Request{
		Ref: "fiko.now", Input: map[string]any{"timezone": "Mars/Olympus"},
	}); err == nil {
		t.Error("geçersiz saat dilimi reddedilmeliydi")
	}
}
