package agent

import (
	"fmt"
	"log/slog"
	"strings"

	"github.com/nextlevelbuilder/goclaw/internal/providers"
	"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)

// InjectedMessage represents a user message injected into a running agent loop
// at the turn boundary (after tool results, before next LLM call).
type InjectedMessage struct {
	Content string
	UserID  string
}

// processedInjection holds the two message forms: one for the LLM (with context wrapper)
// and one for session persistence (original content).
type processedInjection struct {
	forLLM     providers.Message // wrapped with "[User sent a follow-up...]" prefix
	forSession providers.Message // original content for session history
}

// injectBufferSize is the capacity of the per-run injection channel.
const injectBufferSize = 5

// processInjectedMessage validates and wraps an injected message for the LLM.
// Returns nil, false if the message should be skipped (blocked by input guard).
func (l *Loop) processInjectedMessage(injected InjectedMessage, emitRun func(AgentEvent)) (*processedInjection, bool) {
	// Security: scan injected content with input guard
	if l.inputGuard != nil {
		if matches := l.inputGuard.Scan(injected.Content); len(matches) > 0 {
			matchStr := strings.Join(matches, ",")
			if l.injectionAction == "block" {
				slog.Warn("security.injection_blocked_midrun",
					"agent", l.id, "user", injected.UserID,
					"patterns", matchStr)
				return nil, false
			}
			slog.Warn("security.injection_detected_midrun",
				"agent", l.id, "user", injected.UserID,
				"patterns", matchStr)
		}
	}

	// Truncate oversized content
	content := injected.Content
	maxChars := l.maxMessageChars
	if maxChars <= 0 {
		maxChars = 32000
	}
	if len(content) > maxChars {
		content = content[:maxChars] + "\n[Message truncated]"
	}

	// Wrap with context hint so LLM knows this is a mid-run follow-up
	wrapped := fmt.Sprintf("[User sent a follow-up message while you were working]\n%s", content)

	slog.Info("mid-run injection",
		"agent", l.id, "user", injected.UserID,
		"msg_len", len(content))

	// Emit activity event so UI/channels know about the injection
	if emitRun != nil {
		emitRun(AgentEvent{
			Type:    protocol.AgentEventActivity,
			AgentID: l.id,
			Payload: map[string]any{
				"phase":   "injected_message",
				"content": truncateForLog(content, 200),
			},
		})
	}

	return &processedInjection{
		forLLM:     providers.Message{Role: "user", Content: wrapped},
		forSession: providers.Message{Role: "user", Content: content},
	}, true
}

// drainInjectChannel reads all available messages from the injection channel
// without blocking. Returns processed messages ready to append to the loop.
func (l *Loop) drainInjectChannel(ch <-chan InjectedMessage, emitRun func(AgentEvent)) (forLLM, forSession []providers.Message) {
	if ch == nil {
		return nil, nil
	}
	for {
		select {
		case injected := <-ch:
			if result, ok := l.processInjectedMessage(injected, emitRun); ok {
				forLLM = append(forLLM, result.forLLM)
				forSession = append(forSession, result.forSession)
			}
		default:
			return forLLM, forSession
		}
	}
}

// truncateForLog truncates a string for log/event payloads.
func truncateForLog(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}
