// Package channel provides a unified abstraction for multi-platform messaging channels.
// It defines types, interfaces, and a registry for channel adapters such as Telegram and Feishu.
package channel

import (
	"strings"
	"time"
)

// ChannelType identifies a messaging platform (e.g., "telegram", "feishu").
type ChannelType string

// String returns the channel type as a plain string.
func (c ChannelType) String() string {
	return string(c)
}

// Identity represents a sender's identity on a channel.
type Identity struct {
	SubjectID   string
	DisplayName string
	Attributes  map[string]string
}

// Attribute returns the trimmed value for the given key, or empty string if absent.
func (i Identity) Attribute(key string) string {
	if i.Attributes == nil {
		return ""
	}
	return strings.TrimSpace(i.Attributes[key])
}

// Conversation holds metadata about the chat or group context.
type Conversation struct {
	ID       string
	Type     string
	Name     string
	ThreadID string
	Metadata map[string]any
}

// InboundMessage is a message received from an external channel.
type InboundMessage struct {
	Channel      ChannelType
	Message      Message
	BotID        string
	ReplyTarget  string
	RouteKey     string
	Sender       Identity
	Conversation Conversation
	ReceivedAt   time.Time
	Source       string
	Metadata     map[string]any
}

// RoutingKey returns a stable identifier used for reply routing.
// Format: platform:bot_id:conversation_id[:sender_id].
func (m InboundMessage) RoutingKey() string {
	if strings.TrimSpace(m.RouteKey) != "" {
		return strings.TrimSpace(m.RouteKey)
	}
	senderID := strings.TrimSpace(m.Sender.SubjectID)
	if senderID == "" {
		senderID = strings.TrimSpace(m.Sender.DisplayName)
	}
	return GenerateRoutingKey(string(m.Channel), m.BotID, m.Conversation.ID, m.Conversation.Type, senderID)
}

// GenerateRoutingKey builds a route key from platform, bot, conversation, and sender info.
// For group chats, the sender ID is appended to provide per-user context.
func GenerateRoutingKey(platform, botID, conversationID, conversationType, senderID string) string {
	parts := []string{platform, botID, conversationID}
	ct := strings.ToLower(strings.TrimSpace(conversationType))
	if ct != "" && ct != "p2p" && ct != "private" {
		senderID = strings.TrimSpace(senderID)
		if senderID != "" {
			parts = append(parts, senderID)
		}
	}
	return strings.Join(parts, ":")
}

// OutboundMessage pairs a delivery target with the message content.
type OutboundMessage struct {
	Target  string  `json:"target"`
	Message Message `json:"message"`
}

// StreamEventType defines the kind of outbound stream event.
type StreamEventType string

const (
	StreamEventStatus              StreamEventType = "status"
	StreamEventDelta               StreamEventType = "delta"
	StreamEventFinal               StreamEventType = "final"
	StreamEventError               StreamEventType = "error"
	StreamEventToolCallStart       StreamEventType = "tool_call_start"
	StreamEventToolCallEnd         StreamEventType = "tool_call_end"
	StreamEventPhaseStart          StreamEventType = "phase_start"
	StreamEventPhaseEnd            StreamEventType = "phase_end"
	StreamEventAttachment          StreamEventType = "attachment"
	StreamEventAgentStart          StreamEventType = "agent_start"
	StreamEventAgentEnd            StreamEventType = "agent_end"
	StreamEventProcessingStarted   StreamEventType = "processing_started"
	StreamEventProcessingCompleted StreamEventType = "processing_completed"
	StreamEventProcessingFailed    StreamEventType = "processing_failed"
)

// StreamStatus indicates the lifecycle state of a streaming reply.
type StreamStatus string

const (
	StreamStatusStarted   StreamStatus = "started"
	StreamStatusCompleted StreamStatus = "completed"
	StreamStatusFailed    StreamStatus = "failed"
)

// StreamFinalizePayload carries the final reply message emitted by a stream.
type StreamFinalizePayload struct {
	Message Message `json:"message"`
}

// StreamToolCall carries tool invocation data for tool_call_start / tool_call_end events.
type StreamToolCall struct {
	Name   string `json:"name"`
	CallID string `json:"call_id,omitempty"`
	Input  any    `json:"input,omitempty"`
	Result any    `json:"result,omitempty"`
}

// StreamPhase labels a processing stage within a stream (e.g., reasoning, text).
type StreamPhase string

const (
	StreamPhaseReasoning StreamPhase = "reasoning"
	StreamPhaseText      StreamPhase = "text"
)

// StreamEvent represents a unified stream event routed through the channel layer.
type StreamEvent struct {
	Type        StreamEventType        `json:"type"`
	Status      StreamStatus           `json:"status,omitempty"`
	Delta       string                 `json:"delta,omitempty"`
	Final       *StreamFinalizePayload `json:"final,omitempty"`
	Error       string                 `json:"error,omitempty"`
	ToolCall    *StreamToolCall        `json:"tool_call,omitempty"`
	Phase       StreamPhase            `json:"phase,omitempty"`
	Attachments []Attachment           `json:"attachments,omitempty"`
	Metadata    map[string]any         `json:"metadata,omitempty"`
}

// StreamOptions configures how an outbound stream is initialized.
type StreamOptions struct {
	Reply           *ReplyRef      `json:"reply,omitempty"`
	SourceMessageID string         `json:"source_message_id,omitempty"`
	Metadata        map[string]any `json:"metadata,omitempty"`
}

// MessageFormat indicates how the message text should be rendered.
type MessageFormat string

const (
	MessageFormatPlain    MessageFormat = "plain"
	MessageFormatMarkdown MessageFormat = "markdown"
	MessageFormatRich     MessageFormat = "rich"
)

// MessagePartType identifies the kind of a rich-text message part.
type MessagePartType string

const (
	MessagePartText      MessagePartType = "text"
	MessagePartLink      MessagePartType = "link"
	MessagePartCodeBlock MessagePartType = "code_block"
	MessagePartMention   MessagePartType = "mention"
	MessagePartEmoji     MessagePartType = "emoji"
)

// MessageTextStyle describes inline formatting for a text part.
type MessageTextStyle string

const (
	MessageStyleBold          MessageTextStyle = "bold"
	MessageStyleItalic        MessageTextStyle = "italic"
	MessageStyleStrikethrough MessageTextStyle = "strikethrough"
	MessageStyleCode          MessageTextStyle = "code"
)

// MessagePart is a single element within a rich-text message.
type MessagePart struct {
	Type              MessagePartType    `json:"type"`
	Text              string             `json:"text,omitempty"`
	URL               string             `json:"url,omitempty"`
	Styles            []MessageTextStyle `json:"styles,omitempty"`
	Language          string             `json:"language,omitempty"`
	ChannelIdentityID string             `json:"channel_identity_id,omitempty"`
	Emoji             string             `json:"emoji,omitempty"`
	Metadata          map[string]any     `json:"metadata,omitempty"`
}

// AttachmentType classifies the kind of binary attachment.
type AttachmentType string

const (
	AttachmentImage AttachmentType = "image"
	AttachmentAudio AttachmentType = "audio"
	AttachmentVideo AttachmentType = "video"
	AttachmentVoice AttachmentType = "voice"
	AttachmentFile  AttachmentType = "file"
	AttachmentGIF   AttachmentType = "gif"
)

// Attachment represents a binary file attached to a message.
type Attachment struct {
	Type           AttachmentType `json:"type"`
	URL            string         `json:"url,omitempty"`
	PlatformKey    string         `json:"platform_key,omitempty"`
	SourcePlatform string         `json:"source_platform,omitempty"`
	ContentHash    string         `json:"content_hash,omitempty"`
	Base64         string         `json:"base64,omitempty"` // data URL for agent delivery
	Name           string         `json:"name,omitempty"`
	Size           int64          `json:"size,omitempty"`
	Mime           string         `json:"mime,omitempty"`
	DurationMs     int64          `json:"duration_ms,omitempty"`
	Width          int            `json:"width,omitempty"`
	Height         int            `json:"height,omitempty"`
	ThumbnailURL   string         `json:"thumbnail_url,omitempty"`
	Caption        string         `json:"caption,omitempty"`
	Metadata       map[string]any `json:"metadata,omitempty"`
}

// Reference returns the strongest available attachment reference.
// URL is preferred for cross-platform portability, then platform key.
func (a Attachment) Reference() string {
	if strings.TrimSpace(a.URL) != "" {
		return strings.TrimSpace(a.URL)
	}
	return strings.TrimSpace(a.PlatformKey)
}

// HasReference reports whether URL or platform key is available.
func (a Attachment) HasReference() bool {
	return a.Reference() != ""
}

// Action describes an interactive button or link in a message.
type Action struct {
	Type  string `json:"type"`
	Label string `json:"label,omitempty"`
	Value string `json:"value,omitempty"`
	URL   string `json:"url,omitempty"`
}

// ThreadRef references a conversation thread by ID.
type ThreadRef struct {
	ID string `json:"id"`
}

// ReplyRef points to a message being replied to.
type ReplyRef struct {
	Target    string `json:"target,omitempty"`
	MessageID string `json:"message_id,omitempty"`
}

// Message is the unified message structure used across all channels.
type Message struct {
	ID          string         `json:"id,omitempty"`
	Format      MessageFormat  `json:"format,omitempty"`
	Text        string         `json:"text,omitempty"`
	Parts       []MessagePart  `json:"parts,omitempty"`
	Attachments []Attachment   `json:"attachments,omitempty"`
	Actions     []Action       `json:"actions,omitempty"`
	Thread      *ThreadRef     `json:"thread,omitempty"`
	Reply       *ReplyRef      `json:"reply,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

// IsEmpty reports whether the message carries no content.
func (m Message) IsEmpty() bool {
	return strings.TrimSpace(m.Text) == "" &&
		len(m.Parts) == 0 &&
		len(m.Attachments) == 0 &&
		len(m.Actions) == 0
}

// PlainText extracts the plain text representation of the message.
func (m Message) PlainText() string {
	if strings.TrimSpace(m.Text) != "" {
		return strings.TrimSpace(m.Text)
	}
	if len(m.Parts) == 0 {
		return ""
	}
	lines := make([]string, 0, len(m.Parts))
	for _, part := range m.Parts {
		switch part.Type {
		case MessagePartText, MessagePartLink, MessagePartCodeBlock, MessagePartMention, MessagePartEmoji:
			value := strings.TrimSpace(part.Text)
			if value == "" && part.Type == MessagePartLink {
				value = strings.TrimSpace(part.URL)
			}
			if value == "" && part.Type == MessagePartEmoji {
				value = strings.TrimSpace(part.Emoji)
			}
			if value == "" {
				continue
			}
			lines = append(lines, value)
		default:
			continue
		}
	}
	return strings.Join(lines, "\n")
}

// BindingCriteria specifies conditions for matching a user-channel binding.
type BindingCriteria struct {
	SubjectID  string
	Attributes map[string]string
}

// Attribute returns the trimmed value for the given key, or empty string if absent.
func (c BindingCriteria) Attribute(key string) string {
	if c.Attributes == nil {
		return ""
	}
	return strings.TrimSpace(c.Attributes[key])
}

// BindingCriteriaFromIdentity creates BindingCriteria from a channel Identity.
func BindingCriteriaFromIdentity(identity Identity) BindingCriteria {
	return BindingCriteria{
		SubjectID:  strings.TrimSpace(identity.SubjectID),
		Attributes: identity.Attributes,
	}
}

// ChannelConfig holds the configuration for a bot's channel integration.
// Disabled: true means the channel is stopped (not connected); false means enabled.
type ChannelConfig struct {
	ID               string         `json:"id"`
	BotID            string         `json:"bot_id"`
	ChannelType      ChannelType    `json:"channel_type"`
	Credentials      map[string]any `json:"credentials"`
	ExternalIdentity string         `json:"external_identity"`
	SelfIdentity     map[string]any `json:"self_identity"`
	Routing          map[string]any `json:"routing"`
	Disabled         bool           `json:"disabled"`
	VerifiedAt       time.Time      `json:"verified_at"`
	CreatedAt        time.Time      `json:"created_at"`
	UpdatedAt        time.Time      `json:"updated_at"`
}

// ChannelIdentityBinding represents a channel identity's binding to a specific channel type.
type ChannelIdentityBinding struct {
	ID                string         `json:"id"`
	ChannelType       ChannelType    `json:"channel_type"`
	ChannelIdentityID string         `json:"channel_identity_id"`
	Config            map[string]any `json:"config"`
	CreatedAt         time.Time      `json:"created_at"`
	UpdatedAt         time.Time      `json:"updated_at"`
}

// UpsertConfigRequest is the input for creating or updating a channel configuration.
// Disabled: true to stop the channel, false to enable it. Omitted is treated as false (enabled).
type UpsertConfigRequest struct {
	Credentials      map[string]any `json:"credentials"`
	ExternalIdentity string         `json:"external_identity,omitempty"`
	SelfIdentity     map[string]any `json:"self_identity,omitempty"`
	Routing          map[string]any `json:"routing,omitempty"`
	Disabled         *bool          `json:"disabled,omitempty"`
	VerifiedAt       *time.Time     `json:"verified_at,omitempty"`
}

// UpsertChannelIdentityConfigRequest is the input for creating or updating a channel-identity binding.
type UpsertChannelIdentityConfigRequest struct {
	Config map[string]any `json:"config"`
}

// UpdateChannelStatusRequest is the input for enabling/disabling a bot channel config.
type UpdateChannelStatusRequest struct {
	Disabled bool `json:"disabled"`
}

// SendRequest is the input for sending an outbound message through a channel.
type SendRequest struct {
	Target            string  `json:"target,omitempty"`
	ChannelIdentityID string  `json:"channel_identity_id,omitempty"`
	Message           Message `json:"message"`
}

// ReactRequest is the input for adding or removing an emoji reaction on a message.
type ReactRequest struct {
	Target    string `json:"target"`
	MessageID string `json:"message_id"`
	Emoji     string `json:"emoji"`
	Remove    bool   `json:"remove,omitempty"`
}
