package test

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"

	apiv1 "github.com/usememos/memos/proto/gen/api/v1"
	v1 "github.com/usememos/memos/server/router/api/v1" //nolint:revive
	"github.com/usememos/memos/store"
)

// TestListActivitiesWithDeletedMemos verifies that ListActivities gracefully handles
// activities that reference deleted memos instead of crashing the entire request.
func TestListActivitiesWithDeletedMemos(t *testing.T) {
	ctx := context.Background()
	ts := NewTestService(t)
	defer ts.Cleanup()

	// Create two users - one to create memo, one to comment
	userOne, err := ts.CreateRegularUser(ctx, "test-user-1")
	require.NoError(t, err)
	userOneCtx := ts.CreateUserContext(ctx, userOne.ID)

	userTwo, err := ts.CreateRegularUser(ctx, "test-user-2")
	require.NoError(t, err)
	userTwoCtx := ts.CreateUserContext(ctx, userTwo.ID)

	// Create a memo by userOne
	memo1, err := ts.Service.CreateMemo(userOneCtx, &apiv1.CreateMemoRequest{
		Memo: &apiv1.Memo{
			Content:    "Original memo",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, memo1)

	// Create a comment on the memo by userTwo (this will create an activity for userOne)
	comment, err := ts.Service.CreateMemoComment(userTwoCtx, &apiv1.CreateMemoCommentRequest{
		Name: memo1.Name,
		Comment: &apiv1.Memo{
			Content:    "This is a comment",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, comment)

	// Verify activity was created for the comment (check from userOne's perspective - they receive the notification)
	activities, err := ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{})
	require.NoError(t, err)
	initialActivityCount := len(activities.Activities)
	require.Greater(t, initialActivityCount, 0, "Should have at least one activity")

	// Delete the original memo (this deletes the comment too)
	_, err = ts.Service.DeleteMemo(userOneCtx, &apiv1.DeleteMemoRequest{
		Name: memo1.Name,
	})
	require.NoError(t, err)

	// List activities again - should succeed even though the memo is deleted
	activities, err = ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{})
	require.NoError(t, err)
	// Activities list should be empty or not contain the deleted memo activity
	for _, activity := range activities.Activities {
		if activity.Payload != nil && activity.Payload.GetMemoComment() != nil {
			require.NotEqual(t, memo1.Name, activity.Payload.GetMemoComment().Memo,
				"Activity should not reference deleted memo")
		}
	}
	// After deletion, there should be fewer activities
	require.LessOrEqual(t, len(activities.Activities), initialActivityCount-1,
		"Should have filtered out the activity for the deleted memo")
}

// TestGetActivityWithDeletedMemo verifies that GetActivity returns a proper error
// when trying to fetch an activity that references a deleted memo.
func TestGetActivityWithDeletedMemo(t *testing.T) {
	ctx := context.Background()
	ts := NewTestService(t)
	defer ts.Cleanup()

	// Create two users
	userOne, err := ts.CreateRegularUser(ctx, "test-user-1")
	require.NoError(t, err)
	userOneCtx := ts.CreateUserContext(ctx, userOne.ID)

	userTwo, err := ts.CreateRegularUser(ctx, "test-user-2")
	require.NoError(t, err)
	userTwoCtx := ts.CreateUserContext(ctx, userTwo.ID)

	// Create a memo by userOne
	memo1, err := ts.Service.CreateMemo(userOneCtx, &apiv1.CreateMemoRequest{
		Memo: &apiv1.Memo{
			Content:    "Original memo",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, memo1)

	// Create a comment to trigger activity creation by userTwo
	comment, err := ts.Service.CreateMemoComment(userTwoCtx, &apiv1.CreateMemoCommentRequest{
		Name: memo1.Name,
		Comment: &apiv1.Memo{
			Content:    "Comment",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, comment)

	// Get the activity ID by listing activities from userOne's perspective
	activities, err := ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{})
	require.NoError(t, err)
	require.Greater(t, len(activities.Activities), 0)

	activityName := activities.Activities[0].Name

	// Delete the memo
	_, err = ts.Service.DeleteMemo(userOneCtx, &apiv1.DeleteMemoRequest{
		Name: memo1.Name,
	})
	require.NoError(t, err)

	// Try to get the specific activity - should return NotFound error
	_, err = ts.Service.GetActivity(userOneCtx, &apiv1.GetActivityRequest{
		Name: activityName,
	})
	require.Error(t, err)
	require.Contains(t, err.Error(), "activity references deleted content")
}

// TestActivitiesWithPartiallyDeletedMemos verifies that when some memos are deleted,
// other valid activities are still returned.
func TestActivitiesWithPartiallyDeletedMemos(t *testing.T) {
	ctx := context.Background()
	ts := NewTestService(t)
	defer ts.Cleanup()

	// Create two users
	userOne, err := ts.CreateRegularUser(ctx, "test-user-1")
	require.NoError(t, err)
	userOneCtx := ts.CreateUserContext(ctx, userOne.ID)

	userTwo, err := ts.CreateRegularUser(ctx, "test-user-2")
	require.NoError(t, err)
	userTwoCtx := ts.CreateUserContext(ctx, userTwo.ID)

	// Create two memos by userOne
	memo1, err := ts.Service.CreateMemo(userOneCtx, &apiv1.CreateMemoRequest{
		Memo: &apiv1.Memo{
			Content:    "First memo",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)

	memo2, err := ts.Service.CreateMemo(userOneCtx, &apiv1.CreateMemoRequest{
		Memo: &apiv1.Memo{
			Content:    "Second memo",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)

	// Create comments on both by userTwo (creates activities for userOne)
	_, err = ts.Service.CreateMemoComment(userTwoCtx, &apiv1.CreateMemoCommentRequest{
		Name: memo1.Name,
		Comment: &apiv1.Memo{
			Content:    "Comment on first",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)

	_, err = ts.Service.CreateMemoComment(userTwoCtx, &apiv1.CreateMemoCommentRequest{
		Name: memo2.Name,
		Comment: &apiv1.Memo{
			Content:    "Comment on second",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)

	// Should have 2 activities from userOne's perspective
	activities, err := ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{})
	require.NoError(t, err)
	require.Equal(t, 2, len(activities.Activities))

	// Delete first memo
	_, err = ts.Service.DeleteMemo(userOneCtx, &apiv1.DeleteMemoRequest{
		Name: memo1.Name,
	})
	require.NoError(t, err)

	// List activities - should still work and return only the second memo's activity
	activities, err = ts.Service.ListActivities(userOneCtx, &apiv1.ListActivitiesRequest{})
	require.NoError(t, err)
	require.Equal(t, 1, len(activities.Activities), "Should have 1 activity remaining")

	// Verify the remaining activity relates to a valid memo
	require.NotNil(t, activities.Activities[0].Payload.GetMemoComment())
	require.Contains(t, activities.Activities[0].Payload.GetMemoComment().RelatedMemo, "memos/")
}

// TestActivityStoreDirectDeletion tests the scenario where a memo is deleted directly
// from the store (simulating database-level deletion or migration).
func TestActivityStoreDirectDeletion(t *testing.T) {
	ctx := context.Background()
	ts := NewTestService(t)
	defer ts.Cleanup()

	user, err := ts.CreateRegularUser(ctx, "test-user")
	require.NoError(t, err)
	userCtx := ts.CreateUserContext(ctx, user.ID)

	// Create a memo
	memo1, err := ts.Service.CreateMemo(userCtx, &apiv1.CreateMemoRequest{
		Memo: &apiv1.Memo{
			Content:    "Test memo",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)

	// Create a comment
	comment, err := ts.Service.CreateMemoComment(userCtx, &apiv1.CreateMemoCommentRequest{
		Name: memo1.Name,
		Comment: &apiv1.Memo{
			Content:    "Test comment",
			Visibility: apiv1.Visibility_PUBLIC,
		},
	})
	require.NoError(t, err)

	// Extract memo UID from the comment name
	commentMemoUID, err := v1.ExtractMemoUIDFromName(comment.Name)
	require.NoError(t, err)

	commentMemo, err := ts.Store.GetMemo(ctx, &store.FindMemo{
		UID: &commentMemoUID,
	})
	require.NoError(t, err)
	require.NotNil(t, commentMemo)

	// Delete the comment memo directly from store (simulating orphaned activity)
	err = ts.Store.DeleteMemo(ctx, &store.DeleteMemo{ID: commentMemo.ID})
	require.NoError(t, err)

	// List activities should still succeed even with orphaned activity
	activities, err := ts.Service.ListActivities(userCtx, &apiv1.ListActivitiesRequest{})
	require.NoError(t, err)
	// Activities should be empty or not include the orphaned one
	for _, activity := range activities.Activities {
		if activity.Payload != nil && activity.Payload.GetMemoComment() != nil {
			require.NotEqual(t, comment.Name, activity.Payload.GetMemoComment().Memo,
				"Should not return activity with deleted memo")
		}
	}
}
