package serviceaccount

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

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

	ctx := context.Background()
	db := startServiceAccountRollbackDB(t, ctx)
	now := time.Now().UTC().Truncate(time.Second)
	fixture := seedServiceAccountFixture(t, db, "deletion_outbox", "Deletion Outbox", "platform_sa_deleted", now)
	store := New(db)
	input := PlatformDeletionDeliveryInsert{
		ID:                             "sadel_deletion_outbox",
		WorkspaceID:                    fixture.WorkspaceID,
		PlatformServiceAccountID:       "platform_sa_deleted",
		LocalServiceAccountID:          "sacct_deleted",
		LocalServiceAccountPrincipalID: "platform_sa_deleted",
		MessageScopeID:                 fixture.ScopeID,
		Now:                            now,
	}
	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		return store.InTx(tx).InsertPlatformDeletionDelivery(ctx, input)
	}))

	candidates, err := store.ListDuePlatformDeletionDeliveries(ctx, now.Add(-time.Second), "", 10)
	require.NoError(t, err)
	require.Empty(t, candidates)
	candidates, err = store.ListDuePlatformDeletionDeliveries(ctx, now, "", 10)
	require.NoError(t, err)
	require.Equal(t, []PlatformDeletionDeliveryCandidate{{ID: input.ID, WorkspaceID: fixture.WorkspaceID}}, candidates)

	first, leased, err := store.LeasePlatformDeletionDelivery(ctx, input.ID, now, 30*time.Second)
	require.NoError(t, err)
	require.True(t, leased)
	require.Equal(t, int64(1), first.AttemptCount)
	require.NotEmpty(t, first.LeaseToken)
	require.NotNil(t, first.LeaseExpiresAt)
	require.Equal(t, input.PlatformServiceAccountID, first.PlatformServiceAccountID)
	require.Equal(t, input.LocalServiceAccountID, first.LocalServiceAccountID)
	require.Equal(t, input.LocalServiceAccountPrincipalID, first.LocalServiceAccountPrincipalID)
	require.Equal(t, input.MessageScopeID, first.MessageScopeID)

	_, leased, err = store.LeasePlatformDeletionDelivery(ctx, input.ID, now.Add(time.Second), 30*time.Second)
	require.NoError(t, err)
	require.False(t, leased)
	require.NoError(t, store.MarkPlatformDeletionDeliveryFailed(ctx, first.ID, first.LeaseToken, errors.New("runtime observer unavailable"), now, 10*time.Second))

	var attemptCount int64
	var leaseToken *string
	var leaseExpiresAt *time.Time
	var nextAttemptAt time.Time
	var lastError string
	require.NoError(t, db.QueryRow(ctx, `
SELECT attempt_count, lease_token, lease_expires_at, next_attempt_at, last_error
FROM platform_service_account_deletion_outbox
WHERE id = $1
`, input.ID).Scan(&attemptCount, &leaseToken, &leaseExpiresAt, &nextAttemptAt, &lastError))
	require.Equal(t, int64(1), attemptCount)
	require.Nil(t, leaseToken)
	require.Nil(t, leaseExpiresAt)
	require.Equal(t, now.Add(time.Second), nextAttemptAt.UTC())
	require.Equal(t, "runtime observer unavailable", lastError)

	candidates, err = store.ListDuePlatformDeletionDeliveries(ctx, now, "", 10)
	require.NoError(t, err)
	require.Empty(t, candidates)
	candidates, err = store.ListDuePlatformDeletionDeliveries(ctx, nextAttemptAt, "", 10)
	require.NoError(t, err)
	require.Len(t, candidates, 1)

	second, leased, err := store.LeasePlatformDeletionDelivery(ctx, input.ID, nextAttemptAt, 30*time.Second)
	require.NoError(t, err)
	require.True(t, leased)
	require.Equal(t, int64(2), second.AttemptCount)
	require.NotEqual(t, first.LeaseToken, second.LeaseToken)
	require.Empty(t, second.LastError)

	err = store.MarkPlatformDeletionDeliveryDelivered(ctx, second.ID, first.LeaseToken, nextAttemptAt)
	require.ErrorIs(t, err, ErrConflict)
	require.NoError(t, store.MarkPlatformDeletionDeliveryDelivered(ctx, second.ID, second.LeaseToken, nextAttemptAt))
	requireSinglePlatformDeletionDeliveryCount(t, ctx, db, input.ID, 0)
}

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

	ctx := context.Background()
	db := startServiceAccountRollbackDB(t, ctx)
	now := time.Now().UTC().Truncate(time.Second)
	userID := "usr_sa_deletion_outbox_fk"
	organizationID := "org_sa_deletion_outbox_fk"
	workspaceID := "wsp_sa_deletion_outbox_fk"
	mustExec(t, db, `
INSERT INTO users (id, email, name, display_name, password_hash, created_at, updated_at)
VALUES ($1, 'sa-deletion-outbox-fk@example.com', 'sa-deletion-outbox-fk', 'SA Deletion Outbox FK', 'hash', $2, $2)
`, userID, now)
	mustExec(t, db, `
INSERT INTO organizations (id, slug, name, created_by_user_id, created_at, updated_at)
VALUES ($1, 'sa-deletion-outbox-fk', 'SA Deletion Outbox FK', $2, $3, $3)
`, organizationID, userID, now)
	mustExec(t, db, `
INSERT INTO workspaces (id, organization_id, slug, name, created_at, updated_at)
VALUES ($1, $2, 'sa-deletion-outbox-fk', 'SA Deletion Outbox FK', $3, $3)
`, workspaceID, organizationID, now)

	store := New(db)
	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		return store.InTx(tx).InsertPlatformDeletionDelivery(ctx, PlatformDeletionDeliveryInsert{
			ID:                             "sadel_deletion_outbox_fk",
			WorkspaceID:                    workspaceID,
			PlatformServiceAccountID:       "missing_platform_account",
			LocalServiceAccountID:          "missing_local_account",
			LocalServiceAccountPrincipalID: "missing_local_principal",
			MessageScopeID:                 "missing_message_scope",
			Now:                            now,
		})
	}))
	requireSinglePlatformDeletionDeliveryCount(t, ctx, db, "sadel_deletion_outbox_fk", 1)

	mustExec(t, db, `DELETE FROM workspaces WHERE id = $1`, workspaceID)
	requireSinglePlatformDeletionDeliveryCount(t, ctx, db, "sadel_deletion_outbox_fk", 0)
}

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

	require.Equal(t, time.Second, platformDeletionDeliveryRetryDelay(1, 30*time.Second))
	require.Equal(t, 9*time.Second, platformDeletionDeliveryRetryDelay(3, 30*time.Second))
	require.Equal(t, 30*time.Second, platformDeletionDeliveryRetryDelay(6, 30*time.Second))
	require.Equal(t, 30*time.Second, platformDeletionDeliveryRetryDelay(1<<62, 30*time.Second))
}

func requireSinglePlatformDeletionDeliveryCount(t testing.TB, ctx context.Context, db coredb.DB, id string, want int64) {
	t.Helper()
	var count int64
	require.NoError(t, db.QueryRow(ctx, `
SELECT COUNT(*) FROM platform_service_account_deletion_outbox WHERE id = $1
`, id).Scan(&count))
	require.Equal(t, want, count)
}
