package wire

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

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

// ---------------------------------------------------------------------------
// Outbound: Send (types.Sender interface)
// ---------------------------------------------------------------------------

// Send delivers an outbound message using routing information from the inbound
// metadata. Dispatches based on the OutboundMessage concrete type.
func (c *Protocol) Send(ctx context.Context, meta types.Metadata, msg types.OutboundMessage) error {
	lm, ok := meta.(*types.AgentTeamMeta)
	if !ok {
		return fmt.Errorf("agentteam: expected AgentTeamMeta, got %T", meta)
	}
	target := lm.Target
	if target == "" {
		return fmt.Errorf("agentteam: missing agentteam_target in metadata")
	}

	agentID := lm.AgentID
	if agentID == "" {
		_, _, _, _, agentID, _ = c.identitySnapshot()
	}

	// Send text first if present.
	if msg.Content != "" {
		if err := c.SendText(ctx, agentID, target, msg.Content); err != nil {
			return err
		}
	}
	// Send attachments.
	for _, att := range msg.Attachments {
		if err := c.SendMedia(ctx, agentID, target, att.Path, ""); err != nil {
			return err
		}
	}
	return nil
}

// ---------------------------------------------------------------------------
// Outbound: SendText / SendMedia (channel-specific helpers)
// ---------------------------------------------------------------------------

// SendText sends a text message to the specified target via the AgentTeam HTTP API.
func (c *Protocol) SendText(ctx context.Context, agentID, target, text string) error {
	if strings.TrimSpace(target) == "" {
		return fmt.Errorf("agentteam: target is required")
	}

	effectiveAgentID := agentID
	if effectiveAgentID == "" {
		_, _, _, _, effectiveAgentID, _ = c.identitySnapshot()
	}

	chunks := apiclient.SplitMessageText(text, textMaxBytes)
	for _, chunk := range chunks {
		if err := c.doSend(ctx, effectiveAgentID, target, chunk, nil); err != nil {
			return err
		}
	}
	return nil
}

// SendMedia uploads a file and sends it as a message to the specified target.
func (c *Protocol) SendMedia(ctx context.Context, agentID, target, filePath, caption string) error {
	if strings.TrimSpace(target) == "" {
		return fmt.Errorf("agentteam: target is required")
	}

	effectiveAgentID := agentID
	if effectiveAgentID == "" {
		_, _, _, _, effectiveAgentID, _ = c.identitySnapshot()
	}

	// Resolve the target scope before uploading the attachment.
	resolved, err := c.resolveChannel(ctx, effectiveAgentID, target)
	if err != nil {
		return fmt.Errorf("agentteam resolve channel: %w", err)
	}

	// Upload the file through the workspace-scoped runtime route when this
	// desktop profile is bound to a workspace. Old profiles without a persisted
	// workspace id fall back to the Deprecated internal runtime route.
	attachmentID, err := c.uploadFile(ctx, effectiveAgentID, filePath, resolved.ScopeID)
	if err != nil {
		resolved.InvalidateOnError(err)
		return fmt.Errorf("agentteam upload: %w", err)
	}

	return c.doSend(ctx, effectiveAgentID, target, caption, []string{attachmentID})
}

func (c *Protocol) doSend(ctx context.Context, agentID, target, content string, attachmentIDs []string) error {
	body := map[string]any{
		"target":        target,
		"content":       content,
		"attachmentIds": attachmentIDs,
	}

	start := time.Now()
	api := c.APIClient(agentID)
	err := api.Post(ctx, api.AgentRuntimePath("/send"), body, nil)
	elapsed := time.Since(start)
	if err != nil {
		c.metrics.sendErrors.Add(1)
		return fmt.Errorf("send request failed: %w", err)
	}

	c.metrics.sendSuccess.Add(1)
	c.metrics.recordSendLatency(elapsed)
	c.recordSentStats()
	return nil
}

func (c *Protocol) uploadFile(ctx context.Context, agentID, filePath, scopeID string) (string, error) {
	api := c.APIClient(agentID)
	attachmentID, err := api.PostMultipart(
		ctx, api.AgentRuntimePath("/upload"), filePath, "file",
		map[string]string{"scopeId": scopeID}, nil,
	)
	if err != nil {
		return "", fmt.Errorf("upload request failed: %w", err)
	}
	return attachmentID, nil
}

// resolveChannel resolves a target (e.g. "#general") to a cache-bound scope
// handle via the AgentTeam API. The method name is legacy because the server route
// is still named resolve-channel.
func (c *Protocol) resolveChannel(ctx context.Context, agentID, target string) (apiclient.ResolvedTarget, error) {
	api := c.APIClient(agentID)
	resolved, err := api.ResolveTarget(ctx, target, apiclient.ResolvePurposeWritable)
	if err != nil {
		return apiclient.ResolvedTarget{}, fmt.Errorf("resolve channel request failed: %w", err)
	}
	return resolved, nil
}
