package e2e_encryption_perf

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

	g "github.com/onsi/ginkgo/v2"
	"github.com/stretchr/testify/require"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"

	configv1 "github.com/openshift/api/config/v1"
	oauthapiv1 "github.com/openshift/api/oauth/v1"
	operatorv1 "github.com/openshift/api/operator/v1"
	oauthclient "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1"
	operatorlibrary "github.com/openshift/cluster-authentication-operator/test/library"
	operatorencryption "github.com/openshift/cluster-authentication-operator/test/library/encryption"
	library "github.com/openshift/library-go/test/library/encryption"
)

const (
	tokenStatsKey = "created oauthaccesstokens"
)

var _ = g.Describe("[sig-auth] authentication operator", func() {
	g.It("[Encryption][Serial] TestPerfEncryptionTypeAESCBC", func(ctx context.Context) {
		testPerfEncryptionTypeAESCBC(ctx, g.GinkgoTB())
	})
})

func testPerfEncryptionTypeAESCBC(ctx context.Context, tt testing.TB) {
	ctx, cancel := context.WithTimeout(ctx, 30*time.Minute)
	tt.Cleanup(cancel)
	clientSet := getPerfClients(tt)
	library.TestPerfEncryption(ctx, tt, library.PerfScenario{
		BasicScenario: library.BasicScenario{
			Namespace:                       "openshift-config-managed",
			LabelSelector:                   "encryption.apiserver.operator.openshift.io/component" + "=" + "openshift-oauth-apiserver",
			EncryptionConfigSecretName:      fmt.Sprintf("encryption-config-%s", "openshift-oauth-apiserver"),
			EncryptionConfigSecretNamespace: "openshift-config-managed",
			OperatorNamespace:               "openshift-authentication-operator",
			TargetGRs:                       library.WellKnownAuthTargetGRs,
			AssertFunc:                      library.AssertWellKnownTokens,
		},
		GetOperatorConditionsFunc: func(t testing.TB) ([]operatorv1.OperatorCondition, error) {
			apiServerOperator, err := clientSet.OperatorClient.Get(ctx, "cluster", metav1.GetOptions{})
			if err != nil {
				return nil, err
			}
			return apiServerOperator.Status.Conditions, nil
		},
		AssertDBPopulatedFunc: func(t testing.TB, errorStore map[string]int, statStore map[string]int) {
			require.Empty(t, errorStore, "db loader workers reported errors")

			tokenCount, ok := statStore[tokenStatsKey]
			require.True(t, ok, "missing oauth access tokens count stats")
			require.GreaterOrEqual(t, tokenCount, 14000)
			t.Logf("Created %d access tokens", tokenCount)
		},
		AssertMigrationTime: func(t testing.TB, migrationTime time.Duration) {
			t.Logf("migration took %v", migrationTime)
			expectedMigrationTime := 10 * time.Minute
			if migrationTime > expectedMigrationTime {
				t.Errorf("migration took too long (%v), expected it to take no more than %v", migrationTime, expectedMigrationTime)
			}
		},
		DBLoaderWorkers: 3,
		DBLoaderFunc: library.DBLoaderRepeat(1, false,
			library.DBLoaderRepeatParallel(5010, 50, false, createAccessTokenWrapper(ctx, clientSet.TokenClient), reportSecret)),
		EncryptionProvider: library.EncryptionProvider{
			APIServerEncryption: configv1.APIServerEncryption{Type: configv1.EncryptionTypeAESCBC},
		},
	})
}

func createAccessTokenWrapper(ctx context.Context, tokenClient oauthclient.OAuthAccessTokensGetter) library.DBLoaderFuncType {
	return func(_ kubernetes.Interface, namespace string, errorCollector func(error), statsCollector func(string)) error {
		_, tokenNameHash := operatorlibrary.GenerateOAuthTokenPair()
		token := &oauthapiv1.OAuthAccessToken{
			ObjectMeta: metav1.ObjectMeta{
				Name: tokenNameHash,
			},
			RefreshToken: "I have no special talents. I am only passionately curious",
			UserName:     "kube:admin",
			Scopes:       []string{"user:full"},
			RedirectURI:  "redirect.me.to.token.of.life",
			ClientName:   "console",
			UserUID:      "non-existing-user-id",
		}
		_, err := tokenClient.OAuthAccessTokens().Create(ctx, token, metav1.CreateOptions{})
		return err
	}
}

func reportSecret(_ kubernetes.Interface, _ string, _ func(error), statsCollector func(string)) error {
	statsCollector(tokenStatsKey)
	return nil
}

func getPerfClients(t testing.TB) operatorencryption.ClientSet {
	t.Helper()

	kubeConfig := operatorlibrary.NewClientConfigForTest(t)

	kubeConfig.QPS = 300
	kubeConfig.Burst = 600

	return operatorencryption.GetClientsFor(t, kubeConfig)
}
