package app

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{})
	now := time.Date(2026, 6, 30, 10, 0, 0, 0, time.UTC)
	fixture := seedAppAdminFixture(t, pool, "channel_attachments", now)
	peer := seedWorkspaceMemberFixture(t, pool, fixture.WorkspaceID, "channel_attachments_peer", now)
	seedChannelParticipantProjectionForTests(t, pool, fixture.WorkspaceID, fixture.AllChannelID, "all", "", "public", fixture.MemberID, peer.MemberID, now)
	user := model.User{ID: fixture.UserID, Name: "channel_attachments", DisplayName: "channel_attachments"}
	peerUser := model.User{ID: peer.UserID, Name: "channel_attachments_peer", DisplayName: "channel_attachments_peer"}

	channelMessage, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, fixture.AllChannelID, "channel file", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_channel_attachments_file', $1, $2, $3, $4, 'notes.txt', 'text/plain', 4, $5, $6)
`, fixture.WorkspaceID, fixture.AllChannelID, fixture.MemberID, channelMessage.ID, []byte("note"), now)
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, channelMessage.ID))

	peerMessage, _, err := store.SendMessageForUser(ctx, peerUser, fixture.WorkspaceID, fixture.AllChannelID, "peer file", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_channel_attachments_peer_file', $1, $2, $3, $4, 'peer.txt', 'text/plain', 4, $5, $6)
`, fixture.WorkspaceID, fixture.AllChannelID, peer.MemberID, peerMessage.ID, []byte("peer"), now.Add(500*time.Millisecond))
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, peerMessage.ID))

	parent, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, fixture.AllChannelID, "thread parent", nil)
	require.NoError(t, err)
	replyThread, err := message.OpenReplyThreadForMember(ctx, pool, fixture.MemberID, fixture.WorkspaceID, fixture.AllChannelID, parent.ID)
	require.NoError(t, err)
	threadMessage, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, replyThread.ThreadChannelID, "thread image", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_channel_attachments_image', $1, $2, $3, $4, 'shot.png', 'image/png', 7, $5, $6)
`, fixture.WorkspaceID, replyThread.ThreadChannelID, fixture.MemberID, threadMessage.ID, []byte("pngdata"), now.Add(time.Second))
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, threadMessage.ID))

	page, err := store.ListChannelAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, fixture.AllChannelID, message.ChannelAttachmentListOptions{IncludeThreads: true, Limit: 10})
	require.NoError(t, err)
	require.False(t, page.HasMore)
	require.Len(t, page.Items, 3)

	byID := map[string]message.ChannelAttachmentItem{}
	for _, item := range page.Items {
		byID[item.ID] = item
	}
	require.Equal(t, message.ChannelAttachmentKindFile, byID["att_channel_attachments_file"].Kind)
	require.Equal(t, message.ChannelAttachmentKindFile, byID["att_channel_attachments_peer_file"].Kind)
	require.Equal(t, peer.MemberID, byID["att_channel_attachments_peer_file"].Author.MemberID)
	require.Equal(t, message.ChannelAttachmentKindImage, byID["att_channel_attachments_image"].Kind)
	require.Equal(t, "thread", byID["att_channel_attachments_image"].Message.ScopeType)
	require.Equal(t, replyThread.ThreadChannelID, byID["att_channel_attachments_image"].Message.ThreadChannelID)
	require.Equal(t, parent.ID, byID["att_channel_attachments_image"].Message.ThreadRootMessageID)

	peerOnly, err := store.ListChannelAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, fixture.AllChannelID, message.ChannelAttachmentListOptions{FromMemberID: peer.MemberID, IncludeThreads: true, Limit: 10})
	require.NoError(t, err)
	require.Len(t, peerOnly.Items, 1)
	require.Equal(t, "att_channel_attachments_peer_file", peerOnly.Items[0].ID)

	imagesOnly, err := store.ListChannelAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, fixture.AllChannelID, message.ChannelAttachmentListOptions{Kind: message.ChannelAttachmentKindImage, IncludeThreads: true, Limit: 10})
	require.NoError(t, err)
	require.Len(t, imagesOnly.Items, 1)
	require.Equal(t, message.ChannelAttachmentKindImage, imagesOnly.Items[0].Kind)

	withoutThreads, err := store.ListChannelAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, fixture.AllChannelID, message.ChannelAttachmentListOptions{IncludeThreads: false, Limit: 10})
	require.NoError(t, err)
	require.Len(t, withoutThreads.Items, 2)
	for _, item := range withoutThreads.Items {
		require.Equal(t, "channel", item.Message.ScopeType)
	}
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{})
	now := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)
	fixture := seedAppAdminFixture(t, pool, "thread_attachments", now)
	user := model.User{ID: fixture.UserID, Name: "thread_attachments", DisplayName: "thread_attachments"}

	channelMessage, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, fixture.AllChannelID, "channel file", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_thread_attachments_channel', $1, $2, $3, $4, 'channel.txt', 'text/plain', 4, $5, $6)
`, fixture.WorkspaceID, fixture.AllChannelID, fixture.MemberID, channelMessage.ID, []byte("ch"), now)
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, channelMessage.ID))

	parent, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, fixture.AllChannelID, "thread parent", nil)
	require.NoError(t, err)
	replyThread, err := message.OpenReplyThreadForMember(ctx, pool, fixture.MemberID, fixture.WorkspaceID, fixture.AllChannelID, parent.ID)
	require.NoError(t, err)

	threadMessage, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, replyThread.ThreadChannelID, "thread file", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_thread_attachments_one', $1, $2, $3, $4, 'one.txt', 'text/plain', 1, $5, $6)
`, fixture.WorkspaceID, replyThread.ThreadChannelID, fixture.MemberID, threadMessage.ID, []byte("1"), now.Add(time.Second))
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, threadMessage.ID))

	siblingParent, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, fixture.AllChannelID, "sibling thread parent", nil)
	require.NoError(t, err)
	siblingThread, err := message.OpenReplyThreadForMember(ctx, pool, fixture.MemberID, fixture.WorkspaceID, fixture.AllChannelID, siblingParent.ID)
	require.NoError(t, err)
	siblingMessage, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, siblingThread.ThreadChannelID, "sibling thread file", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_thread_attachments_sibling', $1, $2, $3, $4, 'sibling.txt', 'text/plain', 1, $5, $6)
`, fixture.WorkspaceID, siblingThread.ThreadChannelID, fixture.MemberID, siblingMessage.ID, []byte("2"), now.Add(2*time.Second))
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, siblingMessage.ID))

	page, err := store.ListThreadAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, replyThread.ThreadChannelID, message.ChannelAttachmentListOptions{Limit: 10})
	require.NoError(t, err)
	require.False(t, page.HasMore)
	require.Len(t, page.Items, 1)
	require.Equal(t, "att_thread_attachments_one", page.Items[0].ID)
	require.Equal(t, "thread", page.Items[0].Message.ScopeType)
	require.Equal(t, replyThread.ThreadChannelID, page.Items[0].Message.ThreadChannelID)
	require.Equal(t, parent.ID, page.Items[0].Message.ThreadRootMessageID)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{})
	now := time.Date(2026, 6, 30, 11, 0, 0, 0, time.UTC)
	fixture := seedAppAdminFixture(t, pool, "channel_attachments_cursor", now)
	user := model.User{ID: fixture.UserID, Name: "channel_attachments_cursor", DisplayName: "channel_attachments_cursor"}

	msg, _, err := store.SendMessageForUser(ctx, user, fixture.WorkspaceID, fixture.AllChannelID, "two attachments in one message", nil)
	require.NoError(t, err)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_channel_attachments_one', $1, $2, $3, $4, 'one.txt', 'text/plain', 1, $5, $6)
`, fixture.WorkspaceID, fixture.AllChannelID, fixture.MemberID, msg.ID, []byte("1"), now)
	mustExec(t, pool, `
INSERT INTO attachments (id, workspace_id, scope_id, uploaded_by_member_id, message_id, filename, content_type, size_bytes, payload, created_at)
VALUES ('att_channel_attachments_two', $1, $2, $3, $4, 'two.txt', 'text/plain', 1, $5, $6)
`, fixture.WorkspaceID, fixture.AllChannelID, fixture.MemberID, msg.ID, []byte("2"), now)
	require.NoError(t, message.ReconcileChannelAttachmentProjectionForMessage(ctx, pool, msg.ID))

	first, err := store.ListChannelAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, fixture.AllChannelID, message.ChannelAttachmentListOptions{IncludeThreads: true, Limit: 1})
	require.NoError(t, err)
	require.True(t, first.HasMore)
	require.Len(t, first.Items, 1)

	second, err := store.ListChannelAttachmentsForUser(ctx, fixture.UserID, fixture.WorkspaceID, fixture.AllChannelID, message.ChannelAttachmentListOptions{IncludeThreads: true, Limit: 10, Cursor: first.NextCursor})
	require.NoError(t, err)
	require.Len(t, second.Items, 1)
	require.NotEqual(t, first.Items[0].ID, second.Items[0].ID)
}
