package app

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

type recordingCommittedMessageFanout struct {
	query                func(context.Context, message.MessageDelivery)
	projectionOnlyQuery  func(context.Context, message.MessageDelivery)
	deliveries           []message.MessageDelivery
	projectionOnly       []message.MessageDelivery
	projectionMessages   []message.AppMessage
	projectionWorkspaces []string
}

func (fanout *recordingCommittedMessageFanout) DeliverCommittedMessage(
	ctx context.Context,
	_ string,
	_ message.AppMessage,
	delivery message.MessageDelivery,
) error {
	if fanout.query != nil {
		fanout.query(ctx, delivery)
	}
	fanout.deliveries = append(fanout.deliveries, delivery)
	return nil
}

func (fanout *recordingCommittedMessageFanout) EmitCommittedMessageProjectionOnly(
	ctx context.Context,
	workspaceID string,
	appMessage message.AppMessage,
	delivery message.MessageDelivery,
) {
	if fanout.projectionOnlyQuery != nil {
		fanout.projectionOnlyQuery(ctx, delivery)
	}
	fanout.projectionOnly = append(fanout.projectionOnly, delivery)
	fanout.projectionMessages = append(fanout.projectionMessages, appMessage)
	fanout.projectionWorkspaces = append(fanout.projectionWorkspaces, workspaceID)
}

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
	now := time.Now().UTC()
	fixture := seedAppAdminFixture(t, pool, "bot_notification_post_commit_fanout", now)
	approvalApp := installApprovalAppForNotificationTests(t, ctx, pool, fixture, "post_commit_fanout", now)
	const (
		identityID = "agentteam.test.approval.post_commit_fanout"
		basisKind  = "test_post_commit_fanout_assignment"
		basisRef   = "assignment:req_post_commit_fanout"
	)
	configureApprovalAppRecipientForTests(
		t, ctx, pool, store, fixture, approvalApp,
		"test.approval", "request", basisKind, basisRef,
	)
	require.NoError(t, store.RegisterAppNotificationBuiltinService(AppNotificationBuiltinServiceRegistration{
		IdentityID: identityID, ServiceDefinitionSlug: "agentteam-approval", ActionHandlerKey: identityID,
		AllowedBusinessNamespaces: []string{"test.approval"},
		AllowedTargetKinds:        []string{AppNotificationTargetHumanDM},
		AllowedContentKinds:       []string{AppNotificationContentMessage},
	}))

	fanout := &recordingCommittedMessageFanout{
		query: func(callbackCtx context.Context, delivery message.MessageDelivery) {
			var messageCount int
			require.NoError(t, pool.QueryRow(callbackCtx, `
SELECT COUNT(*)
FROM scope_messages
WHERE workspace_id = $1 AND id = $2
`, fixture.WorkspaceID, delivery.MessageID).Scan(&messageCount))
			require.Equal(t, 1, messageCount, "fanout must run only after the message transaction commits")
		},
	}
	store.SetCommittedMessageFanout(fanout)
	command := AppNotificationCommand{
		ClientNotificationID: "approval:req_post_commit_fanout",
		Target: AppNotificationTarget{
			Kind: AppNotificationTargetHumanDM, MemberID: fixture.MemberID,
		},
		RecipientBasis: AppNotificationRecipientBasis{Kind: basisKind, Ref: basisRef},
		Content: AppNotificationContent{
			Kind: AppNotificationContentMessage, Text: "Approval requested",
		},
		BusinessObject: AppNotificationBusinessObject{
			Namespace: "test.approval", Type: "request", ID: "req_post_commit_fanout", Version: 1,
		},
		CausedByActor: AppNotificationActorRef{Type: "human", ID: fixture.MemberID},
	}

	created, err := store.CreateAppNotificationForBuiltin(ctx, identityID, fixture.WorkspaceID, command)
	require.NoError(t, err)
	require.False(t, created.AlreadyCreated)
	require.Len(t, fanout.deliveries, 1)
	require.Equal(t, created.MessageID, fanout.deliveries[0].MessageID)
	require.NotNil(t, fanout.deliveries[0].AppMessage)
	require.NotNil(t, fanout.deliveries[0].Conversation)
	require.Equal(t, "dm:"+created.ScopeID, fanout.deliveries[0].Conversation.ConversationID)
	require.Equal(t, message.ConversationKindDM, fanout.deliveries[0].Conversation.Kind)
	require.Equal(t, message.ConversationKindDM, fanout.deliveries[0].Conversation.Projection["kind"])
	require.Equal(t, "service", fanout.deliveries[0].Conversation.Projection["peerType"])
	require.Equal(t, approvalApp.Installation.ID, fanout.deliveries[0].Conversation.Projection["peerId"])
	require.Equal(t, "Approval", fanout.deliveries[0].Conversation.Projection["title"])
	require.Equal(t, "agentteam-approval", fanout.deliveries[0].Conversation.Projection["subtitle"])
	require.Equal(t, "Approval requested", fanout.deliveries[0].Conversation.Projection["preview"])

	replayed, err := store.CreateAppNotificationForBuiltin(ctx, identityID, fixture.WorkspaceID, command)
	require.NoError(t, err)
	require.True(t, replayed.AlreadyCreated)
	require.Len(t, fanout.deliveries, 1, "idempotent create replay must not project the message twice")
}
