package mcp

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/mcpformat"
)

func (h *mcpHandler) toolSearchMessages(ctx context.Context, api *apiclient.Client, args json.RawMessage) *types.MCPToolResult {
	var p struct {
		Q       string   `json:"q"`
		Channel string   `json:"channel"`
		Target  string   `json:"target"`
		Limit   *float64 `json:"limit"`
		Before  float64  `json:"before"`
	}
	if err := json.Unmarshal(args, &p); err != nil {
		return toolError("invalid arguments: " + err.Error())
	}
	query := strings.TrimSpace(p.Q)
	if query == "" {
		return toolError("q is required")
	}

	limit := float64(searchMessagesDefaultLimit)
	if p.Limit != nil {
		limit = *p.Limit
		if limit > searchMessagesMaxLimit {
			limit = searchMessagesMaxLimit
		}
	}

	q := url.Values{}
	q.Set("q", query)
	q.Set("limit", fmt.Sprintf("%.0f", limit))
	rawTarget := strings.TrimSpace(p.Channel)
	if rawTarget == "" {
		rawTarget = p.Target
	}
	channel, err := normalizeMessageTarget(rawTarget)
	if err != nil {
		return toolError(err.Error())
	}
	if channel != "" {
		q.Set("channel", channel)
	}
	if p.Before > 0 {
		if channel == "" {
			return toolError("channel/target is required when using before")
		}
		q.Set("before_seq", fmt.Sprintf("%.0f", p.Before))
	}

	path := api.AgentRuntimePath("/search?" + q.Encode())
	var result struct {
		Messages                 []historyMessage `json:"messages"`
		HasMore                  bool             `json:"has_more"`
		NextCursor               int64            `json:"next_cursor"`
		CandidateBudgetExhausted bool             `json:"candidate_budget_exhausted"`
		Error                    string           `json:"error"`
	}
	if err := api.Get(ctx, path, &result); err != nil {
		if detail, ok := searchUnavailableDetail(err); ok {
			return formatSearchUnavailableToolResult(channel, detail)
		}
		return toolError(err.Error())
	}
	if result.Error != "" {
		if detail, ok := searchUnavailableMessage(result.Error); ok {
			return formatSearchUnavailableToolResult(channel, detail)
		}
		return toolError(result.Error)
	}
	if len(result.Messages) == 0 {
		return toolText(fmt.Sprintf("No messages found matching \"%s\".", query))
	}

	lines := make([]string, 0, len(result.Messages))
	for _, message := range result.Messages {
		timeText := mcpformat.FormatTimestamp(message.CreatedAt)
		author := message.AuthorIdentity()
		authorName := mcpformat.PromptAuthorMentionName(author)
		senderType := ""
		if author.ActorType == "agent" {
			senderType = " type=agent"
		}
		attachmentSuffix := ""
		if len(message.Attachments) > 0 {
			attachmentSuffix = formatHistoryAttachmentSuffix(message.Attachments)
		}
		lines = append(lines, fmt.Sprintf("[seq=%d time=%s%s] @%s: %s%s",
			message.Seq, timeText, senderType, authorName, mcpformat.TruncateAgentTeamToolRunes(message.Content, historyContentMaxRunes), attachmentSuffix))
	}

	header := fmt.Sprintf("## Search Results for \"%s\" (%d matches)", query, len(result.Messages))
	footer := ""
	if result.CandidateBudgetExhausted {
		footer = "\n\n--- Search candidate budget was exhausted; narrow the query or target. ---"
	} else if result.HasMore && result.NextCursor > 0 {
		footer = fmt.Sprintf("\n\n--- %d results shown. Use before=%d with the same target/query to load older matches. ---", len(result.Messages), result.NextCursor)
	} else if result.HasMore {
		footer = fmt.Sprintf("\n\n--- %d results shown. Narrow the request or use a target filter for more results. ---", len(result.Messages))
	}
	return toolText(header + "\n\n" + strings.Join(lines, "\n") + footer)
}

func searchUnavailableDetail(err error) (string, bool) {
	var apiErr *apiclient.APIError
	if !errors.As(err, &apiErr) {
		return "", false
	}
	if apiErr.StatusCode != http.StatusNotImplemented {
		return "", false
	}
	return searchUnavailableMessage(apiErr.Message)
}

func searchUnavailableMessage(message string) (string, bool) {
	detail := strings.TrimSpace(message)
	if detail == "" {
		return "", false
	}
	if !strings.Contains(strings.ToLower(detail), "message search is unavailable") {
		return "", false
	}
	return detail, true
}

type searchUnavailableStructuredContent struct {
	Kind           string `json:"kind"`
	Classification string `json:"classification"`
	Target         string `json:"target,omitempty"`
	Reason         string `json:"reason"`
	Fallback       string `json:"fallback"`
	OperatorAction string `json:"operatorAction"`
}

func formatSearchUnavailableToolResult(target string, detail string) *types.MCPToolResult {
	return toolTextStructured(formatSearchUnavailableResult(target, detail), searchUnavailableStructuredContent{
		Kind:           "message_search_unavailable",
		Classification: "search_readiness",
		Target:         strings.TrimSpace(target),
		Reason:         strings.TrimSpace(detail),
		Fallback:       "read recent history",
		OperatorAction: "workspace-message-search-ensure-ready",
	})
}

func formatSearchUnavailableResult(target string, detail string) string {
	var sb strings.Builder
	sb.WriteString("Message search is unavailable for this target.\n")
	if target = strings.TrimSpace(target); target != "" {
		fmt.Fprintf(&sb, "target: %s\n", target)
	}
	if detail = strings.TrimSpace(detail); detail != "" {
		fmt.Fprintf(&sb, "reason: %s\n", detail)
	}
	sb.WriteString("This is a search-readiness issue, not a DM or message-routing failure. Read recent target history for context, or ask an operator to run workspace-message-search-ensure-ready for this workspace.")
	return strings.TrimSpace(sb.String())
}
