package tools

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

	"github.com/google/uuid"

	"github.com/nextlevelbuilder/goclaw/internal/bus"
	"github.com/nextlevelbuilder/goclaw/internal/store"
	"github.com/nextlevelbuilder/goclaw/internal/tracing"
	"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)

// TeamMessageTool exposes the team mailbox to agents.
// Actions: send, broadcast, read.
type TeamMessageTool struct {
	manager *TeamToolManager
}

func NewTeamMessageTool(manager *TeamToolManager) *TeamMessageTool {
	return &TeamMessageTool{manager: manager}
}

func (t *TeamMessageTool) Name() string { return "team_message" }

func (t *TeamMessageTool) Description() string {
	return "Send and receive messages within your team. Actions: send, broadcast, read. See TEAM.md for your teammates."
}

func (t *TeamMessageTool) Parameters() map[string]any {
	return map[string]any{
		"type": "object",
		"properties": map[string]any{
			"action": map[string]any{
				"type":        "string",
				"description": "'send', 'broadcast', or 'read'",
			},
			"to": map[string]any{
				"type":        "string",
				"description": "Target agent key (required for action=send)",
			},
			"text": map[string]any{
				"type":        "string",
				"description": "Message content (required for action=send and action=broadcast)",
			},
			"media": map[string]any{
				"type":        "array",
				"description": "Optional file paths to attach as media (for action=send)",
				"items": map[string]any{
					"type": "string",
				},
			},
		},
		"required": []string{"action"},
	}
}

func (t *TeamMessageTool) Execute(ctx context.Context, args map[string]any) *Result {
	action, _ := args["action"].(string)

	switch action {
	case "send":
		return t.executeSend(ctx, args)
	case "broadcast":
		return t.executeBroadcast(ctx, args)
	case "read":
		return t.executeRead(ctx)
	default:
		return ErrorResult(fmt.Sprintf("unknown action: %s (use send, broadcast, or read)", action))
	}
}

func (t *TeamMessageTool) executeSend(ctx context.Context, args map[string]any) *Result {
	team, agentID, err := t.manager.resolveTeam(ctx)
	if err != nil {
		return ErrorResult(err.Error())
	}

	toKey, _ := args["to"].(string)
	if toKey == "" {
		return ErrorResult("to parameter is required for send action")
	}
	text, _ := args["text"].(string)
	if text == "" {
		return ErrorResult("text parameter is required for send action")
	}

	toAgentID, err := t.manager.resolveAgentByKey(toKey)
	if err != nil {
		return ErrorResult(err.Error())
	}

	// Validate recipient is in the same team (prevent cross-team messaging).
	members, err := t.manager.cachedListMembers(ctx, team.ID, agentID)
	if err != nil {
		return ErrorResult("failed to verify team membership: " + err.Error())
	}
	isMember := false
	for _, m := range members {
		if m.AgentID == toAgentID {
			isMember = true
			break
		}
	}
	if !isMember {
		return ErrorResult(fmt.Sprintf("agent %q is not a member of your team", toKey))
	}

	// Parse optional media paths
	var mediaFiles []bus.MediaFile
	if rawMedia, ok := args["media"]; ok {
		if mediaArr, ok := rawMedia.([]any); ok {
			for _, item := range mediaArr {
				if path, ok := item.(string); ok && path != "" {
					mediaFiles = append(mediaFiles, bus.MediaFile{Path: path})
				}
			}
		}
	}

	// Persist to DB
	msg := &store.TeamMessageData{
		TeamID:      team.ID,
		FromAgentID: agentID,
		ToAgentID:   &toAgentID,
		Content:     text,
		MessageType: store.TeamMessageTypeChat,
	}
	if err := t.manager.teamStore.SendMessage(ctx, msg); err != nil {
		return ErrorResult("failed to send message: " + err.Error())
	}

	// Auto-create a "message" task so team messages appear in the Tasks tab.
	subject := text
	if len(subject) > 100 {
		subject = subject[:100] + "..."
	}
	now := time.Now()
	lockExpires := now.Add(30 * time.Minute)
	taskData := &store.TeamTaskData{
		TeamID:           team.ID,
		Subject:          subject,
		Description:      text,
		Status:           store.TeamTaskStatusInProgress,
		TaskType:         "message",
		CreatedByAgentID: &agentID,
		OwnerAgentID:     &toAgentID,
		UserID:           store.UserIDFromContext(ctx),
		Channel:          ToolChannelFromCtx(ctx),
		ChatID:           ToolChatIDFromCtx(ctx),
		LockedAt:         &now,
		LockExpiresAt:    &lockExpires,
	}
	var teamTaskID uuid.UUID
	slog.Info("team_message: creating auto-task",
		"team_id", team.ID,
		"from_agent", agentID,
		"to_agent", toAgentID,
		"status", taskData.Status,
		"task_type", taskData.TaskType,
		"user_id", taskData.UserID,
		"channel", taskData.Channel,
		"chat_id", taskData.ChatID,
	)
	if err := t.manager.teamStore.CreateTask(ctx, taskData); err != nil {
		slog.Warn("team_message: failed to auto-create task",
			"error", err,
			"team_id", team.ID,
			"from_agent", agentID,
			"to_agent", toAgentID,
		)
	} else {
		teamTaskID = taskData.ID
		t.manager.broadcastTeamEvent(protocol.EventTeamTaskCreated, protocol.TeamTaskEventPayload{
			TeamID:           team.ID.String(),
			TaskID:           teamTaskID.String(),
			Subject:          subject,
			Status:           store.TeamTaskStatusInProgress,
			OwnerAgentKey:    toKey,
			OwnerDisplayName: t.manager.agentDisplayName(ctx, toKey),
			UserID:           store.UserIDFromContext(ctx),
			Channel:          ToolChannelFromCtx(ctx),
			ChatID:           ToolChatIDFromCtx(ctx),
			Timestamp:        time.Now().UTC().Format("2006-01-02T15:04:05Z"),
			ActorType:        "agent",
			ActorID:          t.manager.agentKeyFromID(ctx, agentID),
		})
	}

	// Real-time delivery via message bus
	fromKey := t.manager.agentKeyFromID(ctx, agentID)
	t.publishTeammateMessage(fromKey, toKey, text, mediaFiles, teamTaskID, team.ID, team.Settings, ctx)

	preview := text
	if len(preview) > 100 {
		preview = preview[:100] + "..."
	}
	t.manager.broadcastTeamEvent(protocol.EventTeamMessageSent, protocol.TeamMessageEventPayload{
		TeamID:          team.ID.String(),
		FromAgentKey:    fromKey,
		FromDisplayName: t.manager.agentDisplayName(ctx, fromKey),
		ToAgentKey:      toKey,
		ToDisplayName:   t.manager.agentDisplayName(ctx, toKey),
		MessageType:     string(store.TeamMessageTypeChat),
		Preview:         preview,
		UserID:          store.UserIDFromContext(ctx),
		Channel:         ToolChannelFromCtx(ctx),
		ChatID:          ToolChatIDFromCtx(ctx),
	})

	return NewResult(fmt.Sprintf("Message sent to %s.", toKey))
}

func (t *TeamMessageTool) executeBroadcast(ctx context.Context, args map[string]any) *Result {
	team, agentID, err := t.manager.resolveTeam(ctx)
	if err != nil {
		return ErrorResult(err.Error())
	}
	if err := t.manager.requireLead(ctx, team, agentID); err != nil {
		return ErrorResult(err.Error())
	}

	text, _ := args["text"].(string)
	if text == "" {
		return ErrorResult("text parameter is required for broadcast action")
	}

	// Persist to DB (to_agent_id = NULL means broadcast)
	msg := &store.TeamMessageData{
		TeamID:      team.ID,
		FromAgentID: agentID,
		ToAgentID:   nil,
		Content:     text,
		MessageType: store.TeamMessageTypeBroadcast,
	}
	if err := t.manager.teamStore.SendMessage(ctx, msg); err != nil {
		return ErrorResult("failed to broadcast message: " + err.Error())
	}

	// Real-time delivery to all teammates via message bus
	fromKey := t.manager.agentKeyFromID(ctx, agentID)
	members, err := t.manager.cachedListMembers(ctx, team.ID, agentID)
	if err == nil {
		for _, m := range members {
			if m.AgentID == agentID {
				continue // don't send to self
			}
			t.publishTeammateMessage(fromKey, m.AgentKey, text, nil, uuid.Nil, team.ID, team.Settings, ctx)
		}
	}

	preview := text
	if len(preview) > 100 {
		preview = preview[:100] + "..."
	}
	t.manager.broadcastTeamEvent(protocol.EventTeamMessageSent, protocol.TeamMessageEventPayload{
		TeamID:          team.ID.String(),
		FromAgentKey:    fromKey,
		FromDisplayName: t.manager.agentDisplayName(ctx, fromKey),
		ToAgentKey:      "broadcast",
		MessageType:     string(store.TeamMessageTypeBroadcast),
		Preview:         preview,
		UserID:          store.UserIDFromContext(ctx),
		Channel:         ToolChannelFromCtx(ctx),
		ChatID:          ToolChatIDFromCtx(ctx),
	})

	return NewResult(fmt.Sprintf("Broadcast sent to all teammates."))
}

func (t *TeamMessageTool) executeRead(ctx context.Context) *Result {
	team, agentID, err := t.manager.resolveTeam(ctx)
	if err != nil {
		return ErrorResult(err.Error())
	}

	messages, err := t.manager.teamStore.GetUnread(ctx, team.ID, agentID)
	if err != nil {
		return ErrorResult("failed to get unread messages: " + err.Error())
	}

	// Mark all as read
	for _, msg := range messages {
		_ = t.manager.teamStore.MarkRead(ctx, msg.ID)
	}

	resp := map[string]any{
		"messages": messages,
		"count":    len(messages),
	}
	if len(messages) >= 50 {
		resp["note"] = "Showing latest 50 unread messages. Read again after processing these to get more."
		resp["has_more"] = true
	}
	out, _ := json.Marshal(resp)
	return SilentResult(string(out))
}

// publishTeammateMessage sends a real-time notification via the message bus.
// Uses "teammate:{fromKey}" sender prefix so the consumer can route it.
func (t *TeamMessageTool) publishTeammateMessage(fromKey, toKey, text string, media []bus.MediaFile, teamTaskID uuid.UUID, teamID uuid.UUID, teamSettings json.RawMessage, ctx context.Context) {
	if t.manager.msgBus == nil {
		return
	}

	userID := store.UserIDFromContext(ctx)
	chatID := ToolChatIDFromCtx(ctx)
	originChannel := ToolChannelFromCtx(ctx)
	originPeerKind := ToolPeerKindFromCtx(ctx)

	slog.Info("team_message: publishTeammateMessage",
		"from", fromKey, "to", toKey,
		"origin_channel", originChannel,
		"chat_id", chatID,
		"origin_peer_kind", originPeerKind,
		"user_id", userID,
		"team_task_id", teamTaskID,
	)

	teamMeta := map[string]string{
		"origin_channel":   originChannel,
		"origin_peer_kind": originPeerKind,
		"origin_chat_id":   chatID,
		"origin_user_id":   userID,
		"from_agent":       fromKey,
		"to_agent":         toKey,
		"team_id":          teamID.String(),
	}
	if localKey := ToolLocalKeyFromCtx(ctx); localKey != "" {
		teamMeta["origin_local_key"] = localKey
	}
	if teamTaskID != uuid.Nil {
		teamMeta["team_task_id"] = teamTaskID.String()
	}
	// Pass team workspace so the receiving agent can access shared files.
	wsChat := chatID
	if IsSharedWorkspace(teamSettings) {
		wsChat = ""
	}
	if ws, err := WorkspaceDir(t.manager.dataDir, teamID, wsChat); err == nil {
		teamMeta["team_workspace"] = ws
	}
	// Propagate trace context so the receiving agent's trace links back.
	if traceID := tracing.TraceIDFromContext(ctx); traceID != uuid.Nil {
		teamMeta["origin_trace_id"] = traceID.String()
	}
	if rootSpanID := tracing.ParentSpanIDFromContext(ctx); rootSpanID != uuid.Nil {
		teamMeta["origin_root_span_id"] = rootSpanID.String()
	}
	t.manager.msgBus.PublishInbound(bus.InboundMessage{
		Channel:  "system",
		SenderID: fmt.Sprintf("teammate:%s", fromKey),
		ChatID:   chatID,
		Content:  fmt.Sprintf("[Team message from %s]: %s", fromKey, text),
		Media:    media,
		UserID:   userID,
		AgentID:  toKey,
		Metadata: teamMeta,
	})
}
