package agents

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

	"github.com/enmanuel/agents/pkg/command"
	"github.com/enmanuel/agents/pkg/decision"
)

// registerBuiltinCommands registers all built-in command handlers.
func (a *Agent) registerBuiltinCommands() {
	a.commands["help"] = a.cmdHelp
	a.commands["tools"] = a.cmdTools
	a.commands["tool"] = a.cmdTool
	a.commands["ping"] = a.cmdPing
	a.commands["status"] = a.cmdStatus
	a.commands["info"] = a.cmdInfo
	a.commands["clear"] = a.cmdClear
	a.commands["prompts"] = a.cmdPrompts
	a.commands["version"] = a.cmdVersion
}

// cmdHelp lists all available commands (built-in + agent-specific).
func (a *Agent) cmdHelp(_ context.Context, _ decision.MessageContext) string {
	var b strings.Builder
	b.WriteString("**Comandos disponibles:**\n\n")

	// Built-in commands
	for _, spec := range command.Builtins() {
		if spec.Hidden {
			continue
		}
		writeSpec(&b, spec)
	}

	// Agent-specific commands (registered via RegisterCommand)
	if len(a.customSpecs) > 0 {
		b.WriteString("\n**Comandos del agente:**\n\n")
		for _, spec := range a.customSpecs {
			if spec.Hidden {
				continue
			}
			writeSpec(&b, spec)
		}
	}

	return b.String()
}

// writeSpec formats a single command spec for the help output.
func writeSpec(b *strings.Builder, spec command.Spec) {
	aliases := ""
	if len(spec.Aliases) > 0 {
		aliases = " (" + strings.Join(prefixAll(spec.Aliases, "!"), ", ") + ")"
	}
	usage := spec.Usage
	if usage == "" {
		usage = "!" + spec.Name
	}
	fmt.Fprintf(b, "- `%s`%s — %s\n", usage, aliases, spec.Description)
}

// cmdTools lists all tools registered in the agent's tool registry.
func (a *Agent) cmdTools(_ context.Context, _ decision.MessageContext) string {
	names := a.toolReg.Names()
	if len(names) == 0 {
		return "No hay tools registradas."
	}

	var b strings.Builder
	fmt.Fprintf(&b, "**Tools disponibles (%d):**\n\n", len(names))
	for _, name := range names {
		t, _ := a.toolReg.Get(name)
		fmt.Fprintf(&b, "- **%s** — %s\n", t.Def.Name, t.Def.Description)
		for _, p := range t.Def.Parameters {
			req := ""
			if p.Required {
				req = " *(requerido)*"
			}
			fmt.Fprintf(&b, "  - `%s`: %s%s\n", p.Name, p.Description, req)
		}
	}
	b.WriteString("\nUso: `!tool <nombre> [key=value ...]`")
	return b.String()
}

// cmdTool executes a tool directly with key=value args.
func (a *Agent) cmdTool(ctx context.Context, msgCtx decision.MessageContext) string {
	if len(msgCtx.Args) == 0 {
		return "Uso: `!tool <nombre> [key=value ...]`\nUsa `!tools` para ver tools disponibles."
	}

	toolName := msgCtx.Args[0]
	if _, ok := a.toolReg.Get(toolName); !ok {
		return fmt.Sprintf("Tool %q no encontrada. Usa `!tools` para ver tools disponibles.", toolName)
	}

	// Parse remaining args as key=value
	parsed := command.ParseArgs(msgCtx.Args[1:])
	argsJSON := command.ArgsToJSON(parsed.Named)

	a.logger.Info("executing tool via command",
		"tool", toolName,
		"args", argsJSON,
	)

	result := a.toolReg.ExecuteForRoom(ctx, toolName, argsJSON, msgCtx.RoomID)
	if result.Err != nil {
		return fmt.Sprintf("Error ejecutando %s: %s", toolName, result.Err)
	}

	return fmt.Sprintf("%s:\n%s", toolName, result.Output)
}

// cmdPing responds with pong and timestamp.
func (a *Agent) cmdPing(_ context.Context, _ decision.MessageContext) string {
	return fmt.Sprintf("pong — %s", time.Now().Format(time.RFC3339))
}

// cmdStatus shows agent uptime and active rooms.
func (a *Agent) cmdStatus(_ context.Context, _ decision.MessageContext) string {
	uptime := time.Since(a.startTime).Truncate(time.Second)

	a.windowsMu.RLock()
	roomCount := len(a.windows)
	a.windowsMu.RUnlock()

	var b strings.Builder
	fmt.Fprintf(&b, "**Estado de %s:**\n\n", a.cfg.Agent.Name)
	fmt.Fprintf(&b, "- **Uptime:** %s\n", uptime)
	fmt.Fprintf(&b, "- **Rooms activos:** %d\n", roomCount)
	fmt.Fprintf(&b, "- **Window size:** %d\n", a.windowSize)
	fmt.Fprintf(&b, "- **Tools:** %d\n", a.toolReg.Len())

	if a.llm != nil {
		fmt.Fprintf(&b, "- **LLM:** %s/%s\n", a.cfg.LLM.Primary.Provider, a.cfg.LLM.Primary.Model)
	} else {
		b.WriteString("- **LLM:** no configurado\n")
	}

	return b.String()
}

// cmdInfo shows agent metadata, personality, capabilities, and configuration.
func (a *Agent) cmdInfo(_ context.Context, _ decision.MessageContext) string {
	var b strings.Builder

	// === Identidad ===
	b.WriteString("## Identidad\n\n")
	fmt.Fprintf(&b, "- **Nombre:** %s\n", a.cfg.Agent.Name)
	fmt.Fprintf(&b, "- **ID:** `%s`\n", a.cfg.Agent.ID)
	if a.cfg.Agent.Version != "" {
		fmt.Fprintf(&b, "- **Version:** %s\n", a.cfg.Agent.Version)
	}
	fmt.Fprintf(&b, "- **Descripcion:** %s\n", a.cfg.Agent.Description)
	if len(a.cfg.Agent.Tags) > 0 {
		fmt.Fprintf(&b, "- **Tags:** %v\n", a.cfg.Agent.Tags)
	}

	// === Personalidad ===
	if a.personality.Role != "" || a.personality.Communication.Personality != "" {
		b.WriteString("\n## Personalidad\n\n")
		if a.personality.Role != "" {
			fmt.Fprintf(&b, "- **Rol:** %s\n", a.personality.Role)
		}
		if a.personality.Tone != "" {
			fmt.Fprintf(&b, "- **Tono:** %s\n", a.personality.Tone)
		}
		if a.personality.Communication.Formality != "" {
			fmt.Fprintf(&b, "- **Formalidad:** %s\n", a.personality.Communication.Formality)
		}
		if a.personality.Communication.Personality != "" {
			fmt.Fprintf(&b, "- **Tipo:** %s\n", a.personality.Communication.Personality)
		}
		if a.personality.Communication.Humor != "" && a.personality.Communication.Humor != "none" {
			fmt.Fprintf(&b, "- **Humor:** %s\n", a.personality.Communication.Humor)
		}
	}

	// === LLM ===
	if a.cfg.LLM.Primary.Provider != "" {
		b.WriteString("\n## LLM\n\n")
		fmt.Fprintf(&b, "- **Provider:** %s\n", a.cfg.LLM.Primary.Provider)
		fmt.Fprintf(&b, "- **Modelo:** %s\n", a.cfg.LLM.Primary.Model)
		if a.cfg.LLM.ToolUse.Enabled {
			fmt.Fprintf(&b, "- **Tools:** habilitadas (max %d iteraciones)\n", a.cfg.LLM.ToolUse.MaxIterations)
		}
	}

	// === Tools ===
	toolCount := a.toolReg.Len()
	if toolCount > 0 {
		b.WriteString("\n## Tools disponibles\n\n")
		fmt.Fprintf(&b, "- **Total:** %d tools\n", toolCount)
		// Lista de tools (nombres)
		toolNames := a.toolReg.Names()
		if len(toolNames) > 0 && len(toolNames) <= 20 {
			b.WriteString("- **Lista:** ")
			for i, name := range toolNames {
				if i > 0 {
					b.WriteString(", ")
				}
				fmt.Fprintf(&b, "`%s`", name)
			}
			b.WriteString("\n")
		}
	}

	// === Skills ===
	if a.cfg.Skills.Enabled {
		b.WriteString("\n## Skills\n\n")
		b.WriteString("- **Habilitadas:** si\n")
		if len(a.cfg.Skills.Categories) > 0 {
			fmt.Fprintf(&b, "- **Categorias:** %v\n", a.cfg.Skills.Categories)
		}
		if a.skillLoader != nil {
			if metas, err := a.skillLoader.LoadMeta(); err == nil {
				fmt.Fprintf(&b, "- **Cantidad:** %d skills\n", len(metas))
			}
		}
	}

	// === Knowledge ===
	hasPrivate := a.cfg.Tools.Knowledge.Enabled
	hasShared := a.cfg.Tools.SharedKnowledge.Enabled
	if hasPrivate || hasShared {
		b.WriteString("\n## Knowledge\n\n")
		if hasPrivate {
			b.WriteString("- **Privado:** habilitado\n")
		}
		if hasShared {
			b.WriteString("- **Compartido:** habilitado\n")
		}
	}

	// === Memoria ===
	if a.cfg.Memory.Enabled {
		b.WriteString("\n## Memoria\n\n")
		fmt.Fprintf(&b, "- **Habilitada:** si\n")
		fmt.Fprintf(&b, "- **Window size:** %d mensajes\n", a.windowSize)
	}

	// === Schedules ===
	if len(a.cfg.Schedules) > 0 {
		b.WriteString("\n## Schedules\n\n")
		fmt.Fprintf(&b, "- **Cron jobs:** %d configurados\n", len(a.cfg.Schedules))
	}

	// === Uptime ===
	uptime := time.Since(a.startTime).Round(time.Second)
	b.WriteString("\n## Uptime\n\n")
	fmt.Fprintf(&b, "- **Activo desde:** %s\n", uptime)

	return b.String()
}

// cmdPrompts lists available prompt-commands.
func (a *Agent) cmdPrompts(_ context.Context, _ decision.MessageContext) string {
	if len(a.promptCmds) == 0 {
		return "No hay prompt-commands disponibles."
	}

	var b strings.Builder
	fmt.Fprintf(&b, "**Prompt-commands disponibles (%d):**\n\n", len(a.promptCmds))
	for name := range a.promptCmds {
		fmt.Fprintf(&b, "- `!%s`\n", name)
	}
	b.WriteString("\nUso: `!<nombre> [detalles adicionales...]`")
	return b.String()
}

// cmdClear clears the conversation window for the current room.
func (a *Agent) cmdClear(_ context.Context, msgCtx decision.MessageContext) string {
	a.ClearWindow(msgCtx.RoomID)
	return "Ventana de conversacion limpiada."
}

// cmdVersion shows the agent version.
func (a *Agent) cmdVersion(_ context.Context, _ decision.MessageContext) string {
	v := a.cfg.Agent.Version
	if v == "" {
		v = "sin version"
	}
	return fmt.Sprintf("%s %s", a.cfg.Agent.Name, v)
}

// prefixAll adds a prefix to each string in a slice.
func prefixAll(ss []string, prefix string) []string {
	out := make([]string, len(ss))
	for i, s := range ss {
		out[i] = prefix + s
	}
	return out
}
