package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"path/filepath"
	"strings"

	"chat-switchboard/knowledge"
	"chat-switchboard/models"
	"chat-switchboard/store"
)

// ── workspace_search ────────────────────────

type workspaceSearchTool struct {
	workspaceToolBase
	stores   store.Stores
	embedder *knowledge.Embedder
}

func (t *workspaceSearchTool) Definition() ToolDef {
	return ToolDef{
		Name:        "workspace_search",
		DisplayName: "Workspace Search",
		Description: "Semantic search across workspace files. Finds code, documentation, and text content relevant to a natural language query. Returns matching file paths, content snippets, relevance scores, and approximate line numbers.",
		Category:    "workspace",
		Parameters: JSONSchema(map[string]interface{}{
			"query": Prop("string", "Natural language search query (e.g. 'database connection pooling', 'error handling middleware')"),
			"top_k": map[string]interface{}{
				"type":        "integer",
				"description": "Maximum number of results to return (default 10, max 20)",
				"default":     10,
			},
			"file_pattern": Prop("string", "Optional glob pattern to filter results by file path (e.g. '*.go', 'src/*.ts'). Applied post-search."),
		}, []string{"query"}),
	}
}

func (t *workspaceSearchTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Query       string `json:"query"`
		TopK        int    `json:"top_k"`
		FilePattern string `json:"file_pattern"`
	}
	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
		return "", fmt.Errorf("invalid arguments: %w", err)
	}

	if args.Query == "" {
		return "", fmt.Errorf("query is required")
	}
	if args.TopK <= 0 {
		args.TopK = 10
	}
	if args.TopK > 20 {
		args.TopK = 20
	}

	// Resolve workspace
	w, err := loadWorkspace(ctx, t.stores, execCtx)
	if err != nil {
		return "", err
	}

	if !w.IndexingEnabled {
		return "", fmt.Errorf("workspace indexing is disabled for this workspace")
	}

	// Embed the query
	embedResult, err := t.embedder.EmbedChunks(ctx, execCtx.UserID, nil, []string{args.Query})
	if err != nil {
		return "", fmt.Errorf("failed to embed query: %w", err)
	}
	if len(embedResult.Vectors) == 0 {
		return "", fmt.Errorf("embedding returned no vectors")
	}

	queryVec := embedResult.Vectors[0]

	// Fetch more if we'll post-filter by glob
	searchLimit := args.TopK
	if args.FilePattern != "" {
		searchLimit = args.TopK * 3
		if searchLimit > 50 {
			searchLimit = 50
		}
	}

	results, err := t.stores.Workspaces.SimilaritySearch(ctx, w.ID, queryVec, 0.3, searchLimit)
	if err != nil {
		return "", fmt.Errorf("search failed: %w", err)
	}

	// Apply glob filter if specified
	if args.FilePattern != "" {
		results = filterByGlob(results, args.FilePattern)
	}

	// Cap at top_k
	if len(results) > args.TopK {
		results = results[:args.TopK]
	}

	// Format response
	formatted := make([]map[string]interface{}, len(results))
	for i, r := range results {
		m := map[string]interface{}{
			"file_path": r.FilePath,
			"content":   r.Content,
			"score":     fmt.Sprintf("%.3f", r.Score),
		}
		if r.LineHint > 0 {
			m["line_hint"] = r.LineHint
		}
		formatted[i] = m
	}

	resp := map[string]interface{}{
		"query":   args.Query,
		"count":   len(formatted),
		"results": formatted,
	}
	if args.FilePattern != "" {
		resp["file_pattern"] = args.FilePattern
	}
	if len(formatted) == 0 {
		resp["message"] = "No matching results found. Try a different query or check that workspace files have been indexed."
	}

	b, _ := json.Marshal(resp)
	return string(b), nil
}

// filterByGlob filters search results by a glob pattern.
func filterByGlob(results []models.WorkspaceChunkResult, pattern string) []models.WorkspaceChunkResult {
	var filtered []models.WorkspaceChunkResult
	for _, r := range results {
		// Try matching against basename
		matched, _ := filepath.Match(pattern, filepath.Base(r.FilePath))
		if !matched && strings.Contains(pattern, "/") {
			// Try full path match if pattern contains directory separator
			matched, _ = filepath.Match(pattern, r.FilePath)
		}
		if matched {
			filtered = append(filtered, r)
		}
	}
	return filtered
}
