package agent

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"time"

	"github.com/nextlevelbuilder/goclaw/internal/config"
	"github.com/nextlevelbuilder/goclaw/internal/providers"
)

// Default memory flush prompts matching TS memory-flush.ts.
const (
	DefaultMemoryFlushPrompt = "Pre-compaction memory flush. " +
		"Store durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed). " +
		"IMPORTANT: If the file already exists, APPEND new content only and do not overwrite existing entries. " +
		"If nothing to store, reply with NO_REPLY."

	DefaultMemoryFlushSystemPrompt = "Pre-compaction memory flush turn. " +
		"The session is near auto-compaction; capture durable memories to disk. " +
		"You may reply, but usually NO_REPLY is correct."
)

// MemoryFlushSettings holds resolved flush config with defaults applied.
type MemoryFlushSettings struct {
	Enabled      bool
	Prompt       string
	SystemPrompt string
}

// ResolveMemoryFlushSettings resolves flush settings from config, applying defaults.
// Returns nil if disabled.
func ResolveMemoryFlushSettings(compaction *config.CompactionConfig) *MemoryFlushSettings {
	if compaction == nil || compaction.MemoryFlush == nil {
		// Default: enabled
		return &MemoryFlushSettings{
			Enabled:      true,
			Prompt:       DefaultMemoryFlushPrompt,
			SystemPrompt: DefaultMemoryFlushSystemPrompt,
		}
	}

	mf := compaction.MemoryFlush
	if mf.Enabled != nil && !*mf.Enabled {
		return nil
	}

	settings := &MemoryFlushSettings{
		Enabled:      true,
		Prompt:       DefaultMemoryFlushPrompt,
		SystemPrompt: DefaultMemoryFlushSystemPrompt,
	}

	if mf.Prompt != "" {
		settings.Prompt = mf.Prompt
	}
	if mf.SystemPrompt != "" {
		settings.SystemPrompt = mf.SystemPrompt
	}

	return settings
}

// shouldRunMemoryFlush checks whether a memory flush should run before compaction.
// Flush always runs when compaction triggers (called inside maybeSummarize),
// gated only by enabled/memory checks and a dedup guard per compaction cycle.
func (l *Loop) shouldRunMemoryFlush(sessionKey string, totalTokens int, settings *MemoryFlushSettings) bool {
	if settings == nil || !settings.Enabled || !l.hasMemory {
		return false
	}

	if totalTokens <= 0 {
		return false
	}

	// Deduplication: skip if already flushed in this compaction cycle.
	compactionCount := l.sessions.GetCompactionCount(sessionKey)
	lastFlushAt := l.sessions.GetMemoryFlushCompactionCount(sessionKey)
	if lastFlushAt >= 0 && lastFlushAt == compactionCount {
		return false
	}

	return true
}

// runMemoryFlush executes a memory flush turn: sends flush prompt to LLM with tools
// so it can write memory files. Matching TS agent-runner-memory.ts.
func (l *Loop) runMemoryFlush(ctx context.Context, sessionKey string, settings *MemoryFlushSettings) {
	slog.Info("memory flush: starting", "session", sessionKey)

	flushCtx, cancel := context.WithTimeout(ctx, 90*time.Second)
	defer cancel()

	// Build messages: system prompt + history summary + flush prompt
	history := l.sessions.GetHistory(sessionKey)
	summary := l.sessions.GetSummary(sessionKey)

	var messages []providers.Message

	// System prompt: combine agent's normal system prompt context with flush system prompt
	systemPrompt := BuildSystemPrompt(SystemPromptConfig{
		AgentID:   l.id,
		Model:     l.model,
		Workspace: l.workspace,
		Mode:      PromptMinimal,
		ToolNames: l.filteredToolNames(),
		HasMemory: l.hasMemory,
	})
	systemPrompt += "\n\n" + settings.SystemPrompt

	messages = append(messages, providers.Message{
		Role:    "system",
		Content: systemPrompt,
	})

	// Include conversation summary for context
	if summary != "" {
		messages = append(messages, providers.Message{
			Role:    "user",
			Content: fmt.Sprintf("[Previous conversation summary]\n%s", summary),
		})
		messages = append(messages, providers.Message{
			Role:    "assistant",
			Content: "Understood.",
		})
	}

	// Include recent history (last 10 messages for context)
	recentHistory := history
	if len(recentHistory) > 10 {
		recentHistory = recentHistory[len(recentHistory)-10:]
	}
	sanitized, _ := sanitizeHistory(recentHistory)
	messages = append(messages, sanitized...)

	// Flush prompt
	messages = append(messages, providers.Message{
		Role:    "user",
		Content: settings.Prompt,
	})

	// Build tool list — only file tools needed for memory flush
	var toolDefs []providers.ToolDefinition
	if l.toolPolicy != nil {
		toolDefs = l.toolPolicy.FilterTools(l.tools, l.id, l.provider.Name(), nil, nil, false, false)
	} else {
		toolDefs = l.tools.ProviderDefs()
	}

	// Run LLM iteration loop (max 5 iterations for flush)
	maxFlushIter := 5
	for range maxFlushIter {
		resp, err := l.provider.Chat(flushCtx, providers.ChatRequest{
			Messages: messages,
			Tools:    toolDefs,
			Model:    l.model,
			Options: map[string]any{
				"max_tokens":  4096,
				"temperature": 0.3,
			},
		})
		if err != nil {
			slog.Warn("memory flush: LLM call failed", "error", err)
			break
		}

		// No tool calls → done
		if len(resp.ToolCalls) == 0 {
			content := SanitizeAssistantContent(resp.Content)
			if IsSilentReply(content) {
				slog.Info("memory flush: NO_REPLY (nothing to save)")
			} else if content != "" {
				slog.Info("memory flush: completed with response", "content_len", len(content))
			}
			break
		}

		// Process tool calls
		assistantMsg := providers.Message{
			Role:      "assistant",
			Content:   resp.Content,
			ToolCalls: resp.ToolCalls,
		}
		messages = append(messages, assistantMsg)

		for _, tc := range resp.ToolCalls {
			argsJSON, _ := json.Marshal(tc.Arguments)
			slog.Info("memory flush: tool call", "tool", tc.Name, "args_len", len(argsJSON))

			result := l.tools.ExecuteWithContext(flushCtx, tc.Name, tc.Arguments, "", "", "", sessionKey, nil)

			messages = append(messages, providers.Message{
				Role:       "tool",
				Content:    result.ForLLM,
				ToolCallID: tc.ID,
			})
		}
	}

	// Mark flush as done
	l.sessions.SetMemoryFlushDone(sessionKey)
	l.sessions.Save(sessionKey)

	slog.Info("memory flush: completed", "session", sessionKey)
}
