package app

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

	"github.com/stretchr/testify/require"

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

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{})
	now := time.Date(2026, 5, 29, 3, 10, 0, 0, time.UTC)

	owner := seedAppAdminFixture(t, pool, "thread_share_owner", now)
	viewer := seedWorkspaceMemberFixture(t, pool, owner.WorkspaceID, "thread_share_viewer", now)
	seedChannelParticipantProjectionForTests(t, pool, owner.WorkspaceID, owner.AllChannelID, "all", "", "public", owner.MemberID, viewer.MemberID, now)

	const privateSourceChannelID = "chn_thread_share_private_source"
	seedChannelParticipantProjectionWithDisplayNameForTests(t, pool, owner.WorkspaceID, privateSourceChannelID, "private-source", "Private Source", "hidden source", "private", owner.MemberID, owner.MemberID, now)
	ownerUser := model.User{
		ID:          owner.UserID,
		Name:        "thread_share_owner",
		DisplayName: "Thread Share Owner",
		AvatarURL:   "https://example.com/thread-share-owner.png",
	}

	threadID := openThreadWithReplies(t, ctx, pool, store, owner, ownerUser, privateSourceChannelID, "root topic visible to sender", []string{"reply included in immutable snapshot"})
	cardMessage, delivery, err := store.CreateThreadShareCardForUser(ctx, ownerUser, owner.WorkspaceID, threadID, owner.AllChannelID)
	require.NoError(t, err)
	require.Equal(t, owner.AllChannelID, cardMessage.ChannelID)
	require.Equal(t, message.MessageTypeThreadShareCard, cardMessage.MessageType)
	require.Equal(t, "https://example.com/thread-share-owner.png", cardMessage.AuthorProfile.AvatarURL)
	require.Equal(t, cardMessage.ID, delivery.MessageID)
	requireScopeMessageAndSingleFanout(t, pool, delivery.MessageID, owner.WorkspaceID, owner.AllChannelID)
	threadShareMetadata, ok := cardMessage.Metadata["threadShare"].(map[string]any)
	require.True(t, ok)
	require.NotContains(
		t,
		threadShareMetadata,
		"sourceParentMessageId",
		"full parent message id should stay in permission-gated sidecar details",
	)

	viewerCard, err := store.LoadThreadShareCardForUser(ctx, viewer.UserID, owner.WorkspaceID, cardMessage.ID)
	require.NoError(t, err)
	require.False(t, viewerCard.CanOpenSource, "target reader should not gain source-thread access")
	require.Empty(t, viewerCard.SourceParentMessageID)
	require.Len(t, viewerCard.Messages, 2)
	require.Equal(t, "root topic visible to sender", viewerCard.Messages[0].Content)
	require.Equal(t, "reply included in immutable snapshot", viewerCard.Messages[1].Content)

	ownerCard, err := store.LoadThreadShareCardForUser(ctx, owner.UserID, owner.WorkspaceID, cardMessage.ID)
	require.NoError(t, err)
	require.True(t, ownerCard.CanOpenSource)
	require.NotEmpty(t, ownerCard.SourceParentMessageID)

	cardCount := countThreadShareCards(t, ctx, pool, owner.WorkspaceID)
	replies := make([]string, threadShareSnapshotMaxMessages)
	for i := range replies {
		replies[i] = fmt.Sprintf("reply %03d", i+1)
	}
	tooManyThreadID := openThreadWithReplies(t, ctx, pool, store, owner, ownerUser, privateSourceChannelID, "too many root", replies)
	_, _, err = store.CreateThreadShareCardForUser(ctx, ownerUser, owner.WorkspaceID, tooManyThreadID, owner.AllChannelID)
	require.ErrorContains(t, err, "thread has too many messages to share (max 200)")
	require.Equal(t, cardCount, countThreadShareCards(t, ctx, pool, owner.WorkspaceID), "over-message-limit share should not append a card")

	hugeReplies := make([]string, 17)
	for i := range hugeReplies {
		hugeReplies[i] = strings.Repeat("x", 32*1024)
	}
	hugeThreadID := openThreadWithReplies(t, ctx, pool, store, owner, ownerUser, privateSourceChannelID, "huge root", hugeReplies)
	_, _, err = store.CreateThreadShareCardForUser(ctx, ownerUser, owner.WorkspaceID, hugeThreadID, owner.AllChannelID)
	require.ErrorContains(t, err, "thread share snapshot is too large")
	require.Equal(t, cardCount, countThreadShareCards(t, ctx, pool, owner.WorkspaceID), "over-byte-limit share should not append a card")
}

func openThreadWithReplies(t *testing.T, ctx context.Context, pool coredb.DB, store *Store, owner appAdminFixture, ownerUser model.User, channelID string, rootContent string, replies []string) string {
	t.Helper()
	root, _, err := store.SendMessageForUser(ctx, ownerUser, owner.WorkspaceID, channelID, rootContent, nil)
	require.NoError(t, err)
	thread, err := message.OpenReplyThreadForMember(ctx, pool, owner.MemberID, owner.WorkspaceID, channelID, root.ID)
	require.NoError(t, err)
	for _, reply := range replies {
		_, _, err = store.SendMessageForUser(ctx, ownerUser, owner.WorkspaceID, thread.ThreadChannelID, reply, nil)
		require.NoError(t, err)
	}
	return thread.ThreadChannelID
}

func countThreadShareCards(t *testing.T, ctx context.Context, pool coredb.DB, workspaceID string) int {
	t.Helper()
	var count int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_messages
WHERE workspace_id = $1
  AND message_type = $2
`, workspaceID, message.MessageTypeThreadShareCard).Scan(&count))
	return count
}
