package agent

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testhelpers"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
)

func TestImagePullSecretsCloningSuccess(t *testing.T) {
	upstreamSecretName := "secret"
	upstreamSecretNamespace := "secret-namespace"
	workspaceName := "test-workspace"
	workspaceNamespace := "test-workspace-namespace"
	agentKey := testhelpers.AgentkKey1
	imagePullSecrets := []ImagePullSecret{
		{Name: upstreamSecretName, Namespace: upstreamSecretNamespace},
	}
	var testcases = []struct {
		name  string
		setup func(client *MockClient, inf *mockInformer)
	}{

		{
			name: "when the informer has the secret",
			setup: func(client *MockClient, inf *mockInformer) {
				inf.AddResource(generateImagePullSecret(upstreamSecretName, upstreamSecretNamespace, nil, map[string][]byte{"data": {3, 21}}))
			},
		},
		{
			name: "when the informer does not have the secret but the server does",
			setup: func(client *MockClient, inf *mockInformer) {
				secret := toUnstructuredSecret(t, generateImagePullSecret(upstreamSecretName, upstreamSecretNamespace, nil, map[string][]byte{"data": {3, 21}}))
				_, err := client.UpdateOrCreate(context.Background(), secretGVR, upstreamSecretNamespace, secret, true)
				require.NoError(t, err)
			},
		},
	}
	for _, testcase := range testcases {
		t.Run(testcase.name, func(t *testing.T) {
			tracker := newImagePullSecretTracker()
			client := NewMockClient()
			inf := newMockSecretInformer(t)
			testcase.setup(client, inf)

			handler := newImagePullSecretsHandler(testlogger.New(t), client, inf, tracker)
			err := handler(context.Background(), agentKey, workspaceNamespace, workspaceName, imagePullSecrets)
			assert.NoError(t, err)

			// Assert tracker has registered the namespace
			namespaces := tracker.get(upstreamSecretNamespace, upstreamSecretName)
			assert.Contains(t, namespaces, workspaceNamespace)
			assert.Len(t, namespaces, 1)
		})
	}
}

func TestImagePullSecretsCloningFailures(t *testing.T) {
	upstreamSecretName := "secret"
	upstreamSecretNamespace := "secret-namespace"
	workspaceName := "test-workspace"
	workspaceNamespace := "test-workspace-namespace"
	agentKey := testhelpers.AgentkKey1

	tracker := newImagePullSecretTracker()
	inf := newMockSecretInformer(t)

	client := NewMockClient()
	secret := toUnstructuredSecret(t, generateImagePullSecret(upstreamSecretName, upstreamSecretNamespace, nil, nil))
	_, err := client.UpdateOrCreate(context.Background(), secretGVR, upstreamSecretNamespace, secret, true)
	require.NoError(t, err)

	client.MockErrorClient = errors.New("failed to update workspace")
	imagePullSecrets := []ImagePullSecret{{Name: upstreamSecretName, Namespace: upstreamSecretNamespace}}

	handler := newImagePullSecretsHandler(testlogger.New(t), client, inf, tracker)
	err = handler(context.Background(), agentKey, workspaceNamespace, workspaceName, imagePullSecrets)
	assert.ErrorContains(t, err, "failed to clone upstream image pull secret")

	// Assert that the client did not create the secret
	assert.Len(t, client.SecretStore, 1)

	// Assert tracker did not registered the namespace
	namespaces := tracker.get(upstreamSecretNamespace, upstreamSecretName)
	assert.Nil(t, namespaces)

}

func TestImagePullSecretsCloningIgnored(t *testing.T) {
	upstreamSecretName := "secret"
	upstreamSecretNamespace := "secret-namespace"
	workspaceName := "test-workspace"
	workspaceNamespace := "test-workspace-namespace"
	agentKey := testhelpers.AgentkKey1
	var testcases = []struct {
		name             string
		setup            func(client *MockClient, tracker *imagePullSecretTracker)
		imagePullSecrets []ImagePullSecret
	}{

		{
			name: "when an upstream image pull is not present, we attempt to cleanup secrets in the workspace namespace",
			setup: func(client *MockClient, tracker *imagePullSecretTracker) {
				// Set prexisting cloned secret that will be asserted in test does not exist anymore
				tracker.set(upstreamSecretNamespace, upstreamSecretName, workspaceNamespace)
			},
			imagePullSecrets: []ImagePullSecret{
				{Name: upstreamSecretName, Namespace: upstreamSecretNamespace},
			},
		},
		{
			name: "when an upstream image pull secret cannot be parsed",
			setup: func(client *MockClient, tracker *imagePullSecretTracker) {
				upstreamSecret := &unstructured.Unstructured{
					Object: map[string]any{
						"name":      upstreamSecretName,
						"namespace": upstreamSecretNamespace,
						"data":      map[string]any{"password": 12345},
					},
				}

				_, err := client.UpdateOrCreate(context.Background(), secretGVR, upstreamSecretNamespace, upstreamSecret, true)
				require.NoError(t, err)
			},
			imagePullSecrets: []ImagePullSecret{
				{Name: upstreamSecretName, Namespace: upstreamSecretNamespace},
			},
		},
	}
	for _, testcase := range testcases {
		t.Run(testcase.name, func(t *testing.T) {
			tracker := newImagePullSecretTracker()
			client := NewMockClient()
			testcase.setup(client, tracker)
			inf := newMockSecretInformer(t)

			handler := newImagePullSecretsHandler(testlogger.New(t), client, inf, tracker)
			err := handler(context.Background(), agentKey, workspaceNamespace, workspaceName, testcase.imagePullSecrets)
			assert.NoError(t, err)

			// Nothing was originally tracked and all updates were ignored, assert that the  workspaceNamespace is not in the tracker
			namespaces := tracker.get(upstreamSecretNamespace, upstreamSecretName)
			assert.Nil(t, namespaces)
		})
	}
}

func newMockSecretInformer(t *testing.T) *mockInformer {
	var inf *mockInformer
	inf = newMockInformer(func(i any) {
		secret, ok := i.(*v1.Secret)
		require.True(t, ok)
		object, err := runtime.DefaultUnstructuredConverter.ToUnstructured(secret)
		require.NoError(t, err)
		_, exist := inf.Resources[secret.Namespace]
		if !exist {
			inf.Resources[secret.Namespace] = make(map[string]*unstructured.Unstructured)
		}
		inf.Resources[secret.Namespace][secret.Name] = &unstructured.Unstructured{
			Object: object,
		}

	})
	return inf
}
