package k8scliv2

import (
	"context"
	"encoding/base64"
	"testing"

	ackv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ack/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/rest"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/observability/agent-control-server/pkg/testkits"
)

func TestBuildACKKubeconfig(t *testing.T) {
	newACK := func(name string) *ackv1alpha1.ManagedKubernetes {
		return &ackv1alpha1.ManagedKubernetes{ObjectMeta: metav1.ObjectMeta{Name: name}}
	}

	server := "https://1.2.3.4"
	id := "c-123456"
	certRaw := []byte("dummy-cert-bytes")
	cert := base64.StdEncoding.EncodeToString(certRaw)

	tests := []struct {
		name         string
		mutate       func(*ackv1alpha1.ManagedKubernetes) string
		wantErr      bool
		errMsg       string
		verifyConfig func(*testing.T, *clientcmdapi.Config)
	}{
		{
			name: "uses insecure tls when ca data is missing",
			mutate: func(a *ackv1alpha1.ManagedKubernetes) string {
				a.Status.AtProvider.Connections = map[string]*string{"api_server_internet": &server}
				a.Status.AtProvider.ID = &id
				return ""
			},
			verifyConfig: func(t *testing.T, cfg *clientcmdapi.Config) {
				cluster := cfg.Clusters["ack-demo"]
				assert.NotNil(t, cluster)
				assert.Equal(t, server, cluster.Server)
				assert.True(t, cluster.InsecureSkipTLSVerify)
			},
		},
		{
			name: "decodes base64 ca data",
			mutate: func(a *ackv1alpha1.ManagedKubernetes) string {
				a.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": &cert}
				a.Status.AtProvider.Connections = map[string]*string{"api_server_internet": &server}
				a.Status.AtProvider.ID = &id
				return ""
			},
			verifyConfig: func(t *testing.T, cfg *clientcmdapi.Config) {
				cluster := cfg.Clusters["ack-demo"]
				assert.NotNil(t, cluster)
				assert.Equal(t, server, cluster.Server)
				assert.False(t, cluster.InsecureSkipTLSVerify)
				assert.Equal(t, certRaw, cluster.CertificateAuthorityData)
			},
		},
		{
			name: "returns error for invalid base64 ca data",
			mutate: func(a *ackv1alpha1.ManagedKubernetes) string {
				bad := "!!notbase64"
				a.Status.AtProvider.CertificateAuthority = map[string]*string{"cluster_cert": &bad}
				a.Status.AtProvider.Connections = map[string]*string{"api_server_internet": &server}
				a.Status.AtProvider.ID = &id
				return ""
			},
			wantErr: true,
			errMsg:  "failed to decode ack ca data",
		},
		{
			name: "returns error when public endpoint is missing",
			mutate: func(a *ackv1alpha1.ManagedKubernetes) string {
				a.Status.AtProvider.ID = &id
				return ""
			},
			wantErr: true,
			errMsg:  "no public endpoint",
		},
		{
			name: "returns error when cluster id is missing",
			mutate: func(a *ackv1alpha1.ManagedKubernetes) string {
				a.Status.AtProvider.Connections = map[string]*string{"api_server_internet": &server}
				return ""
			},
			wantErr: true,
			errMsg:  "no cluster id",
		},
		{
			name: "appends role arn arg when assume role is set",
			mutate: func(a *ackv1alpha1.ManagedKubernetes) string {
				a.Status.AtProvider.Connections = map[string]*string{"api_server_internet": &server}
				a.Status.AtProvider.ID = &id
				return "acs:ram::123456789012:role/MyACKRole"
			},
			verifyConfig: func(t *testing.T, cfg *clientcmdapi.Config) {
				auth := cfg.AuthInfos["ack-demo"]
				assert.NotNil(t, auth)
				assert.NotNil(t, auth.Exec)
				assert.Contains(t, auth.Exec.Args, "--role-arn")
				assert.Contains(t, auth.Exec.Args, "acs:ram::123456789012:role/MyACKRole")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ack := newACK("ack-demo")
			assume := tt.mutate(ack)

			cfg, err := buildACKKubeconfig(ack, assume)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			if tt.verifyConfig != nil {
				tt.verifyConfig(t, cfg)
			}
		})
	}
}

func TestACKClientManager_GetConfig(t *testing.T) {
	server := "https://49.0.0.1"
	id := "c-999"

	tests := []struct {
		name            string
		withProviderRef bool
		providerAccount string
		roleName        string
		wantErr         bool
		errMsg          string
		verify          func(*testing.T, *rest.Config)
	}{
		{
			name:            "appends role arn when provider ref and role name are set",
			withProviderRef: true,
			providerAccount: "123456789012",
			roleName:        "MyACKRole",
			verify: func(t *testing.T, cfg *rest.Config) {
				assert.NotNil(t, cfg)
				assert.Equal(t, server, cfg.Host)
				assert.NotNil(t, cfg.ExecProvider)
				assert.Contains(t, cfg.ExecProvider.Args, "--role-arn")
			},
		},
		{
			name:            "does not append role arn when assume role is disabled",
			withProviderRef: false,
			roleName:        "",
			verify: func(t *testing.T, cfg *rest.Config) {
				assert.NotNil(t, cfg)
				assert.Equal(t, server, cfg.Host)
				assert.NotNil(t, cfg.ExecProvider)
				assert.NotContains(t, cfg.ExecProvider.Args, "--role-arn")
			},
		},
		{
			name:    "returns error when cluster is not found",
			wantErr: true,
			errMsg:  "not found",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if !tt.wantErr {
				ack := &ackv1alpha1.ManagedKubernetes{ObjectMeta: metav1.ObjectMeta{Name: "ack-demo"}}
				ack.Status.AtProvider.Connections = map[string]*string{"api_server_internet": &server}
				ack.Status.AtProvider.ID = &id
				if tt.withProviderRef {
					ack.Spec.ProviderConfigReference = &xpv1.Reference{Name: tt.providerAccount}
				}
				objs = append(objs, ack)
			}

			fakeCli := testkits.BuildClient(objs...)
			manager := NewACKTenantClientManager(fakeCli, client.Options{}, tt.roleName)
			restCfg, err := manager.GetConfig(context.Background(), client.ObjectKey{Name: "ack-demo"})
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			if tt.verify != nil {
				tt.verify(t, restCfg)
			}
		})
	}
}

func TestAckTenantClientManager_GetClient(t *testing.T) {
	server := "https://49.0.0.1"
	id := "c-999"
	clusterName := "ack-demo"

	tests := []struct {
		name    string
		ack     *ackv1alpha1.ManagedKubernetes
		wantErr bool
		errMsg  string
	}{
		{
			name: "returns client or exec plugin error for valid config",
			ack: &ackv1alpha1.ManagedKubernetes{
				ObjectMeta: metav1.ObjectMeta{Name: clusterName},
				Status: ackv1alpha1.ManagedKubernetesStatus{
					AtProvider: ackv1alpha1.ManagedKubernetesObservation{
						Connections: map[string]*string{"api_server_internet": &server},
						ID:          &id,
					},
				},
			},
			wantErr: false,
		},
		{
			name:    "returns error when config lookup fails",
			ack:     nil,
			wantErr: true,
			errMsg:  "not found",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if tt.ack != nil {
				objs = append(objs, tt.ack)
			}

			fakeCli := testkits.BuildClient(objs...)
			manager := NewACKTenantClientManager(fakeCli, client.Options{}, "MyACKRole")

			ackKey := client.ObjectKey{Name: clusterName}
			if tt.ack == nil {
				ackKey = client.ObjectKey{Name: "non-existent"}
			}

			k8sClient, err := manager.GetClient(context.Background(), ackKey)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				assert.Nil(t, k8sClient)
				return
			}

			if err != nil {
				assert.Contains(t, err.Error(), "exec plugin")
				return
			}
			assert.NotNil(t, k8sClient)
		})
	}
}

func TestAckTenantClientManager_GetMetricsClient(t *testing.T) {
	server := "https://49.0.0.1"
	id := "c-999"
	clusterName := "ack-demo"

	tests := []struct {
		name    string
		ack     *ackv1alpha1.ManagedKubernetes
		wantErr bool
		errMsg  string
	}{
		{
			name: "returns metrics client or exec plugin error for valid config",
			ack: &ackv1alpha1.ManagedKubernetes{
				ObjectMeta: metav1.ObjectMeta{Name: clusterName},
				Status: ackv1alpha1.ManagedKubernetesStatus{
					AtProvider: ackv1alpha1.ManagedKubernetesObservation{
						Connections: map[string]*string{"api_server_internet": &server},
						ID:          &id,
					},
				},
			},
			wantErr: false,
		},
		{
			name:    "returns error when metrics config lookup fails",
			ack:     nil,
			wantErr: true,
			errMsg:  "not found",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if tt.ack != nil {
				objs = append(objs, tt.ack)
			}

			fakeCli := testkits.BuildClient(objs...)
			manager := NewACKTenantClientManager(fakeCli, client.Options{}, "MyACKRole")

			ackKey := client.ObjectKey{Name: clusterName}
			if tt.ack == nil {
				ackKey = client.ObjectKey{Name: "non-existent"}
			}

			metricsClient, err := manager.GetMetricsClient(context.Background(), ackKey)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				assert.Nil(t, metricsClient)
				return
			}

			if err != nil {
				assert.Contains(t, err.Error(), "exec plugin")
				return
			}
			assert.NotNil(t, metricsClient)
		})
	}
}
