package message

import (
	"context"
	"os"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/coredb/testkit"
)

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

	serviceBytes, err := os.ReadFile("service.go")
	require.NoError(t, err)
	projectionBytes, err := os.ReadFile("scope_runtime_projection.go")
	require.NoError(t, err)

	source := strings.Join([]string{
		sourceFromFunctionForMessageTest(t, string(serviceBytes), "func createReplyThread"),
		sourceFromFunctionForMessageTest(t, string(projectionBytes), "func ensureReplyThreadScopeProjectionForParent"),
	}, "\n")
	require.NotContains(t, source, legacySQLTableRefForMessageTest("INSERT INTO", "channels"))
	require.NotContains(t, source, legacySQLTableRefForMessageTest("INSERT INTO", "threads"))
	require.NotContains(t, source, legacyTableNameForMessageTest("reply", "threads"))
	require.NotContains(t, source, legacyTableNameForMessageTest("reply", "thread", "stats"))
	require.Contains(t, source, "UpsertScope")
	require.Contains(t, source, "LoadActiveScopeByIdentityKey")
	require.Contains(t, source, "UpsertChannelProfile")
}

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

	ctx := context.Background()
	pool := startMessagingTestPool(t, ctx)
	now := time.Date(2026, 5, 20, 11, 0, 0, 0, time.UTC)

	const (
		workspaceID     = "wsp_reply_thread_identity"
		userID          = "usr_reply_thread_identity"
		memberID        = "mem_reply_thread_identity"
		parentChannelID = "chn_reply_thread_identity_parent"
		parentMessageID = "msg_reply_thread_identity_parent"
		shortID         = "abcd1234"
	)

	repos := seedReplyThreadParentScopeForTest(t, ctx, pool, workspaceID, userID, memberID, parentChannelID, now)

	first, err := createReplyThread(ctx, pool, workspaceID, parentChannelID, parentMessageID, shortID)
	require.NoError(t, err)
	second, err := createReplyThread(ctx, pool, workspaceID, parentChannelID, parentMessageID, shortID)
	require.NoError(t, err)

	require.NotEmpty(t, first.ThreadChannelID)
	require.Equal(t, first.ThreadChannelID, second.ThreadChannelID)
	require.Equal(t, first.ThreadID, second.ThreadID)

	scope, err := repos.Scopes.LoadScope(ctx, second.ThreadChannelID)
	require.NoError(t, err)
	require.Equal(t, first.ThreadChannelID, scope.ID)
	require.Equal(t, parentChannelID, scope.ParentScopeID)
	require.Equal(t, parentMessageID, scope.RootMessageID)
	require.Equal(t, shortID, scope.RootMessageShortID)
	require.Equal(t, "thread:"+parentChannelID+":"+parentMessageID, scope.IdentityKey)
}

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

	ctx := context.Background()
	rootDSN := messagingTiDB.RootDSN(t)
	dsn := testkit.StartInitializedTiDBWithRootDSN(t, rootDSN)
	sqlDB, err := testkit.OpenTiDBContext(ctx, dsn)
	require.NoError(t, err)
	t.Cleanup(func() { _ = sqlDB.Close() })
	sqlDB.SetMaxOpenConns(4)
	sqlDB.SetMaxIdleConns(4)
	pool := coredb.WrapSQLDB(sqlDB)

	now := time.Date(2026, 5, 20, 11, 30, 0, 0, time.UTC)

	const (
		workspaceID     = "wsp_reply_thread_concurrent"
		userID          = "usr_reply_thread_concurrent"
		memberID        = "mem_reply_thread_concurrent"
		parentChannelID = "chn_reply_thread_concurrent_parent"
		parentMessageID = "msg_reply_thread_concurrent_parent"
		shortID         = "dcba4321"
	)

	seedReplyThreadParentScopeForTest(t, ctx, pool, workspaceID, userID, memberID, parentChannelID, now)

	start := make(chan struct{})
	var wg sync.WaitGroup
	lookups := make([]replyThreadLookup, 2)
	errs := make([]error, 2)
	for i := range lookups {
		wg.Add(1)
		go func(index int) {
			defer wg.Done()
			<-start
			lookups[index], errs[index] = createReplyThread(ctx, pool, workspaceID, parentChannelID, parentMessageID, shortID)
		}(i)
	}

	close(start)
	wg.Wait()

	require.NoError(t, errs[0])
	require.NoError(t, errs[1])
	require.NotEmpty(t, lookups[0].ThreadChannelID)
	require.Equal(t, lookups[0].ThreadChannelID, lookups[1].ThreadChannelID)
	require.Equal(t, lookups[0].ThreadID, lookups[1].ThreadID)
}

func seedReplyThreadParentScopeForTest(t *testing.T, ctx context.Context, pool coredb.QueryExecer, workspaceID string, userID string, memberID string, parentChannelID string, now time.Time) ScopeRepositories {
	t.Helper()

	insertMessagingWorkspace(t, ctx, pool, workspaceID, now)
	insertMessagingUser(t, ctx, pool, userID, workspaceID+"@example.com", now)
	insertMessagingHumanMember(t, ctx, pool, memberID, workspaceID, userID, now)
	repos := NewScopeRepositories(pool)
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                parentChannelID,
		WorkspaceID:       workspaceID,
		Type:              "channel",
		IdentityKey:       "channel:" + parentChannelID,
		Status:            "active",
		CreatedByMemberID: memberID,
		CreatedAt:         now,
		UpdatedAt:         now,
	}))
	return repos
}
