package synap

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

// ChatMessage represents a single chat message for context building.
type ChatMessage struct {
	// ID is the message identifier
	ID string

	// Role is "user" or "agent"
	Role string

	// Content is the message text
	Content string

	// CreatedAt is when the message was sent
	CreatedAt time.Time

	// TokenCount estimates tokens in content (for 2000 token limit)
	TokenCount int
}

// ChatContext holds both recent messages and recalled memories for AI response generation.
type ChatContext struct {
	// RecentMessages contains the last N messages (up to 2000 tokens)
	RecentMessages []ChatMessage

	// RecalledMemories contains relevant memories from Synap
	RecalledMemories []Memory

	// TotalTokens is the estimated token count for all content
	TotalTokens int

	// MemoryContext is a formatted string of recalled memories for prompt injection
	MemoryContext string
}

// BuildChatContext creates context for an AI response by combining recent messages
// and recalled memories from Synap.
//
// This function:
// 1. Includes the last 20 messages (or as many as fit in 2000 tokens)
// 2. Recalls relevant memories from Synap based on the current message
// 3. Formats everything into a ChatContext for prompt injection
//
// Example usage in peach-connection-worker:
//
//	ctx := context.Background()
//	client, err := synap.NewClient("http://localhost:7432", &synap.Config{
//	    DefaultSpace: fmt.Sprintf("conversation_%s", conversationID),
//	})
//	if err != nil {
//	    log.Fatal(err)
//	}
//
//	chatCtx, err := synap.BuildChatContext(ctx, client, &synap.BuildContextRequest{
//	    CurrentMessage: "What hobbies do you enjoy?",
//	    RecentMessages: last20Messages,
//	    MaxTokens:      2000,
//	    RecallQuery:    "hobbies interests activities enjoyment",
//	})
//
//	// Use chatCtx.MemoryContext in your Comm10 template variables
//	variables := map[string]string{
//	    "recalled_memories": chatCtx.MemoryContext,
//	    "recent_context":    formatMessages(chatCtx.RecentMessages),
//	}
func BuildChatContext(ctx context.Context, client *Client, req *BuildContextRequest) (*ChatContext, error) {
	if req == nil {
		return nil, fmt.Errorf("request is required")
	}

	// 1. Select recent messages that fit within token budget
	recentMessages := selectRecentMessages(req.RecentMessages, req.MaxTokens)

	// 2. Recall relevant memories from Synap
	var recalledMemories []Memory
	var memoryContext string

	if req.RecallQuery != "" {
		recallResp, err := client.Recall(ctx, &RecallRequest{
			Query:      req.RecallQuery,
			Mode:       RecallModeHybrid,
			MaxResults: req.MaxRecalledMemories,
			Threshold:  req.RecallThreshold,
		})

		if err != nil {
			// Log error but don't fail - proceed without recalled memories
			// This matches the timeout handling in the architecture docs
			client.logger.Warn("failed to recall memories, proceeding without context",
				"error", err,
				"query", req.RecallQuery)
		} else {
			// Combine all memory categories
			recalledMemories = append(recalledMemories, recallResp.Memories.Vivid...)
			recalledMemories = append(recalledMemories, recallResp.Memories.Associated...)

			// Format memories for prompt injection
			memoryContext = formatMemoriesForPrompt(recalledMemories)
		}
	}

	// 3. Calculate total tokens
	totalTokens := 0
	for _, msg := range recentMessages {
		totalTokens += msg.TokenCount
	}

	// Add rough token count for memory context (4 chars ≈ 1 token)
	totalTokens += len(memoryContext) / 4

	return &ChatContext{
		RecentMessages:   recentMessages,
		RecalledMemories: recalledMemories,
		TotalTokens:      totalTokens,
		MemoryContext:    memoryContext,
	}, nil
}

// BuildContextRequest specifies parameters for building chat context.
type BuildContextRequest struct {
	// CurrentMessage is the user's current message (used for memory recall)
	CurrentMessage string

	// RecentMessages is the conversation history (newest last)
	// Should be the last 20-30 messages to allow token-based filtering
	RecentMessages []ChatMessage

	// MaxTokens is the maximum tokens for recent messages (default: 2000)
	// Memories from Synap are NOT included in this limit
	MaxTokens int

	// RecallQuery is the search query for Synap (optional)
	// If empty, no memory recall is performed
	// Typically this is the current message + keywords from recent context
	RecallQuery string

	// MaxRecalledMemories limits Synap recall results (default: 5)
	MaxRecalledMemories int

	// RecallThreshold is minimum confidence for recalled memories (default: 0.5)
	RecallThreshold float64
}

// selectRecentMessages filters messages to fit within token budget.
//
// Returns messages in chronological order (oldest first), limited by token count.
func selectRecentMessages(messages []ChatMessage, maxTokens int) []ChatMessage {
	if maxTokens == 0 {
		maxTokens = 2000
	}

	// Start from newest messages and work backwards
	totalTokens := 0
	cutoffIndex := 0

	for i := len(messages) - 1; i >= 0; i-- {
		msgTokens := messages[i].TokenCount
		if msgTokens == 0 {
			// Estimate: ~4 characters per token
			msgTokens = len(messages[i].Content) / 4
		}

		if totalTokens+msgTokens > maxTokens {
			cutoffIndex = i + 1
			break
		}

		totalTokens += msgTokens
	}

	// Return selected messages in chronological order
	return messages[cutoffIndex:]
}

// formatMemoriesForPrompt converts Synap memories into a prompt-friendly format.
//
// Example output:
//
//	"Previous relevant memories:
//	- (2 days ago) User mentioned they love hiking and photography
//	- (1 week ago) User discussed favorite travel destinations: Japan, Iceland
//	- (Related) User enjoys outdoor activities and nature"
func formatMemoriesForPrompt(memories []Memory) string {
	if len(memories) == 0 {
		return ""
	}

	var parts []string
	parts = append(parts, "Previous relevant memories:")

	for _, mem := range memories {
		// Format time reference
		var timeRef string
		if !mem.ObservedAt.IsZero() {
			timeRef = formatTimeReference(mem.ObservedAt)
		} else {
			timeRef = "Previously"
		}

		// Get memory content
		content := mem.Content
		if mem.Episode != nil {
			content = mem.Episode.What
		}

		// Format with confidence indicator if medium/low
		confidenceIndicator := ""
		if mem.Confidence.Category == "Medium" || mem.Confidence.Category == "Low" {
			confidenceIndicator = " (uncertain)"
		}

		parts = append(parts, fmt.Sprintf("- (%s) %s%s", timeRef, content, confidenceIndicator))
	}

	return strings.Join(parts, "\n")
}

// formatTimeReference converts a timestamp to human-readable relative time.
//
// Examples: "2 hours ago", "3 days ago", "2 weeks ago"
func formatTimeReference(t time.Time) string {
	duration := time.Since(t)

	if duration < time.Hour {
		minutes := int(duration.Minutes())
		if minutes == 1 {
			return "1 minute ago"
		}
		return fmt.Sprintf("%d minutes ago", minutes)
	}

	if duration < 24*time.Hour {
		hours := int(duration.Hours())
		if hours == 1 {
			return "1 hour ago"
		}
		return fmt.Sprintf("%d hours ago", hours)
	}

	if duration < 7*24*time.Hour {
		days := int(duration.Hours() / 24)
		if days == 1 {
			return "1 day ago"
		}
		return fmt.Sprintf("%d days ago", days)
	}

	if duration < 30*24*time.Hour {
		weeks := int(duration.Hours() / (24 * 7))
		if weeks == 1 {
			return "1 week ago"
		}
		return fmt.Sprintf("%d weeks ago", weeks)
	}

	months := int(duration.Hours() / (24 * 30))
	if months == 1 {
		return "1 month ago"
	}
	return fmt.Sprintf("%d months ago", months)
}

// StoreConversationTurn stores both user and agent messages as episodic memories in Synap.
//
// This should be called by peach-connection-worker after successfully generating
// and storing an agent response.
//
// Example usage:
//
//	err := synap.StoreConversationTurn(ctx, client, &synap.ConversationTurn{
//	    UserMessage:    "What are your favorite hobbies?",
//	    AgentResponse:  "I love photography and hiking in nature...",
//	    Timestamp:      time.Now(),
//	    ConversationID: "conv_abc123",
//	    UserID:         "user_alice",
//	    AgentID:        "agent_sarah",
//	})
func StoreConversationTurn(ctx context.Context, client *Client, turn *ConversationTurn) error {
	if turn == nil {
		return fmt.Errorf("turn is required")
	}

	// Store user message as episode
	userEpisode := &Episode{
		What:       fmt.Sprintf("User said: %s", turn.UserMessage),
		When:       turn.Timestamp,
		Where:      "chat",
		Who:        []string{turn.UserID, turn.AgentID},
		Why:        "conversation",
		Confidence: 0.9,
		Tags:       []string{"user_message", "conversation"},
	}

	userResp, err := client.RememberEpisode(ctx, userEpisode)
	if err != nil {
		return fmt.Errorf("store user message: %w", err)
	}

	client.logger.Debug("stored user message in synap",
		"memory_id", userResp.MemoryID,
		"conversation_id", turn.ConversationID)

	// Store agent response as episode
	agentEpisode := &Episode{
		What:       fmt.Sprintf("Agent responded: %s", turn.AgentResponse),
		When:       turn.Timestamp,
		Where:      "chat",
		Who:        []string{turn.UserID, turn.AgentID},
		Why:        "conversation",
		Confidence: 0.9,
		Tags:       []string{"agent_message", "conversation"},
	}

	agentResp, err := client.RememberEpisode(ctx, agentEpisode)
	if err != nil {
		return fmt.Errorf("store agent response: %w", err)
	}

	client.logger.Debug("stored agent response in synap",
		"memory_id", agentResp.MemoryID,
		"conversation_id", turn.ConversationID)

	return nil
}

// ConversationTurn represents a single user-agent exchange for storage in Synap.
type ConversationTurn struct {
	// UserMessage is what the user said
	UserMessage string

	// AgentResponse is what the agent replied
	AgentResponse string

	// Timestamp is when this exchange occurred
	Timestamp time.Time

	// ConversationID identifies the conversation (for logging only)
	ConversationID string

	// UserID identifies the user
	UserID string

	// AgentID identifies the agent
	AgentID string
}

// EstimateTokenCount provides a rough token count estimate for text.
//
// Uses the approximation: 1 token ≈ 4 characters (common for English text)
// For more accuracy, integrate with tiktoken or your LLM's tokenizer.
func EstimateTokenCount(text string) int {
	return len(text) / 4
}
