package privateagent

import (
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

func TestBuildConversationMessagePlanBindsOrdinaryMessageAndFanout(t *testing.T) {
	t.Parallel()

	createdAt := time.Date(2026, 5, 22, 1, 30, 0, 123, time.FixedZone("CST", 8*60*60))
	metadata := []byte(`{"source":"test"}`)

	plan := buildConversationMessagePlan(conversationMessagePlanInput{
		WorkspaceID:        " wsp-1 ",
		ScopeID:            " scp-1 ",
		MessageID:          " msg-1 ",
		Seq:                42,
		WorkspaceGlobalSeq: 420,
		AuthorMemberID:     " mem-1 ",
		AuthorType:         domain.MemberTypeHuman,
		ClientMessageID:    " client-1 ",
		MessageType:        " text ",
		Content:            "hello",
		Metadata:           metadata,
		CreatedAt:          createdAt,
	})
	metadata[0] = '['

	write := plan.MessageWrite
	require.Equal(t, "msg-1", write.ID)
	require.Equal(t, "wsp-1", write.WorkspaceID)
	require.Equal(t, "scp-1", write.ScopeID)
	require.Equal(t, int64(42), write.Seq)
	require.Equal(t, int64(420), write.WorkspaceGlobalSeq)
	require.Equal(t, "mem-1", write.AuthorMemberID)
	require.Equal(t, string(domain.MemberTypeHuman), write.AuthorType)
	require.Equal(t, "client-1", write.ClientMessageID)
	require.Equal(t, "text", write.MessageType)
	require.Equal(t, "hello", write.Content)
	require.Equal(t, []byte(`{"source":"test"}`), write.Metadata)
	require.Equal(t, createdAt.UTC(), write.CreatedAt)
	require.Equal(t, createdAt.UTC(), write.UpdatedAt)

	require.Empty(t, plan.SourceWrites)
	require.Empty(t, plan.MembershipMutations)
	require.Empty(t, plan.NotificationMutations)
	require.Empty(t, plan.TaskContextIngressRefs)
	require.False(t, plan.SidecarWrites.HasWrites())
	require.Empty(t, plan.Explain)
	require.Len(t, plan.OutboxEvents, 1)
	outbox := plan.OutboxEvents[0]
	require.True(t, strings.HasPrefix(outbox.ID, "out_"))
	require.Equal(t, "wsp-1", outbox.WorkspaceID)
	require.Equal(t, "scp-1", outbox.ScopeID)
	require.Equal(t, "msg-1", outbox.MessageID)
	require.Equal(t, "message", outbox.EventRefType)
	require.Equal(t, "msg-1", outbox.EventRefID)
	require.Equal(t, message.ScopeOutboxFanoutScope, outbox.FanoutType)
	require.Equal(t, "scope:scp-1:message:msg-1:fanout:"+message.ScopeOutboxFanoutScope, outbox.DedupeKey)
	require.Nil(t, outbox.Payload)
	require.Equal(t, "pending", outbox.Status)
	require.Equal(t, 3, outbox.MaxAttempts)
	require.Equal(t, createdAt.UTC(), outbox.CreatedAt)
	require.Equal(t, createdAt.UTC().Add(scopeFanoutInlineDeliveryGrace), outbox.NextAttemptAt)
}

func TestConversationWriteConstructionStaysBehindLocalBoundary(t *testing.T) {
	t.Parallel()

	sourceBytes, err := os.ReadFile("conversation_write.go")
	require.NoError(t, err)
	source := string(sourceBytes)
	writeBody := functionBodyForSourceTest(t, source, "func (store *Store) writeScopeMessageToConversationResult")
	planBody := functionBodyForSourceTest(t, source, "func buildConversationMessagePlan")

	require.Contains(t, writeBody, "buildConversationMessagePlan(conversationMessagePlanInput{")
	require.Contains(t, writeBody, "message.LoadScopeAgentRoutingPlan")
	require.Contains(t, writeBody, "message.FinalizeAgentRoutingPlan")
	require.Contains(t, writeBody, "AuthorMemberID:")
	require.Contains(t, writeBody, "authority.authorMemberID")
	require.Contains(t, writeBody, "AuthorType:")
	require.Contains(t, writeBody, "authority.authorType")
	require.Contains(t, writeBody, "NotifyPrincipalMemberID:")
	require.Contains(t, writeBody, "IdempotencySubjectMemberID:")
	require.NotContains(t, writeBody, "message.MessagePlan{")
	require.NotContains(t, source, "func loadAgentRoutingPlan")
	require.NotContains(t, source, "allowRecipientMessageReceive")
	require.NotContains(t, source, "scope_notification_settings")
	require.Contains(t, planBody, "message.MessagePlan{")
	require.Contains(t, planBody, "SideEffectProfile: message.MessagePlanSideEffectProfileScopeFanout")
	require.Contains(t, planBody, "message.BuildScopeMessageFanoutOutboxRecord(message.ScopeMessageFanoutOutboxRecordParams{")

	applyIndex := strings.Index(writeBody, "message.ApplyMessagePlanTxWithSidecarWriter")
	attachmentsIndex := strings.Index(writeBody, "attachments := applied.Attachments")
	receiveIndex := strings.Index(writeBody, "receiveMessage := message.ReceiveMessage{")
	require.NotEqual(t, -1, applyIndex)
	require.NotEqual(t, -1, attachmentsIndex)
	require.NotEqual(t, -1, receiveIndex)
	require.Contains(t, planBody, "SidecarWrites.AttachmentClaims")
	require.NotContains(t, source, "claimConversationMessageAttachments")
	require.Less(t, applyIndex, attachmentsIndex, "attachments must be claimed by the message-owned plan applier")
	require.Less(t, attachmentsIndex, receiveIndex, "receive payload must use the claimed attachment rows")
}

func functionBodyForSourceTest(t *testing.T, source string, signature string) string {
	t.Helper()
	start := strings.Index(source, signature)
	require.NotEqual(t, -1, start, "missing function %s", signature)
	brace := strings.Index(source[start:], "{")
	require.NotEqual(t, -1, brace, "missing function body for %s", signature)
	bodyStart := start + brace
	depth := 0
	for index := bodyStart; index < len(source); index++ {
		switch source[index] {
		case '{':
			depth++
		case '}':
			depth--
			if depth == 0 {
				return source[bodyStart : index+1]
			}
		}
	}
	t.Fatalf("unterminated function body for %s", signature)
	return ""
}
