package skill

import (
	"context"
	"errors"
	"testing"
)

// fakeToolExecutor skill.ToolExecutor arayüzünün sahte uygulamasıdır.
type fakeToolExecutor struct {
	calls    int
	lastArg  map[string]any
	lastTool string
	out      map[string]any
	err      error
}

func (f *fakeToolExecutor) ExecuteTool(_ context.Context, toolName string, args map[string]any) (ToolRunResult, error) {
	f.calls++
	f.lastTool, f.lastArg = toolName, args
	if f.err != nil {
		return ToolRunResult{}, f.err
	}
	return ToolRunResult{ToolName: toolName, Output: f.out, DurationMs: 7}, nil
}

// fakeToolChecker var olan tool adlarını bilen sahte denetleyicidir.
type fakeToolChecker struct{ known map[string]bool }

func (f *fakeToolChecker) HasTool(_ context.Context, name string) bool { return f.known[name] }

func toolSkill(name, toolName string) Skill {
	return Skill{
		Name:           name,
		Description:    "Tool üzerinden çalışan skill.",
		Status:         StatusActive,
		ExecutorType:   ExecutorTool,
		ExecutorConfig: map[string]any{"tool": toolName},
		Parameters:     []Parameter{{Name: "offset", Type: ParamNumber, Required: true}},
	}
}

func TestToolBindingIsValidated(t *testing.T) {
	svc, _, _ := newTestService(t)
	svc.SetToolChecker(&fakeToolChecker{known: map[string]bool{"catia.create_plane": true}})

	// Var olmayan tool: ACTIVE skill reddedilir.
	in := toolSkill("create_plane", "catia.yok_boyle_bir_tool")
	_, err := svc.Register(context.Background(), in)
	var ve *ValidationError
	if !errors.As(err, &ve) {
		t.Fatalf("ValidationError bekleniyordu, %v", err)
	}
	if ve.Errors[0].Field != "executorConfig" {
		t.Errorf("executorConfig alanı işaretlenmeliydi: %+v", ve.Errors)
	}

	// Var olan tool: kaydedilir, etkinleştirilebilir.
	good, err := svc.Register(context.Background(), toolSkill("create_plane", "catia.create_plane"))
	if err != nil {
		t.Fatal(err)
	}
	if _, err := svc.SetStatus(context.Background(), good.ID, StatusActive); err != nil {
		t.Errorf("geçerli tool ile etkinleştirilmeliydi: %v", err)
	}

	// Denetçi YOKSA bu kontrol yapılmaz (sistem tool'suz da çalışabilir).
	bare := NewService(NewMemoryRepo(), nil)
	if _, err := bare.Register(context.Background(), toolSkill("serbest", "hic.bir.tool")); err != nil {
		t.Errorf("denetçi yokken varlık denetlenmemeli: %v", err)
	}
}

func TestExecuteSkill(t *testing.T) {
	ctx := context.Background()
	svc, _, _ := newTestService(t)
	exec := &fakeToolExecutor{out: map[string]any{"created": "PLANE_1"}}
	svc.SetToolExecutor(exec)
	svc.SetToolChecker(&fakeToolChecker{known: map[string]bool{"catia.create_plane": true}})

	sk, err := svc.Register(ctx, toolSkill("create_plane", "catia.create_plane"))
	if err != nil {
		t.Fatal(err)
	}

	// Zorunlu parametre eksikse çalıştırılmaz.
	if _, err := svc.ExecuteSkill(ctx, sk.ID, nil); err == nil {
		t.Error("zorunlu parametre eksikken çalıştırılmamalıydı")
	} else if exec.calls != 0 {
		t.Error("geçersiz argümanda tool çağrılmamalıydı")
	}

	res, err := svc.ExecuteSkill(ctx, sk.ID, map[string]any{"offset": 30.0})
	if err != nil {
		t.Fatal(err)
	}
	if res.Tool != "catia.create_plane" || res.Output["created"] != "PLANE_1" || res.DurationMs != 7 {
		t.Errorf("sonuç beklenmedik: %+v", res)
	}
	if exec.lastTool != "catia.create_plane" || exec.lastArg["offset"] != 30.0 {
		t.Errorf("tool'a giden istek: %s %v", exec.lastTool, exec.lastArg)
	}

	// Taslak/kapalı skill çalıştırılamaz.
	draftInput := toolSkill("taslak_skill", "catia.create_plane")
	draftInput.Status = StatusDraft
	draft, err := svc.Register(ctx, draftInput)
	if err != nil {
		t.Fatal(err)
	}
	if got, _ := svc.Get(ctx, draft.ID); got.Status != StatusDraft {
		t.Fatalf("test hazırlığı: skill taslak değil (%s)", got.Status)
	}
	if _, err := svc.ExecuteSkill(ctx, draft.ID, map[string]any{"offset": 1.0}); !errors.Is(err, ErrNotExecutable) {
		t.Errorf("taslak: %v, ErrNotExecutable bekleniyordu", err)
	}

	// executorType=llm henüz yürütülemez (Faz 5).
	llmSkill := toolSkill("llm_skill", "catia.create_plane")
	llmSkill.ExecutorType = ExecutorLLM
	llmSkill.ExecutorConfig = map[string]any{"prompt": "çok detaylı bir çizim yap"}
	llmSk, err := svc.Register(ctx, llmSkill)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := svc.ExecuteSkill(ctx, llmSk.ID, nil); !errors.Is(err, ErrExecutorUnsupported) {
		t.Errorf("llm yürütücüsü: %v, ErrExecutorUnsupported bekleniyordu", err)
	}

	// Tool sistemi bağlı değilken açık hata verilir.
	bare := NewService(NewMemoryRepo(), nil)
	sk2, err := bare.Register(ctx, toolSkill("b_skill", "catia.create_plane"))
	if err != nil {
		t.Fatal(err)
	}
	if _, err := bare.ExecuteSkill(ctx, sk2.ID, map[string]any{"offset": 1.0}); !errors.Is(err, ErrNoExecutor) {
		t.Errorf("bağlı değilken: %v, ErrNoExecutor bekleniyordu", err)
	}
}

func TestExecuteSkillPropagatesToolError(t *testing.T) {
	ctx := context.Background()
	svc, _, _ := newTestService(t)
	exec := &fakeToolExecutor{err: errors.New("tool patladı")}
	svc.SetToolExecutor(exec)
	svc.SetToolChecker(&fakeToolChecker{known: map[string]bool{"catia.create_plane": true}})

	sk, err := svc.Register(ctx, toolSkill("create_plane", "catia.create_plane"))
	if err != nil {
		t.Fatal(err)
	}
	if _, err := svc.ExecuteSkill(ctx, sk.ID, map[string]any{"offset": 1.0}); err == nil {
		t.Error("tool hatası aktarılmalıydı")
	}
}
