package knowledgetools

import (
	"context"
	"testing"

	"github.com/enmanuel/agents/pkg/knowledge"
	"github.com/enmanuel/agents/tools"
)

// mockKnowledgeStore implements KnowledgeStore for testing.
type mockKnowledgeStore struct {
	docs map[string]knowledge.Document
}

func newMockKnowledgeStore() *mockKnowledgeStore {
	return &mockKnowledgeStore{docs: make(map[string]knowledge.Document)}
}

func (m *mockKnowledgeStore) Search(_ context.Context, query string, limit int) ([]knowledge.SearchResult, error) {
	var results []knowledge.SearchResult
	for _, d := range m.docs {
		if len(results) >= limit {
			break
		}
		results = append(results, knowledge.SearchResult{
			Slug:    d.Slug,
			Title:   d.Title,
			Snippet: d.Content[:min(len(d.Content), 50)],
			Rank:    1.0,
		})
	}
	return results, nil
}

func (m *mockKnowledgeStore) Get(_ context.Context, slug string) (*knowledge.Document, error) {
	d, ok := m.docs[slug]
	if !ok {
		return nil, &notFoundError{slug}
	}
	return &d, nil
}

func (m *mockKnowledgeStore) Put(_ context.Context, doc knowledge.Document) error {
	m.docs[doc.Slug] = doc
	return nil
}

func (m *mockKnowledgeStore) List(_ context.Context) ([]knowledge.Document, error) {
	var docs []knowledge.Document
	for _, d := range m.docs {
		docs = append(docs, d)
	}
	return docs, nil
}

type notFoundError struct{ slug string }

func (e *notFoundError) Error() string { return "not found: " + e.slug }

func TestKnowledgeSearchTool(t *testing.T) {
	store := newMockKnowledgeStore()
	store.docs["go-patterns"] = knowledge.Document{
		Slug: "go-patterns", Title: "Go Patterns", Content: "Use interfaces",
	}

	tool := NewKnowledgeSearch(store)
	if tool.Def.Name != "knowledge_search" {
		t.Errorf("name = %q, want knowledge_search", tool.Def.Name)
	}

	// Missing query
	r := tool.Exec(context.Background(), map[string]any{})
	if r.Err == nil {
		t.Error("expected error for missing query")
	}

	// Valid search
	r = tool.Exec(context.Background(), map[string]any{"query": "go"})
	if r.Err != nil {
		t.Errorf("unexpected error: %v", r.Err)
	}
	if r.Output == "" {
		t.Error("expected non-empty output")
	}
}

func TestKnowledgeReadTool(t *testing.T) {
	store := newMockKnowledgeStore()
	store.docs["test-doc"] = knowledge.Document{
		Slug: "test-doc", Title: "Test", Content: "Hello world",
	}

	tool := NewKnowledgeRead(store)

	// Missing slug
	r := tool.Exec(context.Background(), map[string]any{})
	if r.Err == nil {
		t.Error("expected error for missing slug")
	}

	// Valid read
	r = tool.Exec(context.Background(), map[string]any{"slug": "test-doc"})
	if r.Err != nil {
		t.Errorf("unexpected error: %v", r.Err)
	}
	if r.Output != "Hello world" {
		t.Errorf("output = %q, want %q", r.Output, "Hello world")
	}

	// Not found
	r = tool.Exec(context.Background(), map[string]any{"slug": "nope"})
	if r.Err == nil {
		t.Error("expected error for nonexistent doc")
	}
}

func TestKnowledgeWriteTool(t *testing.T) {
	store := newMockKnowledgeStore()
	tool := NewKnowledgeWrite(store)

	// Missing params
	r := tool.Exec(context.Background(), map[string]any{"slug": "test"})
	if r.Err == nil {
		t.Error("expected error for missing content")
	}

	// Valid write
	r = tool.Exec(context.Background(), map[string]any{
		"slug":    "new-doc",
		"content": "# New Doc\nSome content",
	})
	if r.Err != nil {
		t.Errorf("unexpected error: %v", r.Err)
	}
	if _, ok := store.docs["new-doc"]; !ok {
		t.Error("document was not stored")
	}
}

func TestKnowledgeListTool(t *testing.T) {
	store := newMockKnowledgeStore()
	tool := NewKnowledgeList(store)

	// Empty
	r := tool.Exec(context.Background(), map[string]any{})
	if r.Err != nil {
		t.Errorf("unexpected error: %v", r.Err)
	}
	if r.Output != "knowledge base is empty" {
		t.Errorf("expected empty message, got %q", r.Output)
	}

	// With docs
	store.docs["doc1"] = knowledge.Document{Slug: "doc1", Title: "Doc 1"}
	r = tool.Exec(context.Background(), map[string]any{})
	if r.Err != nil {
		t.Errorf("unexpected error: %v", r.Err)
	}
	if r.Output == "knowledge base is empty" {
		t.Error("expected non-empty output after adding docs")
	}
}

func TestGetInt(t *testing.T) {
	tests := []struct {
		args map[string]any
		key  string
		want int
	}{
		{map[string]any{"n": float64(5)}, "n", 5},
		{map[string]any{"n": 3}, "n", 3},
		{map[string]any{"n": "str"}, "n", 0},
		{map[string]any{}, "n", 0},
	}
	for _, tt := range tests {
		got := tools.GetInt(tt.args, tt.key)
		if got != tt.want {
			t.Errorf("GetInt(%v, %q) = %d, want %d", tt.args, tt.key, got, tt.want)
		}
	}
}
