package methods

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"github.com/nextlevelbuilder/goclaw/internal/bootstrap"
	"github.com/nextlevelbuilder/goclaw/internal/config"
	"github.com/nextlevelbuilder/goclaw/internal/gateway"
	"github.com/nextlevelbuilder/goclaw/internal/i18n"
	"github.com/nextlevelbuilder/goclaw/internal/store"
	"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)

// --- agents.create ---
// Matching TS src/gateway/server-methods/agents.ts:216-287

func (m *AgentsMethods) handleCreate(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) {
	locale := store.LocaleFromContext(ctx)
	var params struct {
		Name      string   `json:"name"`
		Workspace string   `json:"workspace"`
		Emoji     string   `json:"emoji"`
		Avatar    string   `json:"avatar"`
		AgentType string   `json:"agent_type"`          // "open" (default) or "predefined"
		OwnerIDs  []string `json:"owner_ids,omitempty"` // first entry used as DB owner_id; falls back to "system"
		// Per-agent config overrides
		ToolsConfig      json.RawMessage `json:"tools_config,omitempty"`
		SubagentsConfig  json.RawMessage `json:"subagents_config,omitempty"`
		SandboxConfig    json.RawMessage `json:"sandbox_config,omitempty"`
		MemoryConfig     json.RawMessage `json:"memory_config,omitempty"`
		CompactionConfig json.RawMessage `json:"compaction_config,omitempty"`
		ContextPruning   json.RawMessage `json:"context_pruning,omitempty"`
		OtherConfig      json.RawMessage `json:"other_config,omitempty"`
	}
	if req.Params != nil {
		json.Unmarshal(req.Params, &params)
	}

	if params.Name == "" {
		client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgRequired, "name")))
		return
	}

	agentType := params.AgentType
	if agentType == "" {
		agentType = store.AgentTypeOpen
	}

	agentID := config.NormalizeAgentID(params.Name)
	if agentID == "default" {
		client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidRequest, "cannot create agent with reserved id 'default'")))
		return
	}

	// Resolve workspace
	ws := params.Workspace
	if ws == "" {
		ws = filepath.Join(m.workspace, "agents", agentID)
	} else {
		ws = config.ExpandHome(ws)
	}

	if m.agentStore != nil {
		// --- DB-backed: create agent in store ---
		ctx := context.Background()

		// Check if agent already exists in DB
		if existing, _ := m.agentStore.GetByKey(ctx, agentID); existing != nil {
			client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgAlreadyExists, "agent", agentID)))
			return
		}

		// Resolve owner: use first provided ID so external provisioning tools (e.g. goclaw-wizards)
		// can set a real user as owner at creation time. Falls back to "system" for backward compat.
		ownerID := "system"
		if len(params.OwnerIDs) > 0 && params.OwnerIDs[0] != "" {
			ownerID = params.OwnerIDs[0]
		}

		agentData := &store.AgentData{
			AgentKey:         agentID,
			DisplayName:      params.Name,
			OwnerID:          ownerID,
			AgentType:        agentType,
			Provider:         m.cfg.Agents.Defaults.Provider,
			Model:            m.cfg.Agents.Defaults.Model,
			Workspace:        ws,
			Status:           store.AgentStatusActive,
			ToolsConfig:      params.ToolsConfig,
			SubagentsConfig:  params.SubagentsConfig,
			SandboxConfig:    params.SandboxConfig,
			MemoryConfig:     params.MemoryConfig,
			CompactionConfig: params.CompactionConfig,
			ContextPruning:   params.ContextPruning,
			OtherConfig:      params.OtherConfig,
		}
		if err := m.agentStore.Create(ctx, agentData); err != nil {
			client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToCreate, "agent", fmt.Sprintf("%v", err))))
			return
		}

		// Seed context files to DB (skipped for open agents)
		if _, err := bootstrap.SeedToStore(ctx, m.agentStore, agentData.ID, agentData.AgentType); err != nil {
			slog.Warn("failed to seed bootstrap for agent", "agent", agentID, "error", err)
		}

		// Set identity in DB bootstrap
		if params.Name != "" || params.Emoji != "" || params.Avatar != "" {
			content := buildIdentityContent(params.Name, params.Emoji, params.Avatar)
			if err := m.agentStore.SetAgentContextFile(ctx, agentData.ID, "IDENTITY.md", content); err != nil {
				slog.Warn("failed to set IDENTITY.md", "agent", agentID, "error", err)
			}
		}

		// Invalidate router cache so resolver re-loads from DB
		m.agents.InvalidateAgent(agentID)
	} else {
		// --- Fallback: config.json + filesystem ---
		if _, ok := m.cfg.Agents.List[agentID]; ok {
			client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgAlreadyExists, "agent", agentID)))
			return
		}

		spec := config.AgentSpec{
			DisplayName: params.Name,
			Workspace:   ws,
		}
		if params.Emoji != "" || params.Avatar != "" {
			spec.Identity = &config.IdentityConfig{
				Emoji: params.Emoji,
			}
		}

		if m.cfg.Agents.List == nil {
			m.cfg.Agents.List = make(map[string]config.AgentSpec)
		}
		m.cfg.Agents.List[agentID] = spec

		if err := config.Save(m.cfgPath, m.cfg); err != nil {
			client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToSave, "config", err.Error())))
			return
		}

		// Append identity metadata to IDENTITY.md
		if params.Name != "" || params.Emoji != "" || params.Avatar != "" {
			identityPath := filepath.Join(ws, "IDENTITY.md")
			appendIdentityFields(identityPath, params.Name, params.Emoji, params.Avatar)
		}
	}

	// Both modes: create workspace dir + seed filesystem backup
	os.MkdirAll(ws, 0755)
	bootstrap.EnsureWorkspaceFiles(ws)

	client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
		"ok":        true,
		"agentId":   agentID,
		"name":      params.Name,
		"workspace": ws,
	}))
	emitAudit(m.eventBus, client, "agent.created", "agent", agentID)
}
