package app

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/runeffect"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

func TestUninstallAppPreservesAppNotificationHistoryAndTerminalizesActiveAction(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_uninstall", now)

	const (
		identityID = "agentteam.test.uninstall"
		handlerKey = "agentteam.test.uninstall"
		slug       = "app-notification-uninstall"
		appID      = "app_bot_notification_uninstall"
	)
	insertExternalAppAppForTests(t, pool, externalAppAppSeed{
		AppID:      appID,
		VersionID:  "appver_bot_notification_uninstall_1_0_0",
		Slug:       slug,
		Name:       "App Notification Uninstall",
		Visibility: "unlisted",
		Version:    "1.0.0",
		CreatedAt:  now,
		Manifest: map[string]any{
			"schema_version": "1",
			"slug":           slug,
			"name":           "App Notification Uninstall",
			"version":        "1.0.0",
			"capabilities": map[string]any{
				"runtime": map[string]any{
					"runtime": map[string]any{
						"transports":        []string{serviceactor.RuntimeTransportKindCallback},
						"default_transport": serviceactor.RuntimeTransportKindCallback,
					},
					"events": []string{registry.AppEventMessageMention, registry.AppEventNotificationAction},
					"scopes": []string{
						AppMentionScopeRead,
						AppRuntimeScopeMessageWrite,
						AppNotificationScopeWrite,
						AppNotificationActionScopeRead,
					},
					"notifications": map[string]any{
						"targets": []string{"bound_scope"},
						"content": []string{AppNotificationContentInteractiveCard},
					},
				},
			},
		},
	})

	installation := requestAndApproveExternalAppInstallationForTests(
		t, ctx, store, fixture.UserID, fixture.UserID, fixture.WorkspaceID, appID, slug, nil,
	)
	channelID := "chn_bot_notification_uninstall"
	seedChannelParticipantProjectionForTests(t, pool, fixture.WorkspaceID, channelID, "app-notification-uninstall", "App notification uninstall", "private", fixture.MemberID, fixture.MemberID, now)
	_, err := store.AddChannelServiceForUserWithEvent(ctx, fixture.UserID, fixture.WorkspaceID, channelID, installation.MemberID)
	require.NoError(t, err)
	require.NoError(t, store.RegisterAppNotificationBuiltinService(AppNotificationBuiltinServiceRegistration{
		IdentityID: identityID, ServiceDefinitionSlug: slug, ActionHandlerKey: handlerKey,
		AllowedBusinessNamespaces: []string{"test.uninstall"},
		AllowedTargetKinds:        []string{AppNotificationTargetScope},
		AllowedContentKinds:       []string{AppNotificationContentInteractiveCard},
	}))

	created, err := store.CreateAppNotificationForBuiltin(ctx, identityID, fixture.WorkspaceID, AppNotificationCommand{
		ClientNotificationID: "uninstall:req_1",
		Target:               AppNotificationTarget{Kind: AppNotificationTargetScope, ScopeID: channelID},
		Content: AppNotificationContent{
			Kind:         AppNotificationContentInteractiveCard,
			FallbackText: "Approval requested before uninstall",
			CardJSON: []byte(`{
				"schemaVersion":"1",
				"title":"Approval requested before uninstall",
				"actions":[{"actionKey":"approve","type":"callback","label":"Approve"}]
			}`),
		},
		BusinessObject: AppNotificationBusinessObject{Namespace: "test.uninstall", Type: "request", ID: "req_1", Version: 1},
		CausedByActor:  AppNotificationActorRef{Type: "human", ID: fixture.MemberID},
	})
	require.NoError(t, err)
	action, err := store.InvokeAppNotificationActionForUser(ctx, fixture.UserID, fixture.WorkspaceID, AppNotificationActionCommand{
		MessageID: created.MessageID, ActionKey: "approve", CardRevision: 1,
		ClientActionID: "approve:req_1", Input: map[string]any{},
	})
	require.NoError(t, err)
	require.Equal(t, AppNotificationActionStateAccepted, action.State)
	var deliveryStatus string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT status FROM service_event_deliveries
WHERE workspace_id = $1 AND id = $2
`, fixture.WorkspaceID, action.ServiceEventDeliveryID).Scan(&deliveryStatus))
	require.Equal(t, string(runeffect.StatusRunning), deliveryStatus)

	uninstalled, err := store.UninstallAppInstallationForUser(ctx, fixture.UserID, fixture.WorkspaceID, installation.ID)
	require.NoError(t, err)
	require.False(t, uninstalled.Enabled)

	var installationCount, definitionCount, deliveryCount, subscriptionCount int
	require.NoError(t, pool.QueryRow(ctx, `SELECT COUNT(*) FROM service_installations WHERE id = $1`, installation.ID).Scan(&installationCount))
	require.NoError(t, pool.QueryRow(ctx, `SELECT COUNT(*) FROM service_definitions WHERE id = $1`, appID).Scan(&definitionCount))
	require.NoError(t, pool.QueryRow(ctx, `SELECT COUNT(*) FROM service_event_deliveries WHERE id = $1`, action.ServiceEventDeliveryID).Scan(&deliveryCount))
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*) FROM service_event_subscriptions
WHERE service_installation_id = $1 AND subscription_kind = $2
`, installation.ID, serviceactor.SubscriptionKindAppNotificationAction).Scan(&subscriptionCount))
	require.Zero(t, installationCount)
	require.Zero(t, definitionCount, "the private App definition must be deleted once notification history is terminalized")
	require.Zero(t, deliveryCount)
	require.Zero(t, subscriptionCount)

	notification, err := store.LoadAppNotification(ctx, fixture.WorkspaceID, created.NotificationID)
	require.NoError(t, err)
	require.Equal(t, AppNotificationStatusExpired, notification.Status)
	require.Equal(t, int64(2), notification.Revision)
	require.Equal(t, installation.ID, notification.ServiceInstallationID)
	require.Equal(t, appID, notification.ServiceDefinitionID)
	require.Equal(t, appNotificationUninstallExecutionActor, notification.ExecutionActorID)
	require.NotNil(t, notification.ExpiresAt)

	card, err := store.LoadAppNotificationCardInTx(ctx, pool, created.MessageID, false)
	require.NoError(t, err)
	require.Equal(t, AppNotificationCardStateExpired, card.State)
	require.Equal(t, int64(2), card.CardRevision)

	var messageContent string
	var messageEditRevision int64
	var messageCount int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*), COALESCE(MAX(content), ''), COALESCE(MAX(edit_revision), 0)
FROM scope_messages
WHERE workspace_id = $1 AND scope_id = $2 AND id = $3
`, fixture.WorkspaceID, created.ScopeID, created.MessageID).Scan(&messageCount, &messageContent, &messageEditRevision))
	require.Equal(t, 1, messageCount)
	require.Equal(t, "Approval requested before uninstall", messageContent)
	require.Equal(t, int64(1), messageEditRevision)

	actionRecord, err := store.LoadAppNotificationActionInTx(ctx, pool, fixture.WorkspaceID, action.ActionInvocationID, false)
	require.NoError(t, err)
	require.Equal(t, appNotificationActionStateCancelled, actionRecord.State)
	require.NotEmpty(t, actionRecord.ResultCode)
	require.NotNil(t, actionRecord.CompletedAt)
	require.Equal(t, action.ServiceEventDeliveryID, actionRecord.ServiceEventDeliveryID)

	var mutationKind, executionActorID string
	var mutationCount int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*), COALESCE(MAX(mutation_kind), ''), COALESCE(MAX(execution_actor_id), '')
FROM service_notification_mutations
WHERE workspace_id = $1 AND service_notification_id = $2
`, fixture.WorkspaceID, created.NotificationID).Scan(&mutationCount, &mutationKind, &executionActorID))
	require.Equal(t, 1, mutationCount)
	require.Equal(t, AppNotificationMutationExpire, mutationKind)
	require.Equal(t, appNotificationUninstallExecutionActor, executionActorID)
	var mutationOutboxCount int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_fanout_outbox outbox
JOIN service_notification_mutations mutation ON mutation.id = outbox.event_ref_id
WHERE mutation.service_notification_id = $1
  AND outbox.event_ref_type = $2
  AND outbox.fanout_type = $3
`, created.NotificationID, message.ScopeMessageMutationEventRefType, message.ScopeOutboxFanoutMessageMutation).Scan(&mutationOutboxCount))
	require.Equal(t, 1, mutationOutboxCount)

	var claimableActions int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM service_event_deliveries
WHERE workspace_id = $1
  AND service_installation_id = $2
  AND subscription_kind = $3
  AND status IN ('running', 'pending', 'retry_waiting')
`, fixture.WorkspaceID, installation.ID, serviceactor.SubscriptionKindAppNotificationAction).Scan(&claimableActions))
	require.Zero(t, claimableActions)
}
