package taskboard

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

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/attachment"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

const taskScopeMessageTypeDiscussion = "task_discussion"

type taskScopeMessageTaskMetadata struct {
	ID      string `json:"id"`
	ScopeID string `json:"scopeId"`
}

type taskScopeMessageAppendOptions struct {
	Seq                      int64
	ClientMessageID          string
	IdempotencyPayloadDigest string
	AllowEmptyBody           bool
	AttachmentIDs            []string
}

func (txs TxStore) appendTaskScopeMessage(ctx context.Context, tx coredb.Tx, workspaceID string, channelID string, taskID string, taskScopeID string, messageID string, actorMemberID string, authorType string, body string, metadata map[string]any, now time.Time, options taskScopeMessageAppendOptions) (int64, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedTaskScopeID := strings.TrimSpace(taskScopeID)
	trimmedMessageID := strings.TrimSpace(messageID)
	trimmedActorMemberID := strings.TrimSpace(actorMemberID)
	trimmedBody := strings.TrimSpace(body)
	trimmedAuthorType := strings.TrimSpace(authorType)
	if trimmedWorkspaceID == "" || trimmedTaskScopeID == "" || trimmedMessageID == "" || trimmedActorMemberID == "" || trimmedAuthorType == "" || (!options.AllowEmptyBody && trimmedBody == "") {
		return 0, fmt.Errorf("task scope discussion message requires workspace, task scope, message, actor, author type, and body or attachment")
	}

	seq := options.Seq
	if seq <= 0 {
		var err error
		seq, err = message.AllocateNextScopeMessageSeq(ctx, tx, trimmedTaskScopeID)
		if err != nil {
			return 0, err
		}
	}
	// Task scopes use the scope-local seq as the workspace_global_seq
	// compatibility alias. Do not bump workspaces.latest_message_seq; keeping
	// task discussion on the normal scope write path avoids reintroducing the
	// workspace hot row.
	globalSeq := seq

	var encodedMetadata []byte
	if len(metadata) > 0 {
		var err error
		encodedMetadata, _, err = message.EncodeMessageMetadata(metadata)
		if err != nil {
			return 0, err
		}
	}

	plan := message.MessagePlan{
		MessageWrite: message.ScopeMessageRecord{
			ID:                         trimmedMessageID,
			WorkspaceID:                trimmedWorkspaceID,
			ScopeID:                    trimmedTaskScopeID,
			Seq:                        seq,
			WorkspaceGlobalSeq:         globalSeq,
			AuthorMemberID:             trimmedActorMemberID,
			AuthorType:                 trimmedAuthorType,
			NotifyPrincipalMemberID:    trimmedActorMemberID,
			IdempotencySubjectMemberID: trimmedActorMemberID,
			ClientMessageID:            strings.TrimSpace(options.ClientMessageID),
			IdempotencyPayloadDigest:   strings.TrimSpace(options.IdempotencyPayloadDigest),
			MessageType:                message.MessageTypeTaskDiscussion,
			Content:                    trimmedBody,
			Metadata:                   encodedMetadata,
			CreatedAt:                  now,
			UpdatedAt:                  now,
		},
		SideEffectProfile: message.MessagePlanSideEffectProfileScopeFanout,
		OutboxEvents: []message.ScopeFanoutOutboxRecord{
			message.BuildScopeMessageFanoutOutboxRecord(message.ScopeMessageFanoutOutboxRecordParams{
				WorkspaceID:           trimmedWorkspaceID,
				ScopeID:               trimmedTaskScopeID,
				MessageID:             trimmedMessageID,
				TargetedHumanDelivery: true,
				CreatedAt:             now,
				NextAttemptAt:         now.Add(5 * time.Second),
			}),
		},
	}
	if attachmentIDs := normalizeUniqueStringList(options.AttachmentIDs); len(attachmentIDs) > 0 {
		plan.SidecarWrites.AttachmentClaims = append(plan.SidecarWrites.AttachmentClaims, message.AttachmentClaimWrite{
			WorkspaceID:        trimmedWorkspaceID,
			ScopeID:            trimmedTaskScopeID,
			UploadedByMemberID: trimmedActorMemberID,
			MessageID:          trimmedMessageID,
			AttachmentIDs:      attachmentIDs,
		})
	}
	if _, err := message.ApplyMessagePlanTxWithSidecarWriter(ctx, tx, plan, taskScopeDiscussionSidecarWriter{
		channelID: strings.TrimSpace(channelID),
		taskID:    strings.TrimSpace(taskID),
	}); err != nil {
		return 0, fmt.Errorf("apply task scope discussion message plan: %w", err)
	}
	return seq, nil
}

type taskScopeDiscussionSidecarWriter struct {
	channelID string
	taskID    string
}

func (writer taskScopeDiscussionSidecarWriter) ClaimAttachmentsForMessage(ctx context.Context, tx coredb.Tx, claim message.AttachmentClaimWrite) ([]attachment.AppAttachment, error) {
	if len(claim.AttachmentIDs) == 0 {
		return nil, nil
	}
	if err := claimAttachmentsForTaskDiscussionMessage(ctx, tx, claim.WorkspaceID, writer.channelID, writer.taskID, claim.ScopeID, claim.UploadedByMemberID, claim.MessageID, claim.AttachmentIDs); err != nil {
		return nil, err
	}
	return loadTaskScopeDiscussionClaimedAttachments(ctx, tx, claim.WorkspaceID, claim.MessageID)
}

func (writer taskScopeDiscussionSidecarWriter) CopyMessageAttachments(context.Context, coredb.Tx, message.AttachmentCopyWrite) ([]attachment.AppAttachment, error) {
	return nil, nil
}

func loadTaskScopeDiscussionClaimedAttachments(ctx context.Context, queryer coredb.Queryer, workspaceID string, messageID string) ([]attachment.AppAttachment, error) {
	query, args, err := attachment.BuildLoadAttachmentsForMessagesQuery(strings.TrimSpace(workspaceID), []string{strings.TrimSpace(messageID)})
	if err != nil {
		return nil, err
	}
	rows, err := queryer.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("load task discussion claimed attachments: %w", err)
	}
	defer rows.Close()

	var attachments []attachment.AppAttachment
	for rows.Next() {
		var rowMessageID string
		var item attachment.AppAttachment
		if err := rows.Scan(&rowMessageID, &item.ID, &item.Filename, &item.ContentType, &item.SizeBytes); err != nil {
			return nil, fmt.Errorf("scan task discussion claimed attachment: %w", err)
		}
		attachments = append(attachments, item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate task discussion claimed attachments: %w", err)
	}
	return attachments, nil
}

func taskScopeDiscussionAuthorType(allowAgentAuthor bool) string {
	if allowAgentAuthor {
		return "agent"
	}
	return "human"
}
