package rewind

import (
	"context"
	"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/testfixture"
)

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

	ctx := context.Background()
	db := startRewindRollbackDB(t, ctx)
	now := time.Date(2026, 5, 14, 6, 50, 0, 0, time.UTC)

	const (
		workspaceID        = "wsp_rewind_scope_notifications"
		preExistingUserID  = "usr_rewind_pre_existing"
		extraUserID        = "usr_rewind_extra_subscription"
		preExistingMember  = "mem_rewind_pre_existing"
		suffixMember       = "mem_rewind_suffix_agent_subscription"
		extraMember        = "mem_rewind_extra_subscription"
		replyThreadScopeID = "thr_rewind_scope_notifications"
		suffixAgentID      = "agt_rewind_suffix_subscription"
		suffixMachineID    = "mach_rewind_suffix_subscription"
	)
	seedRewindNotificationMember(t, ctx, db, workspaceID, preExistingUserID, preExistingMember, now)
	seedRewindNotificationMember(t, ctx, db, workspaceID, extraUserID, extraMember, now)
	seedRewindAgentMember(t, ctx, db, workspaceID, preExistingUserID, suffixAgentID, suffixMachineID, suffixMember, now)
	mustExecRecovery(t, db, `
INSERT INTO scopes (
    id, workspace_id, type, identity_key, latest_seq, latest_global_seq,
    latest_message_at, archived_until_seq, status, created_at, updated_at
)
VALUES ($1, $2, 'thread', CONCAT('thread:', $1), 0, 0, NULL, 0, 'active', $3, $3)
`, replyThreadScopeID, workspaceID, now)
	mustExecRecovery(t, db, `
INSERT INTO scope_messages (
    id, workspace_id, scope_id, seq, workspace_global_seq, author_member_id,
    author_type, message_type, content, created_at, updated_at
)
VALUES ('msg_rewind_scope_notification_remaining', $1, $2, 4, 4, $3, 'human', 'text', 'remaining', $4, $4)
`, workspaceID, replyThreadScopeID, preExistingMember, now)
	mustExecRecovery(t, db, `
INSERT INTO scope_memberships (
    scope_id, member_id, visibility, owner_member_id, last_read_seq,
    last_read_global_seq, last_activity_global_seq, owner_context_dismissed_through_global_seq,
    effective_from_seq, effective_to_seq, created_at, updated_at, removed_at, archived_at
)
VALUES
    ($1, $2, 'public', NULL, 0, 0, 0, 0, 0, NULL, $5, $5, NULL, NULL),
    ($1, $3, 'public', NULL, 0, 0, 0, 0, 7, NULL, $5, $5, NULL, NULL),
    ($1, $4, 'public', NULL, 0, 0, 0, 0, 0, NULL, $5, $5, NULL, NULL)
`, replyThreadScopeID, preExistingMember, suffixMember, extraMember, now)
	mustExecRecovery(t, db, `
INSERT INTO scope_notification_settings (
    scope_id, member_id, state, created_at, updated_at
)
VALUES
    ($1, $2, 'active', $4, $4),
    ($1, $3, 'auto-wake', $4, $4)
`, replyThreadScopeID, preExistingMember, suffixMember, now)

	store := New(db, Options{
		ReplaceReplyThreadParticipants: message.ReplaceReplyThreadParticipants,
	})
	tx, err := db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	err = store.recomputeReplyThreadState(ctx, tx, Plan{
		WorkspaceID:        workspaceID,
		ScopeID:            replyThreadScopeID,
		ReplyThreadScopeID: replyThreadScopeID,
		TargetSeq:          5,
	})
	if err != nil {
		_ = tx.Rollback(ctx)
		require.NoError(t, err)
	}
	require.NoError(t, tx.Commit(ctx))

	var preExistingOpenCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_notification_settings
WHERE scope_id = $1
  AND member_id = $2
`, replyThreadScopeID, preExistingMember).Scan(&preExistingOpenCount))
	require.Equal(t, int64(1), preExistingOpenCount)

	var preExistingMembershipOpenCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND effective_from_seq = 0
  AND effective_to_seq IS NULL
  AND removed_at IS NULL
  AND archived_at IS NULL
`, replyThreadScopeID, preExistingMember).Scan(&preExistingMembershipOpenCount))
	require.Equal(t, int64(1), preExistingMembershipOpenCount)

	var suffixCurrentCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_notification_settings
WHERE scope_id = $1
  AND member_id = $2
  AND state = 'auto-wake'
`, replyThreadScopeID, suffixMember).Scan(&suffixCurrentCount))
	require.Equal(t, int64(1), suffixCurrentCount)

	var suffixMembershipClosedCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND effective_from_seq = 7
  AND effective_to_seq = 7
`, replyThreadScopeID, suffixMember).Scan(&suffixMembershipClosedCount))
	require.Equal(t, int64(1), suffixMembershipClosedCount)

	var suffixOpenMembershipCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND effective_to_seq IS NULL
  AND removed_at IS NULL
  AND archived_at IS NULL
`, replyThreadScopeID, suffixMember).Scan(&suffixOpenMembershipCount))
	require.Zero(t, suffixOpenMembershipCount)

	var suffixSeqZeroMembershipCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND effective_from_seq = 0
`, replyThreadScopeID, suffixMember).Scan(&suffixSeqZeroMembershipCount))
	require.Zero(t, suffixSeqZeroMembershipCount)

	var extraOpenMembershipCount int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND effective_to_seq IS NULL
  AND removed_at IS NULL
  AND archived_at IS NULL
`, replyThreadScopeID, extraMember).Scan(&extraOpenMembershipCount))
	require.Zero(t, extraOpenMembershipCount)
}

func seedRewindNotificationMember(t *testing.T, ctx context.Context, db coredb.QueryExecer, workspaceID string, userID string, memberID string, now time.Time) {
	t.Helper()

	mustExecRecovery(t, db, `
INSERT IGNORE INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, CONCAT($1, '@example.com'), $1, $1, '', FALSE, 'hash', $2, $2, $2)
`, userID, now)
	mustExecRecovery(t, db, `
INSERT IGNORE INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, $1, $1, 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, db, workspaceID, now), now)
	mustExecRecovery(t, db, `
INSERT IGNORE INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', $1, $3, 'member', $4, $4)
`, memberID, workspaceID, userID, now)
}

func seedRewindAgentMember(t *testing.T, ctx context.Context, db coredb.QueryExecer, workspaceID string, createdByUserID string, agentID string, machineID string, memberID string, now time.Time) {
	t.Helper()

	mustExecRecovery(t, db, `
INSERT IGNORE INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, $1, $1, 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, db, workspaceID, now), now)
	mustExecRecovery(t, db, `
INSERT IGNORE INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', $1, $3, $3)
`, memberID, workspaceID, now)
	mustExecRecovery(t, db, `
INSERT IGNORE INTO machines (
    id, workspace_id, created_by_user_id, name, api_key, hostname, os,
    daemon_version, connected_at, disconnected_at, created_at, updated_at
)
VALUES ($1, $2, $3, $1, CONCAT($1, '_key'), '', '', '', NULL, NULL, $4, $4)
`, machineID, workspaceID, createdByUserID, now)
	mustExecRecovery(t, db, `
INSERT IGNORE INTO agents (
    id, workspace_id, machine_id, member_id, name, runtime, model,
    reasoning_effort, description, status, activity, activity_detail,
    created_at, updated_at
)
VALUES ($1, $2, $3, $4, $1, 'codex', 'test-model', '', '', 'active', 'idle', '', $5, $5)
`, agentID, workspaceID, machineID, memberID, now)
}
