package seahorse

import (
	"context"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// ParseLastDuration parses a "last" duration string like "6h", "7d", "2w", "1m".
// Returns the duration and nil error, or zero and error if invalid.
func ParseLastDuration(s string) (time.Duration, error) {
	if s == "" {
		return 0, fmt.Errorf("empty duration")
	}

	re := regexp.MustCompile(`^(\d+)([hdwm])$`)
	matches := re.FindStringSubmatch(s)
	if matches == nil {
		return 0, fmt.Errorf("invalid duration format: %q (use format like 6h, 7d, 2w, 1m)", s)
	}

	value, _ := strconv.Atoi(matches[1])
	unit := matches[2]

	switch unit {
	case "h":
		return time.Duration(value) * time.Hour, nil
	case "d":
		return time.Duration(value) * 24 * time.Hour, nil
	case "w":
		return time.Duration(value) * 7 * 24 * time.Hour, nil
	case "m":
		return time.Duration(value) * 30 * 24 * time.Hour, nil
	default:
		return 0, fmt.Errorf("unknown unit: %q", unit)
	}
}

// GrepInput controls search across summaries and messages.
type GrepInput struct {
	Pattern          string     `json:"pattern"`
	Scope            string     `json:"scope,omitempty"` // "both" (default), "summary", or "message"
	Role             string     `json:"role,omitempty"`  // "user", "assistant", or "" (all)
	AllConversations bool       `json:"allConversations,omitempty"`
	Since            *time.Time `json:"since,omitempty"`
	Before           *time.Time `json:"before,omitempty"`
	Last             string     `json:"last,omitempty"` // shortcut: "6h", "7d", "2w", "1m"
	Limit            int        `json:"limit,omitempty"`
}

// GrepResult contains search results.
type GrepResult struct {
	Success        bool                `json:"success"`
	Summaries      []GrepSummaryResult `json:"summaries"`
	Messages       []GrepMessageResult `json:"messages"`
	TotalSummaries int                 `json:"totalSummaries"`
	TotalMessages  int                 `json:"totalMessages"`
	Hint           string              `json:"hint,omitempty"`
}

// GrepSummaryResult is a summary match from grep.
type GrepSummaryResult struct {
	ID             string      `json:"id"`
	Content        string      `json:"content"`
	Depth          int         `json:"depth"`
	Kind           SummaryKind `json:"kind"`
	ConversationID int64       `json:"conversationId"`
	// Rank is the bm25 relevance score (negative value, lower = better match).
	// Examples: -5.0 = excellent match, -2.0 = good match, -0.5 = partial match.
	Rank float64 `json:"rank,omitempty"`
}

// GrepMessageResult is a message match from grep.
type GrepMessageResult struct {
	ID             int64   `json:"id,string"`
	Snippet        string  `json:"snippet"`
	Role           string  `json:"role"`
	ConversationID int64   `json:"conversationId"`
	Rank           float64 `json:"rank,omitempty"` // Relevance score (more negative = better match)
}

// ExpandMessagesResult contains expanded messages.
type ExpandMessagesResult struct {
	Messages   []Message `json:"messages"`
	TokenCount int       `json:"tokenCount"`
}

// Grep searches summaries and messages for matching content.
func (r *RetrievalEngine) Grep(ctx context.Context, input GrepInput) (*GrepResult, error) {
	if input.Pattern == "" {
		return nil, fmt.Errorf("grep: pattern is required")
	}

	limit := input.Limit
	if limit == 0 {
		limit = 20
	}

	// Handle Last parameter: convert to Since
	since := input.Since
	if input.Last != "" {
		dur, err := ParseLastDuration(input.Last)
		if err != nil {
			return nil, fmt.Errorf("grep: invalid last: %w", err)
		}
		t := time.Now().UTC().Add(-dur)
		since = &t
	}

	// Auto-detect mode: use LIKE if pattern contains %, otherwise full-text
	mode := ""
	if strings.Contains(input.Pattern, "%") {
		mode = "like"
	}

	searchInput := SearchInput{
		Pattern:          input.Pattern,
		Mode:             mode,
		Role:             input.Role,
		AllConversations: input.AllConversations,
		Since:            since,
		Before:           input.Before,
		Limit:            limit,
	}

	result := &GrepResult{
		Success:        true,
		Summaries:      make([]GrepSummaryResult, 0),
		Messages:       make([]GrepMessageResult, 0),
		TotalSummaries: 0,
		TotalMessages:  0,
	}

	// Determine scope
	scope := input.Scope
	if scope == "" {
		scope = "both"
	}

	// Search summaries if requested
	if scope == "both" || scope == "summary" {
		sumResults, err := r.store.SearchSummaries(ctx, searchInput)
		if err != nil {
			return nil, fmt.Errorf("search summaries: %w", err)
		}
		for _, sr := range sumResults {
			if sr.SummaryID != "" {
				result.Summaries = append(result.Summaries, GrepSummaryResult{
					ID:             sr.SummaryID,
					Content:        sr.Content,
					Depth:          sr.Depth,
					Kind:           sr.Kind,
					ConversationID: sr.ConversationID,
					Rank:           sr.Rank,
				})
			}
		}
		if len(sumResults) > 0 {
			result.TotalSummaries = sumResults[0].TotalCount
		}
	}

	// Search messages if requested
	if scope == "both" || scope == "message" {
		msgResults, err := r.store.SearchMessages(ctx, searchInput)
		if err != nil {
			return nil, fmt.Errorf("search messages: %w", err)
		}
		for _, sr := range msgResults {
			if sr.MessageID > 0 {
				result.Messages = append(result.Messages, GrepMessageResult{
					ID:             sr.MessageID,
					Snippet:        sr.Snippet,
					Role:           sr.Role,
					ConversationID: sr.ConversationID,
					Rank:           sr.Rank,
				})
			}
		}
		if len(msgResults) > 0 {
			result.TotalMessages = msgResults[0].TotalCount
		}
	}

	// Add hint if no results
	if len(result.Summaries) == 0 && len(result.Messages) == 0 {
		result.Hint = "No matches. Try: %keyword% for fuzzy search, or all_conversations: true"
	}

	return result, nil
}

// ExpandMessages retrieves full message content by IDs.
func (r *RetrievalEngine) ExpandMessages(ctx context.Context, messageIDs []int64) (*ExpandMessagesResult, error) {
	result := &ExpandMessagesResult{
		Messages: make([]Message, 0, len(messageIDs)),
	}

	for _, msgID := range messageIDs {
		msg, err := r.store.GetMessageByID(ctx, msgID)
		if err != nil {
			continue
		}
		result.Messages = append(result.Messages, *msg)
		result.TokenCount += msg.TokenCount
	}

	return result, nil
}
