package message

import (
	"context"
	"testing"
	"time"

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

	"github.com/stretchr/testify/require"
)

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	repos := NewScopeRepositories(db)
	now := time.Now().UTC().Truncate(time.Second)
	workspaceID := "wsp_freshness_allocator"
	scopeID := "scp_freshness_allocator"
	memberID := "mem_freshness_allocator"

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingAgentMember(t, ctx, db, memberID, workspaceID, now)
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                scopeID,
		WorkspaceID:       workspaceID,
		Type:              "channel",
		IdentityKey:       "channel:freshness-allocator",
		LatestSeq:         2,
		LatestGlobalSeq:   2,
		Status:            "active",
		CreatedByMemberID: memberID,
		CreatedAt:         now,
		UpdatedAt:         now,
	}))

	tx, err := db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	allocation, err := AllocateNextScopeMessageSeqWithFreshness(ctx, tx, scopeID, 2)
	require.NoError(t, err)
	require.Equal(t, ScopeMessageSeqAllocation{State: SendStateSent, Seq: 3, LatestSeq: 3}, allocation)
	require.NoError(t, tx.Commit(ctx))

	tx, err = db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	allocation, err = AllocateNextScopeMessageSeqWithFreshness(ctx, tx, scopeID, 2)
	require.NoError(t, err)
	require.Equal(t, ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: 3}, allocation)
	require.NoError(t, tx.Rollback(ctx))
}

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	repos := NewScopeRepositories(db)
	now := time.Now().UTC().Truncate(time.Second)
	workspaceID := "wsp_visible_freshness_allocator"
	scopeID := "scp_visible_freshness_allocator"
	ownerMemberID := "mem_visible_freshness_owner"
	strangerMemberID := "mem_visible_freshness_stranger"
	privateAgentMemberID := "mem_visible_freshness_private_agent"

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingUser(t, ctx, db, "usr_visible_freshness_owner", "visible-freshness-owner@example.com", now)
	insertMessagingHumanMember(t, ctx, db, ownerMemberID, workspaceID, "usr_visible_freshness_owner", now)
	insertMessagingUser(t, ctx, db, "usr_visible_freshness_stranger", "visible-freshness-stranger@example.com", now)
	insertMessagingHumanMember(t, ctx, db, strangerMemberID, workspaceID, "usr_visible_freshness_stranger", now)
	insertMessagingAgentMember(t, ctx, db, privateAgentMemberID, workspaceID, now)
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                scopeID,
		WorkspaceID:       workspaceID,
		Type:              "channel",
		IdentityKey:       "channel:visible-freshness-allocator",
		LatestSeq:         2,
		LatestGlobalSeq:   2,
		Status:            "active",
		CreatedByMemberID: ownerMemberID,
		CreatedAt:         now,
		UpdatedAt:         now,
	}))
	for _, membership := range []ScopeMembershipRecord{
		{ScopeID: scopeID, MemberID: ownerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
		{ScopeID: scopeID, MemberID: strangerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
		{ScopeID: scopeID, MemberID: privateAgentMemberID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
	} {
		require.NoError(t, repos.Memberships.Upsert(ctx, membership))
	}
	for _, record := range []ScopeMessageRecord{
		{ID: "msg_visible_freshness_owner", WorkspaceID: workspaceID, ScopeID: scopeID, Seq: 1, WorkspaceGlobalSeq: 1, AuthorMemberID: ownerMemberID, AuthorType: ScopeActorHuman, MessageType: "text", Content: "owner visible", CreatedAt: now.Add(time.Second), UpdatedAt: now.Add(time.Second)},
		{ID: "msg_visible_freshness_hidden", WorkspaceID: workspaceID, ScopeID: scopeID, Seq: 2, WorkspaceGlobalSeq: 2, AuthorMemberID: strangerMemberID, AuthorType: ScopeActorHuman, MessageType: "text", Content: "stranger hidden", CreatedAt: now.Add(2 * time.Second), UpdatedAt: now.Add(2 * time.Second)},
	} {
		require.NoError(t, repos.Messages.Insert(ctx, record))
	}

	tx, err := db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	allocation, err := AllocateNextScopeMessageSeqWithVisibleFreshness(ctx, tx, ScopeMessageSeqVisibleFreshnessRequest{
		ScopeID:             scopeID,
		ViewerMemberID:      privateAgentMemberID,
		ViewerType:          ScopeActorPrivateAgent,
		SeenUpToSeq:         1,
		UseVisibleMissCheck: true,
	})
	require.NoError(t, err)
	require.Equal(t, ScopeMessageSeqAllocation{State: SendStateSent, Seq: 3, LatestSeq: 3}, allocation)
	require.NoError(t, tx.Commit(ctx))

	visibleNew := ScopeMessageRecord{ID: "msg_visible_freshness_new_owner", WorkspaceID: workspaceID, ScopeID: scopeID, Seq: 4, WorkspaceGlobalSeq: 4, AuthorMemberID: ownerMemberID, AuthorType: ScopeActorHuman, MessageType: "text", Content: "owner visible new", CreatedAt: now.Add(4 * time.Second), UpdatedAt: now.Add(4 * time.Second)}
	require.NoError(t, repos.Messages.Insert(ctx, visibleNew))
	require.NoError(t, repos.Scopes.AdvanceMessageStats(ctx, scopeID, visibleNew.Seq, visibleNew.WorkspaceGlobalSeq, visibleNew.CreatedAt, true, visibleNew.AuthorMemberID))

	tx, err = db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	allocation, err = AllocateNextScopeMessageSeqWithVisibleFreshness(ctx, tx, ScopeMessageSeqVisibleFreshnessRequest{
		ScopeID:             scopeID,
		ViewerMemberID:      privateAgentMemberID,
		ViewerType:          ScopeActorPrivateAgent,
		SeenUpToSeq:         3,
		UseVisibleMissCheck: true,
	})
	require.NoError(t, err)
	require.Equal(t, ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: 4}, allocation)
	require.NoError(t, tx.Rollback(ctx))
}

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	repos := NewScopeRepositories(db)
	now := time.Now().UTC().Truncate(time.Second)
	workspaceID := "wsp_visible_freshness_parent_private"
	parentScopeID := "chn_visible_freshness_parent_private"
	threadScopeID := "thr_visible_freshness_parent_private"
	ownerMemberID := "mem_visible_freshness_parent_owner"
	strangerMemberID := "mem_visible_freshness_parent_stranger"
	privateAgentMemberID := "mem_visible_freshness_parent_agent"

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingUser(t, ctx, db, "usr_visible_freshness_parent_owner", "visible-freshness-parent-owner@example.com", now)
	insertMessagingHumanMember(t, ctx, db, ownerMemberID, workspaceID, "usr_visible_freshness_parent_owner", now)
	insertMessagingUser(t, ctx, db, "usr_visible_freshness_parent_stranger", "visible-freshness-parent-stranger@example.com", now)
	insertMessagingHumanMember(t, ctx, db, strangerMemberID, workspaceID, "usr_visible_freshness_parent_stranger", now)
	insertMessagingAgentMember(t, ctx, db, privateAgentMemberID, workspaceID, now)
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                parentScopeID,
		WorkspaceID:       workspaceID,
		Type:              "channel",
		IdentityKey:       "channel:visible-freshness-parent-private",
		LatestSeq:         1,
		LatestGlobalSeq:   1,
		Status:            "active",
		CreatedByMemberID: ownerMemberID,
		CreatedAt:         now,
		UpdatedAt:         now,
	}))
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                 threadScopeID,
		WorkspaceID:        workspaceID,
		Type:               "thread",
		ParentScopeID:      parentScopeID,
		RootMessageID:      "msg_visible_freshness_parent_root",
		RootMessageShortID: "parent01",
		IdentityKey:        "thread:" + parentScopeID + ":msg_visible_freshness_parent_root",
		LatestSeq:          1,
		LatestGlobalSeq:    1,
		Status:             "active",
		CreatedByMemberID:  ownerMemberID,
		CreatedAt:          now,
		UpdatedAt:          now,
	}))
	for _, membership := range []ScopeMembershipRecord{
		{ScopeID: parentScopeID, MemberID: ownerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
		{ScopeID: parentScopeID, MemberID: strangerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
		{ScopeID: parentScopeID, MemberID: privateAgentMemberID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
		{ScopeID: threadScopeID, MemberID: ownerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
		{ScopeID: threadScopeID, MemberID: strangerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now},
	} {
		require.NoError(t, repos.Memberships.Upsert(ctx, membership))
	}
	for _, record := range []ScopeMessageRecord{
		{ID: "msg_visible_freshness_parent_root", WorkspaceID: workspaceID, ScopeID: parentScopeID, Seq: 1, WorkspaceGlobalSeq: 1, AuthorMemberID: ownerMemberID, AuthorType: ScopeActorHuman, MessageType: "text", Content: "thread root", CreatedAt: now, UpdatedAt: now},
		{ID: "msg_visible_freshness_parent_hidden", WorkspaceID: workspaceID, ScopeID: threadScopeID, Seq: 1, WorkspaceGlobalSeq: 1, AuthorMemberID: strangerMemberID, AuthorType: ScopeActorHuman, MessageType: "text", Content: "stranger hidden reply", CreatedAt: now.Add(time.Second), UpdatedAt: now.Add(time.Second)},
	} {
		require.NoError(t, repos.Messages.Insert(ctx, record))
	}

	tx, err := db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	allocation, err := AllocateNextScopeMessageSeqWithVisibleFreshness(ctx, tx, ScopeMessageSeqVisibleFreshnessRequest{
		ScopeID:             threadScopeID,
		ViewerMemberID:      privateAgentMemberID,
		ViewerType:          ScopeActorPrivateAgent,
		SeenUpToSeq:         0,
		UseVisibleMissCheck: true,
	})
	require.NoError(t, err)
	require.Equal(t, ScopeMessageSeqAllocation{State: SendStateSent, Seq: 2, LatestSeq: 2}, allocation)
	require.NoError(t, tx.Commit(ctx))

	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: threadScopeID, MemberID: privateAgentMemberID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	visibleNew := ScopeMessageRecord{ID: "msg_visible_freshness_parent_owner", WorkspaceID: workspaceID, ScopeID: threadScopeID, Seq: 3, WorkspaceGlobalSeq: 3, AuthorMemberID: ownerMemberID, AuthorType: ScopeActorHuman, MessageType: "text", Content: "owner visible reply", CreatedAt: now.Add(3 * time.Second), UpdatedAt: now.Add(3 * time.Second)}
	require.NoError(t, repos.Messages.Insert(ctx, visibleNew))
	require.NoError(t, repos.Scopes.AdvanceMessageStats(ctx, threadScopeID, visibleNew.Seq, visibleNew.WorkspaceGlobalSeq, visibleNew.CreatedAt, true, visibleNew.AuthorMemberID))

	tx, err = db.BeginTx(ctx, coredb.TxOptions{})
	require.NoError(t, err)
	allocation, err = AllocateNextScopeMessageSeqWithVisibleFreshness(ctx, tx, ScopeMessageSeqVisibleFreshnessRequest{
		ScopeID:             threadScopeID,
		ViewerMemberID:      privateAgentMemberID,
		ViewerType:          ScopeActorPrivateAgent,
		SeenUpToSeq:         2,
		UseVisibleMissCheck: true,
	})
	require.NoError(t, err)
	require.Equal(t, ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: 3}, allocation)
	require.NoError(t, tx.Rollback(ctx))
}
