package overlay

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/standardbeagle/agnt/internal/aichannel"
	"github.com/standardbeagle/agnt/internal/protocol"
)

// Summarizer aggregates system status and uses an AI channel to generate summaries.
// It uses a shared DaemonClient for all requests.
type Summarizer struct {
	conn        DaemonClient
	channel     *aichannel.Channel
	debugOutput io.Writer
	projectPath string // Current project directory for filtering processes/proxies
}

// SummarizerConfig configures the Summarizer.
type SummarizerConfig struct {
	// Agent type for AI summarization
	Agent aichannel.AgentType
	// Optional custom command (overrides agent default)
	Command string
	// Optional additional args
	Args []string
	// Timeout for AI response (default 2 minutes)
	Timeout time.Duration
	// DebugOutput is where debug messages are written (defaults to os.Stderr)
	DebugOutput io.Writer
	// ProjectPath is the current project directory for filtering processes/proxies
	ProjectPath string

	// --- API Mode Configuration ---
	// UseAPI forces API mode. For non-Claude agents, API mode is enabled automatically
	// since Claude Code Max plan only supports CLI, while other providers need API keys.
	// Set to true to force API mode even for Claude agent.
	UseAPI bool
	// LLMProvider specifies which provider to use (auto-detected if empty)
	LLMProvider aichannel.LLMProvider
	// APIKey overrides environment variable lookup
	APIKey string
	// Model overrides the provider's default model
	Model string
}

// NewSummarizer creates a new Summarizer using a shared connection.
// For Claude agent, uses CLI mode (required for Claude Code Max plan).
// For all other agents, automatically uses Anthropic API mode as a fallback
// since those CLIs may not be available or may require their own subscriptions.
func NewSummarizer(conn DaemonClient, config SummarizerConfig) *Summarizer {
	channelConfig := aichannel.Config{
		Agent:   config.Agent,
		Command: config.Command,
		Args:    config.Args,
		Timeout: config.Timeout,
		// OutputFormat will be set to "json" if agent supports it (in applyDefaults)
		// For agents that support JSON, we'll use it to extract just the final result
		OutputFormat: "json",
		// Always set system prompt - used by both CLI mode (--system-prompt) and API mode
		SystemPrompt: buildSummarySystemPrompt(),
	}

	// Determine whether to use API mode:
	// - Claude agent: Use CLI mode (Claude Code Max plan only supports CLI)
	// - All other agents: Use Anthropic API mode as fallback
	useAPI := config.UseAPI
	if !useAPI && config.Agent != aichannel.AgentClaude {
		// Auto-enable API mode for non-Claude agents
		useAPI = true
	}

	if useAPI {
		channelConfig.UseAPI = true
		channelConfig.LLMProvider = config.LLMProvider
		channelConfig.APIKey = config.APIKey
		channelConfig.Model = config.Model
	}

	return &Summarizer{
		conn:        conn,
		channel:     aichannel.NewWithConfig(channelConfig),
		debugOutput: config.DebugOutput,
		projectPath: config.ProjectPath,
	}
}

// debugWriter returns the debug output writer, defaulting to os.Stderr.
func (s *Summarizer) debugWriter() io.Writer {
	if s.debugOutput != nil {
		return s.debugOutput
	}
	return os.Stderr
}

// IsAvailable returns true if the AI channel is available.
func (s *Summarizer) IsAvailable() bool {
	return s.channel.IsAvailable()
}

// AgentType returns the configured agent type.
func (s *Summarizer) AgentType() aichannel.AgentType {
	return s.channel.Config().Agent
}

// SummaryResult contains the result of a summarization.
type SummaryResult struct {
	Summary     string
	ProcessData []ProcessSummary
	ProxyData   []ProxySummary
	ErrorCount  int
	Duration    time.Duration
}

// ProcessSummary contains summarized info about a process.
type ProcessSummary struct {
	ID        string
	Command   string
	State     string
	HasErrors bool
	Output    string // Last N lines
}

// ProxySummary contains summarized info about a proxy.
type ProxySummary struct {
	ID         string
	TargetURL  string
	ListenAddr string
	ErrorCount int
	PageCount  int
	RecentLogs string // Recent log entries (errors, panel messages, etc.)
}

// Summarize aggregates all system data and generates a summary.
func (s *Summarizer) Summarize(ctx context.Context) (*SummaryResult, error) {
	start := time.Now()

	// Ensure connection is established
	if err := s.conn.EnsureConnected(); err != nil {
		return nil, fmt.Errorf("failed to connect to daemon: %w", err)
	}

	// Gather all data
	processes, err := s.gatherProcesses()
	if err != nil {
		return nil, fmt.Errorf("failed to gather process data: %w", err)
	}

	proxies, err := s.gatherProxies()
	if err != nil {
		return nil, fmt.Errorf("failed to gather proxy data: %w", err)
	}

	// Build context for AI
	contextData := s.buildContext(processes, proxies)

	// Generate summary via AI
	prompt := s.buildPrompt()

	// Log what we're about to do for debugging
	if s.channel.IsAPIMode() {
		config := s.channel.Config()
		provider := config.LLMProvider
		if provider == "" {
			provider = aichannel.GetDefaultProvider()
		}
		model := config.Model
		if model == "" {
			model = "default"
		}
		fmt.Fprintf(s.debugWriter(), "[agnt] Calling %s API (%s) with %d bytes of context...\r\n",
			provider, model, len(contextData))
	} else {
		fmt.Fprintf(s.debugWriter(), "[agnt] Calling %s with %d bytes of context...\r\n",
			s.channel.Config().Command, len(contextData))
	}

	// Use SendAndParse to get structured response with just the final result
	response, err := s.channel.SendAndParse(ctx, prompt, contextData)
	if err != nil {
		return nil, fmt.Errorf("AI summarization failed: %w", err)
	}

	summary := response.Result

	// Count errors
	errorCount := 0
	for _, p := range processes {
		if p.HasErrors {
			errorCount++
		}
	}
	for _, p := range proxies {
		errorCount += p.ErrorCount
	}

	return &SummaryResult{
		Summary:     summary,
		ProcessData: processes,
		ProxyData:   proxies,
		ErrorCount:  errorCount,
		Duration:    time.Since(start),
	}, nil
}

func (s *Summarizer) gatherProcesses() ([]ProcessSummary, error) {
	// Get process list - scoped to current project directory
	result, err := s.conn.RequestJSON(protocol.VerbProc, protocol.DirectoryFilter{Directory: s.projectPath}, protocol.SubVerbList)
	if err != nil {
		return nil, err
	}

	processesRaw, ok := result["processes"].([]interface{})
	if !ok {
		return nil, nil
	}

	processes := make([]ProcessSummary, 0, len(processesRaw))
	for _, p := range processesRaw {
		pm, ok := p.(map[string]interface{})
		if !ok {
			continue
		}

		summary := ProcessSummary{}
		if id, ok := pm["id"].(string); ok {
			summary.ID = id
		}
		if cmd, ok := pm["command"].(string); ok {
			summary.Command = cmd
		}
		if state, ok := pm["state"].(string); ok {
			summary.State = state
		}

		// Fetch last 50 lines of output
		if summary.ID != "" {
			output, err := s.conn.RequestString(protocol.VerbProc, protocol.SubVerbOutput, summary.ID, "stream=combined", "tail=50")
			if err == nil {
				summary.Output = output
				// Check for error patterns in output
				summary.HasErrors = containsErrorPatterns(output)
			}
		}

		processes = append(processes, summary)
	}

	return processes, nil
}

func (s *Summarizer) gatherProxies() ([]ProxySummary, error) {
	// Get proxy list - scoped to current project directory
	result, err := s.conn.RequestJSON(protocol.VerbProxy, protocol.DirectoryFilter{Directory: s.projectPath}, protocol.SubVerbList)
	if err != nil {
		return nil, err
	}

	proxiesRaw, ok := result["proxies"].([]interface{})
	if !ok {
		return nil, nil
	}

	proxies := make([]ProxySummary, 0, len(proxiesRaw))
	for _, p := range proxiesRaw {
		pm, ok := p.(map[string]interface{})
		if !ok {
			continue
		}

		summary := ProxySummary{}
		if id, ok := pm["id"].(string); ok {
			summary.ID = id
		}
		if target, ok := pm["target_url"].(string); ok {
			summary.TargetURL = target
		}
		if listen, ok := pm["listen_addr"].(string); ok {
			summary.ListenAddr = listen
		}

		// Get stats
		if stats, ok := pm["stats"].(map[string]interface{}); ok {
			if errCount, ok := stats["error_count"].(float64); ok {
				summary.ErrorCount = int(errCount)
			}
		}

		// Get page count using request builder
		pagesResult, err := s.conn.RequestJSON(protocol.VerbCurrentPage, nil, protocol.SubVerbList, summary.ID)
		if err == nil {
			if sessions, ok := pagesResult["sessions"].([]interface{}); ok {
				summary.PageCount = len(sessions)
			}
		}

		// Get recent proxy logs (errors, panel messages, custom logs)
		// Include multiple log types that are useful for summarization
		logFilter := protocol.LogQueryFilter{
			Types: []string{"error", "panel_message", "custom", "sketch"},
			Limit: 20, // Last 20 relevant entries
		}
		logsResult, err := s.conn.RequestJSON(protocol.VerbProxyLog, logFilter, protocol.SubVerbQuery, summary.ID)
		if err == nil {
			summary.RecentLogs = formatProxyLogs(logsResult)
		}

		proxies = append(proxies, summary)
	}

	return proxies, nil
}

// formatProxyLogs formats proxy log entries for the AI context.
func formatProxyLogs(result map[string]interface{}) string {
	entries, ok := result["entries"].([]interface{})
	if !ok || len(entries) == 0 {
		return ""
	}

	var sb strings.Builder
	for _, entry := range entries {
		em, ok := entry.(map[string]interface{})
		if !ok {
			continue
		}

		logType, _ := em["type"].(string)
		timestamp, _ := em["timestamp"].(string)

		switch logType {
		case "error":
			message, _ := em["message"].(string)
			sb.WriteString(fmt.Sprintf("  [ERROR %s] %s\n", timestamp, message))
		case "panel_message":
			message, _ := em["message"].(string)
			sb.WriteString(fmt.Sprintf("  [PANEL %s] %s\n", timestamp, message))
		case "custom":
			level, _ := em["level"].(string)
			message, _ := em["message"].(string)
			sb.WriteString(fmt.Sprintf("  [%s %s] %s\n", strings.ToUpper(level), timestamp, message))
		case "sketch":
			sb.WriteString(fmt.Sprintf("  [SKETCH %s] User created a sketch/wireframe\n", timestamp))
		}
	}

	return sb.String()
}

func (s *Summarizer) buildContext(processes []ProcessSummary, proxies []ProxySummary) string {
	var sb strings.Builder

	sb.WriteString("=== SYSTEM STATUS ===\n\n")

	// Processes section
	sb.WriteString("== PROCESSES ==\n")
	if len(processes) == 0 {
		sb.WriteString("No running processes.\n")
	} else {
		for _, p := range processes {
			sb.WriteString(fmt.Sprintf("\n--- Process: %s ---\n", p.ID))
			sb.WriteString(fmt.Sprintf("Command: %s\n", p.Command))
			sb.WriteString(fmt.Sprintf("State: %s\n", p.State))
			if p.Output != "" {
				sb.WriteString("Output (last 50 lines):\n")
				sb.WriteString(p.Output)
				sb.WriteString("\n")
			}
		}
	}

	// Proxies section
	sb.WriteString("\n== PROXIES ==\n")
	if len(proxies) == 0 {
		sb.WriteString("No running proxies.\n")
	} else {
		for _, p := range proxies {
			sb.WriteString(fmt.Sprintf("\n--- Proxy: %s ---\n", p.ID))
			sb.WriteString(fmt.Sprintf("Target: %s\n", p.TargetURL))
			sb.WriteString(fmt.Sprintf("Listen: %s\n", p.ListenAddr))
			sb.WriteString(fmt.Sprintf("Error Count: %d\n", p.ErrorCount))
			sb.WriteString(fmt.Sprintf("Active Pages: %d\n", p.PageCount))
			if p.RecentLogs != "" {
				sb.WriteString("Recent Logs (errors, messages):\n")
				sb.WriteString(p.RecentLogs)
			}
		}
	}

	return sb.String()
}

// buildSummarySystemPrompt returns the system prompt for API mode.
func buildSummarySystemPrompt() string {
	return `You are a concise system status summarizer.

CRITICAL RULES:
1. ONLY summarize the data provided to you - DO NOT scan files, explore the codebase, or use any external information
2. Be extremely concise (2-5 lines max) - this will be displayed in a small terminal indicator
3. Base your summary ENTIRELY on the process output and proxy logs provided

Format:
- If healthy: "✓ All systems OK" (single line)
- If issues: Brief bullet points, max 3-4 items

Focus ONLY on:
• Active errors or failures from the provided output
• Critical state changes visible in the data
• Actionable issues mentioned in the logs

DO NOT:
• Read or scan any files
• Explore the codebase
• Add explanations or context beyond what's in the data
• Include full stack traces
• Suggest fixes unless trivially obvious from the data
• Include process/proxy IDs unless relevant to an error

Example good response:
"✓ 2 processes running, 1 proxy active"

Example good error response:
"• test-server: EADDRINUSE port 3000
• proxy dev: 3 frontend errors"`
}

func (s *Summarizer) buildPrompt() string {
	// Both API mode and CLI mode (with --system-prompt) use the system prompt for instructions.
	// The user prompt is just the request.
	return "Analyze ONLY the provided system status data and summarize. Do not scan files or explore the codebase."
}

// containsErrorPatterns checks if output contains common error patterns.
func containsErrorPatterns(output string) bool {
	lower := strings.ToLower(output)
	patterns := []string{
		"error",
		"exception",
		"failed",
		"panic",
		"fatal",
		"segfault",
		"timeout",
		"refused",
		"denied",
		"not found",
		"undefined",
		"null pointer",
		"stack trace",
		"traceback",
	}

	for _, p := range patterns {
		if strings.Contains(lower, p) {
			return true
		}
	}
	return false
}
