package ack

import (
	"context"
	"errors"
	"testing"

	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type errorListClient struct {
	client.Client
	err error
}

func (c errorListClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
	return c.err
}

func TestGetACKACRImagePullSecret(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name        string
		cli         client.Client
		objects     []client.Object
		want        corev1.LocalObjectReference
		wantErr     bool
		errContains string
	}{
		{
			name:        "list secrets failed",
			cli:         errorListClient{Client: buildFakeClient(), err: errors.New("boom")},
			wantErr:     true,
			errContains: "failed to list secrets in kube-system namespace",
		},
		{
			name:        "no secrets",
			objects:     nil,
			wantErr:     true,
			errContains: "no valid ACR imagePullSecret found in kube-system namespace",
		},
		{
			name: "valid secret but in other namespace",
			objects: []client.Object{
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"a", "default", `{"auths":{"registry.example.com":{}}}`),
			},
			wantErr:     true,
			errContains: "no valid ACR imagePullSecret found in kube-system namespace",
		},
		{
			name: "only aggregation secret is valid",
			objects: []client.Object{
				newACRDockerConfigSecret(acrImagePullSecretAggregationName, "kube-system", `{"auths":{"registry.example.com":{}}}`),
			},
			want: corev1.LocalObjectReference{Name: acrImagePullSecretAggregationName},
		},
		{
			name: "prefer non-aggregation secret even if it sorts after aggregation",
			objects: []client.Object{
				newACRDockerConfigSecret(acrImagePullSecretAggregationName, "kube-system", `{"auths":{"registry.example.com":{}}}`),
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"zzzz", "kube-system", `{"auths":{"registry.example.com":{}}}`),
			},
			want: corev1.LocalObjectReference{Name: acrImagePullSecretPrefix + "zzzz"},
		},
		{
			name: "pick lexicographically smallest non-aggregation secret",
			objects: []client.Object{
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"b", "kube-system", `{"auths":{"registry.example.com":{}}}`),
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"a", "kube-system", `{"auths":{"registry.example.com":{}}}`),
				newACRDockerConfigSecret(acrImagePullSecretAggregationName, "kube-system", `{"auths":{"registry.example.com":{}}}`),
			},
			want: corev1.LocalObjectReference{Name: acrImagePullSecretPrefix + "a"},
		},
		{
			name: "all candidate secrets invalid",
			objects: []client.Object{
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"bad-json", "kube-system", `not-json`),
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"empty-auths", "kube-system", `{"auths":{}}`),
				newACRSecretMissingDockerConfigKey(acrImagePullSecretPrefix+"missing-key", "kube-system"),
			},
			wantErr:     true,
			errContains: "no valid ACR imagePullSecret found in kube-system namespace",
		},
		{
			name: "ignore invalid secrets and return valid one",
			objects: []client.Object{
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"bad-json", "kube-system", `not-json`),
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"empty-auths", "kube-system", `{"auths":{}}`),
				newACRSecretWrongType(acrImagePullSecretPrefix+"wrong-type", "kube-system", `{"auths":{"registry.example.com":{}}}`),
				newACRSecretMissingDockerConfigKey(acrImagePullSecretPrefix+"missing-key", "kube-system"),
				newACRDockerConfigSecret(acrImagePullSecretPrefix+"ok", "kube-system", `{"auths":{"registry.example.com":{}}}`),
			},
			want: corev1.LocalObjectReference{Name: acrImagePullSecretPrefix + "ok"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewWithT(t)

			cli := tt.cli
			if cli == nil {
				cli = buildFakeClient(tt.objects...)
			}

			got, err := getACKACRImagePullSecret(ctx, cli)
			if tt.wantErr {
				g.Expect(err).To(HaveOccurred())
				if tt.errContains != "" {
					g.Expect(err.Error()).To(ContainSubstring(tt.errContains))
				}
				return
			}

			g.Expect(err).Should(Succeed())
			g.Expect(got).To(Equal(tt.want))
		})
	}
}

func newACRDockerConfigSecret(name, namespace, dockerConfigJSON string) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Type: corev1.SecretTypeDockerConfigJson,
		Data: map[string][]byte{
			corev1.DockerConfigJsonKey: []byte(dockerConfigJSON),
		},
	}
}

func newACRSecretMissingDockerConfigKey(name, namespace string) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Type: corev1.SecretTypeDockerConfigJson,
		Data: map[string][]byte{},
	}
}

func newACRSecretWrongType(name, namespace, dockerConfigJSON string) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Type: corev1.SecretTypeOpaque,
		Data: map[string][]byte{
			corev1.DockerConfigJsonKey: []byte(dockerConfigJSON),
		},
	}
}
