package app

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

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

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	appStore := New(pool, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
	now := time.Date(2026, 7, 21, 9, 0, 0, 0, time.UTC)
	admin := seedAppAdminFixture(t, pool, "app_delivery_scope", now)
	machineOwner := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "app_delivery_scope_machine_owner", now)
	appAuthorizer := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "app_delivery_scope_authorizer", now)
	outsider := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "app_delivery_scope_outsider", now)
	reviewChannelID := seedProviderWatchReviewChannel(t, pool, admin, "app_delivery_scope", now)
	seedChannelParticipantProjectionForTests(t, pool, admin.WorkspaceID, reviewChannelID, "app_delivery_scope_review", "", "private", admin.MemberID, machineOwner.MemberID, now)

	installation, err := appStore.installAppForUserForTest(ctx, admin.UserID, admin.WorkspaceID, InstallAppParams{AppSlug: "github"})
	require.NoError(t, err)
	seedProviderWatchReadyConnection(t, ctx, appStore, admin, installation, "github", "789", "acme")

	adminSubscription, err := appStore.CreateServiceEventSubscriptionForUser(
		ctx,
		admin.UserID,
		admin.WorkspaceID,
		installation.ID,
		reviewChannelID,
		"https://github.com/acme/agentteam/pull/42",
		nil,
		nil,
	)
	require.NoError(t, err)
	machineOwnerSubscription, err := appStore.CreateServiceEventSubscriptionForUser(
		ctx,
		machineOwner.UserID,
		admin.WorkspaceID,
		installation.ID,
		reviewChannelID,
		"https://github.com/acme/agentteam/pull/42",
		nil,
		nil,
	)
	require.NoError(t, err)

	agentID := "agt_app_delivery_scope_machine_owner"
	agentMemberID := "mem_app_delivery_scope_machine_owner_agent"
	seedAppAgentParticipant(
		t,
		pool,
		admin.WorkspaceID,
		machineOwner.UserID,
		machineOwner.MemberID,
		reviewChannelID,
		"mac_app_delivery_scope_machine_owner",
		agentID,
		agentMemberID,
		"delivery-scope-agent",
		now,
	)
	authorization, err := appStore.UpsertAgentAppAuthorization(ctx, UpsertAgentAppAuthorizationParams{
		WorkspaceID:          admin.WorkspaceID,
		AgentID:              agentID,
		AppInstallationID:    installation.ID,
		AuthorizedByMemberID: appAuthorizer.MemberID,
		Scopes:               []string{ServiceEventSubscriptionScopeRead, ServiceEventSubscriptionScopeWrite},
	})
	require.NoError(t, err)
	require.Equal(t, appAuthorizer.MemberID, authorization.AuthorizedByMemberID)
	require.NotEqual(t, machineOwner.MemberID, authorization.AuthorizedByMemberID)
	agentTarget, err := ResolveServiceEventTargetForScope(ctx, pool, admin.WorkspaceID, agentMemberID, reviewChannelID)
	require.NoError(t, err)
	agentSubscription, err := appStore.CreateServiceEventSubscription(ctx, admin.WorkspaceID, CreateServiceEventSubscriptionParams{
		Provider:       "github",
		InstallationID: installation.ID,
		Actor: ServiceEventActor{
			ActorType:        "agent",
			ActorID:          agentID,
			MemberID:         agentMemberID,
			AuthorizerUserID: authorization.AuthorizedByMemberID,
		},
		Target:       agentTarget,
		ResourceRef:  "https://github.com/acme/agentteam/pull/43",
		ResourceKey:  "github:acme/agentteam#43",
		EventFilters: map[string]any{},
		CreatedVia:   "mcp",
	})
	require.NoError(t, err)
	require.Equal(t, machineOwner.MemberID, agentSubscription.Subscription.CreatedByMemberID)

	seedAppWebhookDeliveryForSubscriptions(
		t,
		ctx,
		pool,
		appStore,
		admin.WorkspaceID,
		installation.ID,
		"admin_only",
		[]string{adminSubscription.Subscription.ID},
		now.Add(time.Minute),
	)
	seedAppWebhookDeliveryForSubscriptions(
		t,
		ctx,
		pool,
		appStore,
		admin.WorkspaceID,
		installation.ID,
		"shared",
		[]string{adminSubscription.Subscription.ID, machineOwnerSubscription.Subscription.ID},
		now.Add(2*time.Minute),
	)
	seedAppWebhookDeliveryForSubscriptions(
		t,
		ctx,
		pool,
		appStore,
		admin.WorkspaceID,
		installation.ID,
		"member_agent",
		[]string{agentSubscription.Subscription.ID},
		now.Add(3*time.Minute),
	)

	adminDeliveries, err := appStore.ListAppWebhookDeliveriesForUser(ctx, admin.UserID, admin.WorkspaceID, installation.ID, 25)
	require.NoError(t, err)
	require.Len(t, adminDeliveries, 3)
	require.Equal(
		t,
		[]string{adminSubscription.Subscription.ID},
		appWebhookDeliveryMatchedSubscriptionIDs(appWebhookDeliveryByKey(t, adminDeliveries, "admin_only")),
	)
	require.ElementsMatch(
		t,
		[]string{adminSubscription.Subscription.ID, machineOwnerSubscription.Subscription.ID},
		appWebhookDeliveryMatchedSubscriptionIDs(appWebhookDeliveryByKey(t, adminDeliveries, "shared")),
	)

	machineOwnerDeliveries, err := appStore.ListAppWebhookDeliveriesForUser(ctx, machineOwner.UserID, admin.WorkspaceID, installation.ID, 25)
	require.NoError(t, err)
	require.Len(t, machineOwnerDeliveries, 2)
	require.ElementsMatch(t, []string{"shared", "member_agent"}, appWebhookDeliveryKeys(machineOwnerDeliveries))
	require.Equal(
		t,
		[]string{machineOwnerSubscription.Subscription.ID},
		appWebhookDeliveryMatchedSubscriptionIDs(appWebhookDeliveryByKey(t, machineOwnerDeliveries, "shared")),
	)
	require.Equal(
		t,
		[]string{agentSubscription.Subscription.ID},
		appWebhookDeliveryMatchedSubscriptionIDs(appWebhookDeliveryByKey(t, machineOwnerDeliveries, "member_agent")),
	)

	appAuthorizerDeliveries, err := appStore.ListAppWebhookDeliveriesForUser(ctx, appAuthorizer.UserID, admin.WorkspaceID, installation.ID, 25)
	require.NoError(t, err)
	require.Empty(t, appAuthorizerDeliveries)

	outsiderDeliveries, err := appStore.ListAppWebhookDeliveriesForUser(ctx, outsider.UserID, admin.WorkspaceID, installation.ID, 25)
	require.NoError(t, err)
	require.Empty(t, outsiderDeliveries)

	mustExec(t, pool, `UPDATE members SET workspace_role = 'owner', updated_at = $2 WHERE id = $1`, admin.MemberID, now.Add(4*time.Minute))
	ownerDeliveries, err := appStore.ListAppWebhookDeliveriesForUser(ctx, admin.UserID, admin.WorkspaceID, installation.ID, 25)
	require.NoError(t, err)
	require.Len(t, ownerDeliveries, 3)
}

func seedAppWebhookDeliveryForSubscriptions(
	t *testing.T,
	ctx context.Context,
	pool coredb.DB,
	appStore *Store,
	workspaceID string,
	installationID string,
	deliveryKey string,
	subscriptionIDs []string,
	now time.Time,
) {
	t.Helper()

	require.NoError(t, coredb.WithTx(ctx, pool, func(tx coredb.Tx) error {
		actorStore := appStore.serviceActorStore().InTx(tx)
		receipt, _, err := actorStore.ReserveWebhookReceipt(ctx, serviceactor.WebhookReceiptReserve{
			WorkspaceID:           workspaceID,
			ServiceDefinitionID:   "app_github",
			ServiceInstallationID: installationID,
			ServiceKind:           serviceactor.KindApp,
			EndpointKind:          serviceactor.EndpointKindProviderWebhook,
			DeliveryKey:           deliveryKey,
			EventType:             "issue_comment.created",
			EventID:               deliveryKey,
			EventKey:              "github:" + deliveryKey,
			PayloadExcerpt:        fmt.Sprintf(`{"deliveryKey":%q}`, deliveryKey),
			Payload:               []byte(fmt.Sprintf(`{"deliveryKey":%q}`, deliveryKey)),
			Now:                   now,
		})
		if err != nil {
			return err
		}
		if err := requireAppWebhookDeliveryMatchedSubscriptionCount(
			ctx,
			tx,
			installationID,
			deliveryKey,
			0,
		); err != nil {
			return fmt.Errorf("assert reserved webhook delivery count: %w", err)
		}

		for index, subscriptionID := range subscriptionIDs {
			_, _, err := actorStore.ReserveEventDelivery(ctx, serviceactor.EventDeliveryReserve{
				ID:                  fmt.Sprintf("svced_%s_%d", deliveryKey, index),
				WorkspaceID:         workspaceID,
				EventSubscriptionID: subscriptionID,
				WebhookReceiptID:    receipt.ID,
				EventType:           "issue_comment.created",
				EventID:             deliveryKey,
				EventKey:            "github:" + deliveryKey,
				IdempotencyKey:      fmt.Sprintf("app-delivery-scope:%s:%s", deliveryKey, subscriptionID),
				Now:                 now,
			})
			if err != nil {
				return err
			}
		}
		if err := actorStore.CompleteWebhookReceipt(ctx, serviceactor.WebhookReceiptOutcome{
			ID:                       receipt.ID,
			MatchedSubscriptionCount: len(subscriptionIDs),
			Status:                   serviceactor.WebhookReceiptStatusProcessed,
			Now:                      now,
		}); err != nil {
			return err
		}
		if err := requireAppWebhookDeliveryMatchedSubscriptionCount(
			ctx,
			tx,
			installationID,
			deliveryKey,
			len(subscriptionIDs),
		); err != nil {
			return fmt.Errorf("assert updated webhook delivery count: %w", err)
		}
		return nil
	}))
}

func appWebhookDeliveryMatchedSubscriptionCount(
	ctx context.Context,
	queryer coredb.QueryRower,
	installationID string,
	deliveryKey string,
) (int, error) {
	var got int
	if err := queryer.QueryRow(ctx, `
SELECT matched_subscription_count
FROM service_webhook_receipts
WHERE service_installation_id = $1
  AND delivery_key = $2
`, installationID, deliveryKey).Scan(&got); err != nil {
		return 0, err
	}
	return got, nil
}

func requireAppWebhookDeliveryMatchedSubscriptionCount(
	ctx context.Context,
	queryer coredb.QueryRower,
	installationID string,
	deliveryKey string,
	want int,
) error {
	got, err := appWebhookDeliveryMatchedSubscriptionCount(ctx, queryer, installationID, deliveryKey)
	if err != nil {
		return err
	}
	if got != want {
		return fmt.Errorf("matched subscription count = %d, want %d", got, want)
	}
	return nil
}

func appWebhookDeliveryByKey(t *testing.T, deliveries []AppWebhookDeliveryView, key string) AppWebhookDeliveryView {
	t.Helper()
	for _, delivery := range deliveries {
		if delivery.DeliveryKey == key {
			return delivery
		}
	}
	t.Fatalf("webhook delivery %q not found", key)
	return AppWebhookDeliveryView{}
}

func appWebhookDeliveryKeys(deliveries []AppWebhookDeliveryView) []string {
	keys := make([]string, 0, len(deliveries))
	for _, delivery := range deliveries {
		keys = append(keys, delivery.DeliveryKey)
	}
	return keys
}

func appWebhookDeliveryMatchedSubscriptionIDs(delivery AppWebhookDeliveryView) []string {
	ids := make([]string, 0, len(delivery.MatchedSubscriptions))
	for _, subscription := range delivery.MatchedSubscriptions {
		ids = append(ids, subscription.ID)
	}
	return ids
}
