package tools

import (
	"context"
	"fmt"
	"strings"

	"github.com/google/uuid"

	"github.com/nextlevelbuilder/goclaw/internal/store"
)

// KnowledgeGraphSearchTool provides graph-based search for agents.
type KnowledgeGraphSearchTool struct {
	kgStore store.KnowledgeGraphStore
}

// NewKnowledgeGraphSearchTool creates a new KnowledgeGraphSearchTool.
func NewKnowledgeGraphSearchTool() *KnowledgeGraphSearchTool {
	return &KnowledgeGraphSearchTool{}
}

// SetKGStore sets the KnowledgeGraphStore for this tool.
func (t *KnowledgeGraphSearchTool) SetKGStore(ks store.KnowledgeGraphStore) {
	t.kgStore = ks
}

func (t *KnowledgeGraphSearchTool) Name() string { return "knowledge_graph_search" }

func (t *KnowledgeGraphSearchTool) Description() string {
	return "Search the knowledge graph to find people, projects, organizations, and how they connect. Better than memory_search when you need: who works with whom, what projects someone is involved in, dependencies between tasks, or any multi-hop relationship question. Use specific names (e.g. 'Minh', 'GoClaw') — not generic words. Use query='*' to list all known entities. Use entity_id to traverse connections from a specific entity."
}

func (t *KnowledgeGraphSearchTool) Parameters() map[string]any {
	return map[string]any{
		"type": "object",
		"properties": map[string]any{
			"query": map[string]any{
				"type":        "string",
				"description": "Search query for entity names or descriptions",
			},
			"entity_type": map[string]any{
				"type":        "string",
				"description": "Filter by entity type (person, project, task, event, concept, location, organization)",
			},
			"entity_id": map[string]any{
				"type":        "string",
				"description": "Entity ID to traverse from (for relationship discovery)",
			},
			"max_depth": map[string]any{
				"type":        "number",
				"description": "Maximum traversal depth (default 2, max 3)",
			},
		},
		"required": []string{"query"},
	}
}

func (t *KnowledgeGraphSearchTool) Execute(ctx context.Context, args map[string]any) *Result {
	if t.kgStore == nil {
		return NewResult("Knowledge graph is not enabled for this agent.")
	}

	agentID := store.AgentIDFromContext(ctx)
	if agentID == uuid.Nil {
		return ErrorResult("agent context not available")
	}
	userID := store.MemoryUserID(ctx)

	query, _ := args["query"].(string)
	if query == "" {
		return ErrorResult("query parameter is required")
	}

	entityID, _ := args["entity_id"].(string)
	maxDepth := 2
	if md, ok := args["max_depth"].(float64); ok && md > 0 {
		maxDepth = min(int(md), 3)
	}

	// Traversal mode: entity_id provided
	if entityID != "" {
		return t.executeTraversal(ctx, agentID.String(), userID, entityID, maxDepth)
	}

	// List-all mode: query="*"
	if query == "*" {
		return t.executeListAll(ctx, agentID.String(), userID)
	}

	// Search mode
	return t.executeSearch(ctx, agentID.String(), userID, query, args)
}

func (t *KnowledgeGraphSearchTool) executeTraversal(ctx context.Context, agentID, userID, entityID string, maxDepth int) *Result {
	results, err := t.kgStore.Traverse(ctx, agentID, userID, entityID, maxDepth)
	if err != nil {
		return ErrorResult(fmt.Sprintf("graph traversal failed: %v", err))
	}
	if len(results) == 0 {
		return NewResult(fmt.Sprintf("No connected entities found from entity_id=%q within depth %d.", entityID, maxDepth))
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Graph traversal from %q (max depth %d):\n\n", entityID, maxDepth))
	for _, r := range results {
		sb.WriteString(fmt.Sprintf("- [depth %d] %s (%s)", r.Depth, r.Entity.Name, r.Entity.EntityType))
		if r.Via != "" {
			sb.WriteString(fmt.Sprintf(" via %q", r.Via))
		}
		if r.Entity.Description != "" {
			sb.WriteString(fmt.Sprintf("\n  %s", r.Entity.Description))
		}
		if len(r.Path) > 0 {
			sb.WriteString(fmt.Sprintf("\n  path: %s", strings.Join(r.Path, " → ")))
		}
		sb.WriteString("\n")
	}
	return NewResult(sb.String())
}

func (t *KnowledgeGraphSearchTool) executeListAll(ctx context.Context, agentID, userID string) *Result {
	entities, err := t.kgStore.ListEntities(ctx, agentID, userID, store.EntityListOptions{Limit: 30})
	if err != nil {
		return ErrorResult(fmt.Sprintf("list entities failed: %v", err))
	}
	if len(entities) == 0 {
		return NewResult("Knowledge graph is empty. No entities have been extracted yet.")
	}
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Knowledge graph has %d entities:\n\n", len(entities)))
	for _, e := range entities {
		sb.WriteString(fmt.Sprintf("- %s [%s] (id: %s)\n", e.Name, e.EntityType, e.ID))
		if e.Description != "" {
			sb.WriteString(fmt.Sprintf("  %s\n", e.Description))
		}
	}
	sb.WriteString("\nTip: Use entity_id parameter to traverse relationships from a specific entity.")
	return NewResult(sb.String())
}

func (t *KnowledgeGraphSearchTool) executeSearch(ctx context.Context, agentID, userID, query string, args map[string]any) *Result {
	entities, err := t.kgStore.SearchEntities(ctx, agentID, userID, query, 10)
	if err != nil {
		return ErrorResult(fmt.Sprintf("entity search failed: %v", err))
	}

	// No results: show available entities as hints
	if len(entities) == 0 {
		return t.noResultsHint(ctx, agentID, userID, query)
	}

	// Optional type filter (post-search)
	entityType, _ := args["entity_type"].(string)
	if entityType != "" {
		filtered := entities[:0]
		for _, e := range entities {
			if e.EntityType == entityType {
				filtered = append(filtered, e)
			}
		}
		entities = filtered
		if len(entities) == 0 {
			return NewResult(fmt.Sprintf("No entities of type %q found matching %q.", entityType, query))
		}
	}

	// Build entity name lookup for relation display
	entityNames := make(map[string]string, len(entities))
	for _, e := range entities {
		entityNames[e.ID] = e.Name
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d entities matching %q:\n\n", len(entities), query))
	for _, e := range entities {
		sb.WriteString(fmt.Sprintf("- %s [%s] (id: %s)\n", e.Name, e.EntityType, e.ID))
		if e.Description != "" {
			sb.WriteString(fmt.Sprintf("  %s\n", e.Description))
		}

		// Fetch relations to show connections with names
		relations, err := t.kgStore.ListRelations(ctx, agentID, userID, e.ID)
		if err == nil && len(relations) > 0 {
			sb.WriteString("  Relations:\n")
			for _, rel := range relations {
				srcName := t.resolveEntityName(ctx, agentID, userID, rel.SourceEntityID, entityNames)
				tgtName := t.resolveEntityName(ctx, agentID, userID, rel.TargetEntityID, entityNames)
				sb.WriteString(fmt.Sprintf("    %s —[%s]→ %s\n", srcName, rel.RelationType, tgtName))
			}
		}
	}
	return NewResult(sb.String())
}

// resolveEntityName returns a human-readable name for an entity ID, using cache or DB lookup.
func (t *KnowledgeGraphSearchTool) resolveEntityName(ctx context.Context, agentID, userID, entityID string, cache map[string]string) string {
	if name, ok := cache[entityID]; ok {
		return name
	}
	e, err := t.kgStore.GetEntity(ctx, agentID, userID, entityID)
	if err == nil && e != nil {
		cache[entityID] = e.Name
		return e.Name
	}
	return entityID[:8] // fallback: short UUID
}

// noResultsHint returns top entities so the model knows what's available.
func (t *KnowledgeGraphSearchTool) noResultsHint(ctx context.Context, agentID, userID, query string) *Result {
	top, _ := t.kgStore.ListEntities(ctx, agentID, userID, store.EntityListOptions{Limit: 10})
	if len(top) == 0 {
		return NewResult("Knowledge graph is empty. No entities have been extracted yet.")
	}
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("No entities found matching %q. ", query))
	sb.WriteString(fmt.Sprintf("The knowledge graph has %d entities. Here are some available ones:\n\n", len(top)))
	for _, e := range top {
		sb.WriteString(fmt.Sprintf("- %s [%s] (id: %s)\n", e.Name, e.EntityType, e.ID))
	}
	sb.WriteString("\nTry searching with a specific name from the list above, or use query='*' to see all.")
	return NewResult(sb.String())
}
