package tools

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gokin/internal/memory"

	"google.golang.org/genai"
)

// MemoryTool provides persistent memory storage between sessions.
type MemoryTool struct {
	store *memory.Store
}

// NewMemoryTool creates a new memory tool.
func NewMemoryTool() *MemoryTool {
	return &MemoryTool{}
}

// SetStore sets the memory store.
func (t *MemoryTool) SetStore(store *memory.Store) {
	t.store = store
}

func (t *MemoryTool) Name() string {
	return "memory"
}

func (t *MemoryTool) Description() string {
	return "Persistent memory storage for remembering and recalling information across sessions"
}

func (t *MemoryTool) Declaration() *genai.FunctionDeclaration {
	return &genai.FunctionDeclaration{
		Name:        t.Name(),
		Description: t.Description(),
		Parameters: &genai.Schema{
			Type: genai.TypeObject,
			Properties: map[string]*genai.Schema{
				"action": {
					Type:        genai.TypeString,
					Description: "Action to perform: 'remember' (save), 'recall' (retrieve), 'forget' (delete), 'list' (show all), 'feedback' (mark retrieval success/failure)",
					Enum:        []string{"remember", "recall", "forget", "list", "feedback"},
				},
				"content": {
					Type:        genai.TypeString,
					Description: "Content to remember (required for 'remember' action)",
				},
				"key": {
					Type:        genai.TypeString,
					Description: "Optional key to identify the memory. Makes it easier to recall or update later",
				},
				"query": {
					Type:        genai.TypeString,
					Description: "Search query for 'recall' action (searches in content and key)",
				},
				"tags": {
					Type:        genai.TypeArray,
					Items:       &genai.Schema{Type: genai.TypeString},
					Description: "Tags for organizing memories. Use for filtering in 'recall'",
				},
				"scope": {
					Type:        genai.TypeString,
					Description: "Scope of the memory: 'session' (current session only), 'project' (this repository only), 'global' (all projects). Default: 'project'",
					Enum:        []string{"session", "project", "global"},
				},
				"id": {
					Type:        genai.TypeString,
					Description: "Memory ID for 'forget' action",
				},
				"project_only": {
					Type:        genai.TypeBoolean,
					Description: "If true, only show/search memories for current project (default: false for 'recall' and 'list')",
				},
				"ttl_minutes": {
					Type:        genai.TypeInteger,
					Description: "Optional TTL in minutes for remembered memory. After expiration, memory is archived.",
				},
				"include_archived": {
					Type:        genai.TypeBoolean,
					Description: "Include archived memories in search/list results (default: false)",
				},
				"success": {
					Type:        genai.TypeBoolean,
					Description: "For 'feedback' action: true if retrieved memory was useful, false otherwise.",
				},
			},
			Required: []string{"action"},
		},
	}
}

func (t *MemoryTool) Validate(args map[string]any) error {
	action, ok := GetString(args, "action")
	if !ok {
		return NewValidationError("action", "action is required")
	}

	switch action {
	case "remember":
		if _, ok := GetString(args, "content"); !ok {
			return NewValidationError("content", "content is required for 'remember' action")
		}
	case "forget":
		// Need either id or key
		_, hasID := GetString(args, "id")
		_, hasKey := GetString(args, "key")
		if !hasID && !hasKey {
			return NewValidationError("id", "either 'id' or 'key' is required for 'forget' action")
		}
	case "feedback":
		_, hasID := GetString(args, "id")
		_, hasKey := GetString(args, "key")
		if !hasID && !hasKey {
			return NewValidationError("id", "either 'id' or 'key' is required for 'feedback' action")
		}
		if _, ok := GetBool(args, "success"); !ok {
			return NewValidationError("success", "success is required for 'feedback' action")
		}
	case "recall", "list":
		// No required parameters
	default:
		return NewValidationError("action", "invalid action: "+action)
	}

	return nil
}

func (t *MemoryTool) Execute(ctx context.Context, args map[string]any) (ToolResult, error) {
	if t.store == nil {
		return NewErrorResult("memory store not configured"), nil
	}

	action, _ := GetString(args, "action")

	switch action {
	case "remember":
		result, err := t.remember(args)
		if err == nil && result.Success {
			EmitMemoryNotify(ctx, "saved", truncate(GetStringDefault(args, "content", ""), 60))
		}
		return result, err
	case "recall":
		result, err := t.recall(args)
		if err == nil && result.Success {
			EmitMemoryNotify(ctx, "recalled", truncate(GetStringDefault(args, "query", ""), 40))
		}
		return result, err
	case "forget":
		result, err := t.forget(args)
		if err == nil && result.Success {
			EmitMemoryNotify(ctx, "forgotten", "")
		}
		return result, err
	case "list":
		return t.list(args)
	case "feedback":
		return t.feedback(args)
	default:
		return NewErrorResult("invalid action: " + action), nil
	}
}

// maxMemoryTTLMinutes caps a memory TTL at ~1 year. Without a bound, a large
// ttl_minutes (e.g. 999999999) overflows time.Duration(n)*time.Minute past
// int64, wrapping to a NEGATIVE duration — the entry would expire instantly
// instead of "effectively never".
const maxMemoryTTLMinutes = 525600 // 365 days

// clampTTLMinutes bounds a model-supplied TTL to [0, maxMemoryTTLMinutes].
func clampTTLMinutes(m int) int {
	if m < 0 {
		return 0
	}
	if m > maxMemoryTTLMinutes {
		return maxMemoryTTLMinutes
	}
	return m
}

func (t *MemoryTool) remember(args map[string]any) (ToolResult, error) {
	content, _ := GetString(args, "content")
	key, _ := GetString(args, "key")
	tagsRaw := args["tags"]
	ttlMinutes := GetIntDefault(args, "ttl_minutes", 0)

	// Parse scope — all memories are scoped to the current project directory.
	// Global scope is disabled to prevent cross-project memory leaks.
	scopeName, _ := GetString(args, "scope")
	memType := memory.MemoryProject
	switch scopeName {
	case "session":
		memType = memory.MemorySession
	case "global":
		// Redirect global to project — memories should stay within the working directory
		memType = memory.MemoryProject
	}

	// Create entry
	entry := memory.NewEntry(content, memType)
	if key != "" {
		entry.WithKey(key)
	}
	if ttlMinutes > 0 {
		entry.WithTTL(time.Duration(clampTTLMinutes(ttlMinutes)) * time.Minute)
	}

	// Parse tags
	if tagsRaw != nil {
		if tagsSlice, ok := tagsRaw.([]any); ok {
			tags := make([]string, 0, len(tagsSlice))
			for _, t := range tagsSlice {
				if s, ok := t.(string); ok {
					tags = append(tags, s)
				}
			}
			entry.WithTags(tags)
		}
	}

	// Save entry
	if err := t.store.Add(entry); err != nil {
		return NewErrorResult(fmt.Sprintf("failed to save memory: %s", err)), nil
	}

	var msg string
	if key != "" {
		msg = fmt.Sprintf("Remembered: %q with key %q", truncate(content, 100), key)
	} else {
		msg = fmt.Sprintf("Remembered: %q (id: %s)", truncate(content, 100), entry.ID)
	}

	return NewSuccessResultWithData(msg, map[string]any{
		"id":      entry.ID,
		"key":     entry.Key,
		"content": entry.Content,
		"type":    entry.Type,
		"tags":    entry.Tags,
		"expires": entry.ExpiresAt,
	}), nil
}

func (t *MemoryTool) recall(args map[string]any) (ToolResult, error) {
	key, _ := GetString(args, "key")
	query, _ := GetString(args, "query")
	tagsRaw := args["tags"]
	projectOnly := GetBoolDefault(args, "project_only", true) // Default: only current directory
	includeArchived := GetBoolDefault(args, "include_archived", false)

	// If key is specified, try exact match first
	if key != "" {
		if entry, ok := t.store.Get(key); ok {
			t.store.RecordAccess(entry.ID)
			return NewSuccessResultWithData(
				fmt.Sprintf("Found memory with key %q: %s", key, entry.Content),
				map[string]any{
					"id":      entry.ID,
					"key":     entry.Key,
					"content": entry.Content,
					"tags":    entry.Tags,
					"found":   true,
				},
			), nil
		}
	}

	// Build search query
	searchQuery := memory.SearchQuery{
		Key:             key,
		Query:           query,
		ProjectOnly:     projectOnly,
		Limit:           20, // Reasonable default
		IncludeArchived: includeArchived,
	}

	// Parse tags
	if tagsRaw != nil {
		if tagsSlice, ok := tagsRaw.([]any); ok {
			for _, t := range tagsSlice {
				if s, ok := t.(string); ok {
					searchQuery.Tags = append(searchQuery.Tags, s)
				}
			}
		}
	}

	results := t.store.Search(searchQuery)
	for i, entry := range results {
		if i >= 3 {
			break
		}
		t.store.RecordAccess(entry.ID)
	}

	if len(results) == 0 {
		return NewSuccessResultWithData("No memories found", map[string]any{
			"found": false,
			"count": 0,
		}), nil
	}

	// Build results string by type
	byType := make(map[memory.MemoryType][]*memory.Entry)
	for _, entry := range results {
		byType[entry.Type] = append(byType[entry.Type], entry)
	}

	var builder strings.Builder
	fmt.Fprintf(&builder, "Found %d memories:\n\n", len(results))

	types := []struct {
		t     memory.MemoryType
		label string
	}{
		{memory.MemorySession, "Session"},
		{memory.MemoryProject, "Project"},
		{memory.MemoryGlobal, "Global"},
	}

	resultData := make([]map[string]any, 0, len(results))
	for _, tc := range types {
		if items, ok := byType[tc.t]; ok && len(items) > 0 {
			fmt.Fprintf(&builder, "[%s Memories]\n", tc.label)
			for _, entry := range items {
				if entry.Key != "" {
					fmt.Fprintf(&builder, "- [%s] %s\n", entry.Key, entry.Content)
				} else {
					fmt.Fprintf(&builder, "- %s\n", entry.Content)
				}
				resultData = append(resultData, map[string]any{
					"id":           entry.ID,
					"key":          entry.Key,
					"content":      entry.Content,
					"type":         entry.Type,
					"tags":         entry.Tags,
					"archived":     entry.Archived,
					"success_rate": entry.SuccessRate(),
				})
			}
			builder.WriteString("\n")
		}
	}

	return NewSuccessResultWithData(builder.String(), map[string]any{
		"found":   true,
		"count":   len(results),
		"results": resultData,
	}), nil
}

func (t *MemoryTool) forget(args map[string]any) (ToolResult, error) {
	id, _ := GetString(args, "id")
	key, _ := GetString(args, "key")

	target := id
	if target == "" {
		target = key
	}

	if t.store.Remove(target) {
		return NewSuccessResult(fmt.Sprintf("Forgot memory: %s", target)), nil
	}

	return NewErrorResult(fmt.Sprintf("Memory not found: %s", target)), nil
}

func (t *MemoryTool) list(args map[string]any) (ToolResult, error) {
	projectOnly := GetBoolDefault(args, "project_only", true) // Default: only current directory
	includeArchived := GetBoolDefault(args, "include_archived", false)

	var entries []*memory.Entry
	if includeArchived {
		entries = t.store.Search(memory.SearchQuery{
			ProjectOnly:     projectOnly,
			IncludeArchived: true,
			Limit:           200,
		})
	} else {
		entries = t.store.List(projectOnly)
	}

	if len(entries) == 0 {
		scope := "global"
		if projectOnly {
			scope = "project"
		}
		return NewSuccessResultWithData(
			fmt.Sprintf("No memories stored (%s scope)", scope),
			map[string]any{"count": 0},
		), nil
	}

	// Build results string by type
	byType := make(map[memory.MemoryType][]*memory.Entry)
	for _, entry := range entries {
		byType[entry.Type] = append(byType[entry.Type], entry)
	}

	var builder strings.Builder
	fmt.Fprintf(&builder, "Stored memories (%d total):\n\n", len(entries))

	types := []struct {
		t     memory.MemoryType
		label string
	}{
		{memory.MemorySession, "Session"},
		{memory.MemoryProject, "Project"},
		{memory.MemoryGlobal, "Global"},
	}

	resultData := make([]map[string]any, 0, len(entries))
	for _, tc := range types {
		if items, ok := byType[tc.t]; ok && len(items) > 0 {
			fmt.Fprintf(&builder, "[%s Memories]\n", tc.label)
			for _, entry := range items {
				if entry.Key != "" {
					fmt.Fprintf(&builder, "- [%s] %s (id: %s)\n", entry.Key, truncate(entry.Content, 60), entry.ID)
				} else {
					fmt.Fprintf(&builder, "- %s (id: %s)\n", truncate(entry.Content, 60), entry.ID)
				}
				resultData = append(resultData, map[string]any{
					"id":           entry.ID,
					"key":          entry.Key,
					"content":      entry.Content,
					"type":         entry.Type,
					"tags":         entry.Tags,
					"archived":     entry.Archived,
					"success_rate": entry.SuccessRate(),
				})
			}
			builder.WriteString("\n")
		}
	}

	return NewSuccessResultWithData(builder.String(), map[string]any{
		"count":   len(entries),
		"entries": resultData,
	}), nil
}

func (t *MemoryTool) feedback(args map[string]any) (ToolResult, error) {
	id, _ := GetString(args, "id")
	key, _ := GetString(args, "key")
	success, _ := GetBool(args, "success")

	targetID := id
	if targetID == "" && key != "" {
		entry, ok := t.store.Get(key)
		if !ok {
			return NewErrorResult(fmt.Sprintf("Memory not found by key: %s", key)), nil
		}
		targetID = entry.ID
	}

	if targetID == "" {
		return NewErrorResult("missing memory id/key for feedback"), nil
	}

	if ok := t.store.RecordFeedback(targetID, success); !ok {
		return NewErrorResult(fmt.Sprintf("Memory not found: %s", targetID)), nil
	}

	outcome := "failure"
	if success {
		outcome = "success"
	}
	return NewSuccessResult(fmt.Sprintf("Recorded memory feedback (%s): %s", outcome, targetID)), nil
}

// truncate truncates a string to the specified length (rune-safe).
func truncate(s string, maxLen int) string {
	runes := []rune(s)
	if maxLen <= 0 || len(runes) <= maxLen {
		return s
	}
	if maxLen <= 3 {
		return string(runes[:maxLen]) // no room for ellipsis; avoids maxLen-3 underflow panic
	}
	return string(runes[:maxLen-3]) + "..."
}
