package k8s

import (
	"encoding/base64"
	"strings"
	"testing"

	ackv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ack/v1alpha1"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

func TestBuildACKKubeconfig(t *testing.T) {
	g := NewWithT(t)

	strPtr := func(s string) *string { return &s }

	validCertRaw := []byte("test-cert")
	validCertEncoded := base64.StdEncoding.EncodeToString(validCertRaw)

	serverURL := "https://1.2.3.4:6443"
	clusterID := "c1234567890"
	roleARN := "acs:ram::123456789012:role/TestRole"

	newBase := func() *ackv1alpha1.ManagedKubernetes {
		return &ackv1alpha1.ManagedKubernetes{
			ObjectMeta: metav1.ObjectMeta{Name: "test-ack"},
		}
	}

	tests := []struct {
		name      string
		mutate    func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string)
		expectErr string
		verify    func(cfg *clientcmdapi.Config)
	}{
		{
			name: "success with cert and role arn",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": strPtr(validCertEncoded)}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": strPtr(serverURL)}
				return ack, roleARN
			},
			verify: func(cfg *clientcmdapi.Config) {
				g.Expect(cfg).NotTo(BeNil())
				g.Expect(cfg.CurrentContext).To(Equal("test-ack"))
				cluster := cfg.Clusters["test-ack"]
				g.Expect(cluster).NotTo(BeNil())
				g.Expect(cluster.Server).To(Equal(serverURL))
				g.Expect(cluster.InsecureSkipTLSVerify).To(BeFalse())
				g.Expect(cluster.CertificateAuthorityData).To(Equal(validCertRaw))
				auth := cfg.AuthInfos["test-ack"]
				g.Expect(auth.Exec).NotTo(BeNil())
				g.Expect(auth.Exec.APIVersion).To(Equal(v1execConfigAPIVersionV1beta1))
				g.Expect(auth.Exec.Command).To(Equal("ack-ram-tool"))
				g.Expect(auth.Exec.Args).To(Equal([]string{"credential-plugin", "get-token", "--cluster-id", clusterID, "--api-version", "v1beta1", "--log-level", "error", "--role-arn", roleARN}))
			},
		},
		{
			name: "error missing certificate authority map",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				// no certificate authority map set
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": strPtr(serverURL)}
				return ack, ""
			},
			expectErr: "cluster CA certificate is required but not found or empty",
		},
		{
			name: "error certificate key present but nil value",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": nil}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": strPtr(serverURL)}
				return ack, ""
			},
			expectErr: "cluster CA certificate is required but not found or empty",
		},
		{
			name: "error certificate empty string",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				emptyStr := ""
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": &emptyStr}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": strPtr(serverURL)}
				return ack, ""
			},
			expectErr: "cluster CA certificate is required but not found or empty",
		},
		{
			name: "error invalid base64 cert",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				invalid := "not-base64-***"
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": &invalid}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": strPtr(serverURL)}
				return ack, ""
			},
			expectErr: "failed to decode ack ca data",
		},
		{
			name: "error no public endpoint (connections map empty)",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": strPtr(validCertEncoded)}
				ack.Status.AtProvider.Connections = map[string]*string{}
				return ack, ""
			},
			expectErr: "no public endpoint found for ack",
		},
		{
			name: "error endpoint key present but value nil",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				ack.Status.AtProvider.ID = strPtr(clusterID)
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": strPtr(validCertEncoded)}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": nil}
				return ack, ""
			},
			expectErr: "no public endpoint found for ack",
		},
		{
			name: "error no cluster id",
			mutate: func(ack *ackv1alpha1.ManagedKubernetes) (*ackv1alpha1.ManagedKubernetes, string) {
				// missing ID
				ack.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": strPtr(validCertEncoded)}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": strPtr(serverURL)}
				return ack, ""
			},
			expectErr: "no cluster id found",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ack := newBase()
			ack, role := tt.mutate(ack)
			cfg, err := BuildACKKubeconfig(ack, role)
			if tt.expectErr != "" {
				g.Expect(err).To(HaveOccurred())
				g.Expect(err.Error()).To(ContainSubstring(tt.expectErr))
				g.Expect(cfg).To(BeNil())
				return
			}
			g.Expect(err).To(Not(HaveOccurred()))
			g.Expect(cfg).NotTo(BeNil())
			// common asserts for successful cases
			cluster := cfg.Clusters[ack.Name]
			g.Expect(cluster).NotTo(BeNil())
			g.Expect(cluster.Server).To(Equal(serverURL))
			auth := cfg.AuthInfos[ack.Name]
			g.Expect(auth).NotTo(BeNil())
			g.Expect(auth.Exec).NotTo(BeNil())
			g.Expect(auth.Exec.Args[0:3]).To(Equal([]string{"credential-plugin", "get-token", "--cluster-id"}))
			g.Expect(auth.Exec.Args[3]).To(Equal(clusterID))
			g.Expect(auth.Exec.Command).To(Equal("ack-ram-tool"))
			if strings.Contains(tt.name, "role arn") {
				g.Expect(auth.Exec.Args[len(auth.Exec.Args)-2:]).To(Equal([]string{"--role-arn", roleARN}))
			}
			if tt.verify != nil {
				tt.verify(cfg)
			}
		})
	}
}
