package app

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

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

const (
	threadShareSnapshotMaxMessages = 200
	threadShareSnapshotMaxBytes    = 512 * 1024
)

type threadShareCardRecord struct {
	AnchorMessageID string
	MessageType     string
	Payload         threadShareCardPayload
}

type threadShareCardPayload struct {
	SourceThreadChannelID      string                               `json:"sourceThreadChannelId"`
	SourceParentChannelID      string                               `json:"sourceParentChannelId"`
	SourceParentChannelType    string                               `json:"sourceParentChannelType"`
	SourceParentMessageID      string                               `json:"sourceParentMessageId"`
	SourceParentMessageShortID string                               `json:"sourceParentMessageShortId"`
	SourceLabel                string                               `json:"sourceLabel"`
	Title                      string                               `json:"title"`
	Preview                    string                               `json:"preview"`
	MessageCount               int                                  `json:"messageCount"`
	ParticipantCount           int                                  `json:"participantCount"`
	Messages                   []message.ThreadShareSnapshotMessage `json:"messages,omitempty"`
}

type threadShareSourceMessage struct {
	ID             string
	Seq            int64
	AuthorMemberID string
	AuthorType     string
	AuthorName     string
	Content        string
	CreatedAt      time.Time
}

func threadShareCardMetadata(payload threadShareCardPayload) map[string]any {
	return map[string]any{
		"threadShare": map[string]any{
			"sourceThreadChannelId":      strings.TrimSpace(payload.SourceThreadChannelID),
			"sourceParentChannelId":      strings.TrimSpace(payload.SourceParentChannelID),
			"sourceParentChannelType":    strings.TrimSpace(payload.SourceParentChannelType),
			"sourceParentMessageShortId": strings.TrimSpace(payload.SourceParentMessageShortID),
			"sourceLabel":                strings.TrimSpace(payload.SourceLabel),
			"title":                      strings.TrimSpace(payload.Title),
			"preview":                    strings.TrimSpace(payload.Preview),
			"messageCount":               payload.MessageCount,
			"participantCount":           payload.ParticipantCount,
		},
	}
}

func decodeThreadShareCardPayload(raw []byte) (threadShareCardPayload, error) {
	var payload threadShareCardPayload
	if err := model.UnmarshalJSONObject("thread share card payload", raw, &payload); err != nil {
		return threadShareCardPayload{}, err
	}
	payload.SourceThreadChannelID = strings.TrimSpace(payload.SourceThreadChannelID)
	payload.SourceParentChannelID = strings.TrimSpace(payload.SourceParentChannelID)
	payload.SourceParentChannelType = strings.TrimSpace(payload.SourceParentChannelType)
	payload.SourceParentMessageID = strings.TrimSpace(payload.SourceParentMessageID)
	payload.SourceParentMessageShortID = strings.TrimSpace(payload.SourceParentMessageShortID)
	payload.SourceLabel = strings.TrimSpace(payload.SourceLabel)
	payload.Title = strings.TrimSpace(payload.Title)
	payload.Preview = strings.TrimSpace(payload.Preview)
	for i := range payload.Messages {
		payload.Messages[i].ID = strings.TrimSpace(payload.Messages[i].ID)
		payload.Messages[i].AuthorName = strings.TrimSpace(payload.Messages[i].AuthorName)
		payload.Messages[i].CreatedAt = strings.TrimSpace(payload.Messages[i].CreatedAt)
	}
	return payload, nil
}

func threadShareSourceLabel(threadAccess access.HumanThreadAccess) string {
	if label := strings.TrimSpace(threadAccess.ParentChannelName); label != "" {
		return label
	}
	if label := strings.TrimSpace(threadAccess.ParentChannelID); label != "" {
		return label
	}
	return strings.TrimSpace(threadAccess.Channel.ChannelID)
}

func compactThreadShareText(content string, fallback string, limit int) string {
	trimmed := strings.TrimSpace(strings.Join(strings.Fields(content), " "))
	if trimmed == "" {
		trimmed = strings.TrimSpace(fallback)
	}
	if limit <= 0 || trimmed == "" {
		return trimmed
	}
	runes := []rune(trimmed)
	if len(runes) <= limit {
		return trimmed
	}
	return strings.TrimSpace(string(runes[:limit])) + "…"
}

func threadShareParticipantCount(messages []threadShareSourceMessage) int {
	seen := make(map[string]struct{}, len(messages))
	for _, appMessage := range messages {
		key := strings.TrimSpace(appMessage.AuthorMemberID)
		if key == "" {
			key = strings.TrimSpace(appMessage.AuthorType) + ":" + strings.TrimSpace(appMessage.AuthorName)
		}
		if key == ":" || key == "" {
			continue
		}
		seen[key] = struct{}{}
	}
	return len(seen)
}

func threadShareSnapshotMessage(appMessage threadShareSourceMessage) message.ThreadShareSnapshotMessage {
	return message.ThreadShareSnapshotMessage{
		ID:         strings.TrimSpace(appMessage.ID),
		AuthorName: strings.TrimSpace(appMessage.AuthorName),
		Content:    appMessage.Content,
		CreatedAt:  appMessage.CreatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func threadShareSnapshotMessages(messages []threadShareSourceMessage) []message.ThreadShareSnapshotMessage {
	if len(messages) == 0 {
		return nil
	}
	snapshot := make([]message.ThreadShareSnapshotMessage, 0, len(messages))
	for _, appMessage := range messages {
		snapshot = append(snapshot, threadShareSnapshotMessage(appMessage))
	}
	return snapshot
}

func (store *Store) loadThreadShareMessages(ctx context.Context, queryer coredb.Queryer, workspaceID string, threadChannelID string) ([]threadShareSourceMessage, error) {
	replyLimit := threadShareSnapshotMaxMessages
	rows, err := queryer.Query(ctx, fmt.Sprintf(`
WITH root_message AS (
    SELECT
        pm.id,
        pm.seq,
        pm.author_member_id,
        pm.author_type,
        pm.content,
        pm.created_at,
        0 AS sort_group
    FROM scopes reply_scope
    JOIN scope_messages pm
      ON pm.workspace_id = reply_scope.workspace_id
     AND pm.scope_id = reply_scope.parent_scope_id
     AND pm.id = reply_scope.root_message_id
    WHERE reply_scope.workspace_id = $1
      AND reply_scope.id = $2
      AND reply_scope.type = 'thread'
      AND %s
), reply_page AS (
    SELECT
        m.id,
        m.seq,
        m.author_member_id,
        m.author_type,
        m.content,
        m.created_at,
        1 AS sort_group
    FROM scope_messages m
    WHERE m.workspace_id = $1
      AND m.scope_id = $2
      AND %s
    ORDER BY m.seq DESC
    LIMIT $3
)
SELECT
    page.id,
    page.seq,
    page.author_member_id,
    page.author_type,
    COALESCE(NULLIF(u.display_name, ''), NULLIF(sender.display_name, ''), NULLIF(u.name, ''), NULLIF(a.name, ''), sender.id) AS author_name,
    page.content,
    page.created_at
FROM (
    SELECT id, seq, author_member_id, author_type, content, created_at, sort_group
    FROM root_message
    UNION ALL
    SELECT id, seq, author_member_id, author_type, content, created_at, sort_group
    FROM reply_page
) page
JOIN members sender ON sender.id = page.author_member_id
LEFT JOIN users u ON page.author_type = 'human' AND u.id = sender.user_id
LEFT JOIN agents a ON page.author_type = 'agent' AND a.member_id = sender.id
ORDER BY page.sort_group ASC, page.seq ASC
`, humanVisibleMessageTypeSQL("pm"), humanVisibleMessageTypeSQL("m")), strings.TrimSpace(workspaceID), strings.TrimSpace(threadChannelID), replyLimit)
	if err != nil {
		return nil, fmt.Errorf("load thread share messages: %w", err)
	}
	defer rows.Close()

	messages := make([]threadShareSourceMessage, 0)
	seen := make(map[string]struct{})
	for rows.Next() {
		var appMessage threadShareSourceMessage
		if err := rows.Scan(&appMessage.ID, &appMessage.Seq, &appMessage.AuthorMemberID, &appMessage.AuthorType, &appMessage.AuthorName, &appMessage.Content, &appMessage.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan thread share message: %w", err)
		}
		messageID := strings.TrimSpace(appMessage.ID)
		if _, ok := seen[messageID]; ok {
			continue
		}
		seen[messageID] = struct{}{}
		messages = append(messages, appMessage)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate thread share messages: %w", err)
	}
	if len(messages) == 0 {
		return nil, ErrNotFound
	}
	return messages, nil
}

func (store *Store) buildThreadShareSnapshot(ctx context.Context, tx coredb.Tx, workspaceID string, threadAccess access.HumanThreadAccess) (threadShareCardPayload, error) {
	threadChannelID := strings.TrimSpace(threadAccess.Channel.ChannelID)
	messages, err := store.loadThreadShareMessages(ctx, tx, workspaceID, threadChannelID)
	if err != nil {
		return threadShareCardPayload{}, err
	}

	if len(messages) > threadShareSnapshotMaxMessages {
		return threadShareCardPayload{}, badRequest(fmt.Sprintf("thread has too many messages to share (max %d)", threadShareSnapshotMaxMessages))
	}

	root := messages[0]
	payload := threadShareCardPayload{
		SourceThreadChannelID:      threadChannelID,
		SourceParentChannelID:      strings.TrimSpace(threadAccess.ParentChannelID),
		SourceParentChannelType:    string(threadAccess.ParentThreadType),
		SourceParentMessageID:      strings.TrimSpace(threadAccess.ParentMessageID),
		SourceParentMessageShortID: strings.TrimSpace(threadAccess.ParentMessageShort),
		SourceLabel:                threadShareSourceLabel(threadAccess),
		Title:                      compactThreadShareText(root.Content, "Forwarded topic", 80),
		Preview:                    compactThreadShareText(root.Content, "", 160),
		MessageCount:               len(messages),
		ParticipantCount:           threadShareParticipantCount(messages),
		Messages:                   threadShareSnapshotMessages(messages),
	}
	return payload, nil
}

func (store *Store) CreateThreadShareCardForUser(ctx context.Context, user model.User, workspaceID string, sourceThreadChannelID string, targetScopeID string) (message.AppMessage, message.MessageDelivery, error) {
	var appMessage message.AppMessage
	var delivery message.MessageDelivery
	tx, err := store.db.BeginTx(ctx, coredb.TxOptions{})
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, fmt.Errorf("begin create thread share card tx: %w", err)
	}
	defer rollback(tx, ctx)

	threadID := strings.TrimSpace(sourceThreadChannelID)
	if threadID == "" {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("threadChannelId is required")
	}
	targetID := strings.TrimSpace(targetScopeID)
	if targetID == "" {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("targetScopeId is required")
	}

	threadAccess, err := store.loadAuthorizedHumanThreadAccess(ctx, tx, user.ID, workspaceID, threadID, threadReadAction)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	snapshot, err := store.buildThreadShareSnapshot(ctx, tx, workspaceID, threadAccess)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	shareCardPayload, err := model.MarshalJSONObject("thread share card payload", snapshot)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	if len(shareCardPayload) > threadShareSnapshotMaxBytes {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("thread share snapshot is too large")
	}

	cardID, err := identifiers.NewEntityID("tsc")
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, fmt.Errorf("generate thread share card id: %w", err)
	}
	content := fmt.Sprintf("Shared topic: %s", snapshot.Title)
	prepared, err := store.prepareAppMessageForUserTx(ctx, tx, user, workspaceID, targetID, content, nil, SendAppMessageOptions{
		MessageType: MessageTypeThreadShareCard,
		Metadata:    threadShareCardMetadata(snapshot),
	})
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	targetScopeIDVal := appChannelIDForTarget(prepared.target)
	targetID = targetScopeIDVal
	if targetID == "" {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("targetScopeId is required")
	}

	delivery, err = store.appendMessageToThread(ctx, tx, prepared.membership.Member.ID, domain.MemberTypeHuman, prepared.target, prepared.trimmedContent, prepared.normalizedAttachmentIDs, appendMessageOptions{
		MessageType:         MessageTypeThreadShareCard,
		ExplicitRoutingPlan: prepared.explicitRoutingPlan,
		Metadata:            prepared.metadata,
	}, messageAppendCallerOwned)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}

	createdAt := delivery.Message.Timestamp
	if err := message.NewScopeRepositories(tx).ShareCards.Insert(ctx, message.ScopeShareCardRecord{
		ID:                cardID,
		WorkspaceID:       workspaceID,
		ScopeID:           targetScopeID,
		MessageID:         delivery.MessageID,
		SourceScopeID:     threadID,
		SourceMessageID:   snapshot.SourceParentMessageID,
		TargetScopeID:     targetScopeID,
		CreatedByMemberID: prepared.membership.Member.ID,
		Payload:           shareCardPayload,
		CreatedAt:         createdAt,
		UpdatedAt:         createdAt,
	}); err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}

	appMessage = appMessageFromDelivery(
		targetScopeID,
		humanAuthorProfileFromUser(user, prepared.membership.Member.ID, prepared.membership.Member.DisplayName),
		delivery,
	)
	if err := attachResolvedMentionsForSentAppMessage(ctx, tx, workspaceID, &appMessage); err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	attachAppMessageToDelivery(&delivery, &appMessage)
	if err := store.persistScopeFanoutPayloads(ctx, tx, delivery); err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	if err := tx.Commit(ctx); err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, fmt.Errorf("commit create thread share card: %w", err)
	}
	return appMessage, delivery, nil
}

func (store *Store) loadThreadShareCardRecordForUser(ctx context.Context, queryer coredb.QueryExecer, userID string, workspaceID string, messageID string) (access.HumanWorkspaceMember, threadShareCardRecord, error) {
	trimmedMessageID := strings.TrimSpace(messageID)
	if trimmedMessageID == "" {
		return access.HumanWorkspaceMember{}, threadShareCardRecord{}, badRequest("messageId is required")
	}
	query := `
SELECT
    sc.message_id,
    sm.message_type,
    sc.payload
FROM scope_share_cards sc
JOIN scope_messages sm
  ON sm.workspace_id = sc.workspace_id
 AND sm.scope_id = sc.scope_id
 AND sm.id = sc.message_id
WHERE sc.workspace_id = $1
  AND sc.message_id = $2
`
	var record threadShareCardRecord
	var payload []byte
	if err := queryer.QueryRow(ctx, query, strings.TrimSpace(workspaceID), trimmedMessageID).Scan(
		&record.AnchorMessageID,
		&record.MessageType,
		&payload,
	); err != nil {
		if coredb.IsNotFound(err) {
			return access.HumanWorkspaceMember{}, threadShareCardRecord{}, ErrNotFound
		}
		return access.HumanWorkspaceMember{}, threadShareCardRecord{}, fmt.Errorf("load thread share card: %w", err)
	}
	if strings.TrimSpace(record.MessageType) != MessageTypeThreadShareCard {
		return access.HumanWorkspaceMember{}, threadShareCardRecord{}, ErrNotFound
	}
	decoded, err := decodeThreadShareCardPayload(payload)
	if err != nil {
		return access.HumanWorkspaceMember{}, threadShareCardRecord{}, err
	}
	record.Payload = decoded

	membership, err := store.loadAuthorizedHumanWorkspaceMember(ctx, queryer, userID, workspaceID, workspaceAccessAction)
	if err != nil {
		return access.HumanWorkspaceMember{}, threadShareCardRecord{}, err
	}
	if err := store.authorizeMessageSidecarReadForMember(ctx, queryer, membership.Member.ID, workspaceID, record.AnchorMessageID, message.SidecarKindShareCard); err != nil {
		return access.HumanWorkspaceMember{}, threadShareCardRecord{}, err
	}
	return membership, record, nil
}

func (store *Store) threadShareCardView(ctx context.Context, queryer coredb.QueryExecer, membership access.HumanWorkspaceMember, record threadShareCardRecord) (message.ThreadShareCardView, error) {
	payload := record.Payload
	canOpenSource := false
	if strings.TrimSpace(payload.SourceThreadChannelID) != "" {
		userID := ""
		if membership.Member.UserID != nil {
			userID = strings.TrimSpace(*membership.Member.UserID)
		}
		if _, err := store.loadAuthorizedHumanThreadAccess(ctx, queryer, userID, membership.Member.WorkspaceID, payload.SourceThreadChannelID, threadReadAction); err == nil {
			canOpenSource = true
		} else if err != nil && err != ErrForbidden && err != ErrNotFound {
			return message.ThreadShareCardView{}, err
		}
	}
	sourceParentMessageID := ""
	if canOpenSource {
		sourceParentMessageID = strings.TrimSpace(payload.SourceParentMessageID)
	}
	return message.ThreadShareCardView{
		MessageID:                  strings.TrimSpace(record.AnchorMessageID),
		SourceParentChannelID:      strings.TrimSpace(payload.SourceParentChannelID),
		SourceParentMessageID:      sourceParentMessageID,
		SourceParentMessageShortID: strings.TrimSpace(payload.SourceParentMessageShortID),
		SourceLabel:                strings.TrimSpace(payload.SourceLabel),
		Title:                      strings.TrimSpace(payload.Title),
		Preview:                    strings.TrimSpace(payload.Preview),
		MessageCount:               payload.MessageCount,
		ParticipantCount:           payload.ParticipantCount,
		CanOpenSource:              canOpenSource,
		Messages:                   payload.Messages,
	}, nil
}

func (store *Store) LoadThreadShareCardForUser(ctx context.Context, userID string, workspaceID string, messageID string) (message.ThreadShareCardView, error) {
	membership, record, err := store.loadThreadShareCardRecordForUser(ctx, store.db, userID, workspaceID, messageID)
	if err != nil {
		return message.ThreadShareCardView{}, err
	}
	return store.threadShareCardView(ctx, store.db, membership, record)
}
