package dataplane

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/assert/yaml"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	k8sscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	"k8s.io/utils/ptr"

	dmpbv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
)

func init() {
	_ = v1alpha1.AddToScheme(k8sscheme.Scheme)
}

// generateTestKubeconfig generates a base64 encoded kubeconfig for testing
func generateTestKubeconfig(clusterName string) string {
	config := clientcmdapi.Config{
		Clusters: map[string]*clientcmdapi.Cluster{
			clusterName: {
				Server: "https://test-cluster:6443",
				// CertificateAuthorityData: []byte("test-ca-data"),
			},
		},
		Contexts: map[string]*clientcmdapi.Context{
			clusterName: {
				Cluster:  clusterName,
				AuthInfo: "test-user",
			},
		},
		CurrentContext: clusterName,
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
			"test-user": {
				Token: "test-token",
			},
		},
	}

	data, _ := clientcmd.Write(config)
	return base64.StdEncoding.EncodeToString(data)
}

func TestDataPlaneService_buildKongAuditLogConfigData(t *testing.T) {
	tests := []struct {
		name     string
		provider string
		req      *dmpbv1.SetKongAuditLogSettingRequest
		wantErr  bool
		validate func(t *testing.T, configStr string)
	}{
		{
			name:     "valid config with AWS provider",
			provider: consts.ProviderTypeAWS,
			req: &dmpbv1.SetKongAuditLogSettingRequest{
				Enabled:     true,
				MaxSize:     100,
				MaxLifetime: ptr.To(int32(7)),
				BucketUrl:   "s3://test-bucket",
				Region:      "us-west-2",
				ProviderIdentity: &dmpbv1.SetKongAuditLogSettingRequest_ProviderIdentity{
					CloudRoleIdentity: "arn:aws:iam::123456789012:role/test-role",
					ExternalId:        "test-external-id",
				},
			},
			wantErr: false,
			validate: func(t *testing.T, configStr string) {
				var config map[string]interface{}
				err := yaml.Unmarshal([]byte(configStr), &config)
				require.NoError(t, err)
				assert.Equal(t, true, config["enableLog"])
				assert.Equal(t, 100, config["maxSize"])
				assert.Equal(t, 7, config["maxLifetime"])
				assert.Equal(t, "s3://test-bucket", config["bucketURL"])
				assert.Equal(t, "us-west-2", config["region"])
				assert.Equal(t, "/var/log/auditlog", config["logDir"])
				assert.Equal(t, "ops-audit.log", config["auditLogFileName"])
				assert.Equal(t, 20, config["gracefulStopWaitSeconds"])
				assert.Equal(t, 9080, config["producerPort"])

				awsConfig, ok := config["aws"].(map[string]interface{})
				require.True(t, ok)
				assert.Equal(t, "arn:aws:iam::123456789012:role/test-role", awsConfig["roleARN"])
				assert.Equal(t, "test-external-id", awsConfig["externalID"])
			},
		},
		{
			name:     "config without provider identity",
			provider: consts.ProviderTypeAWS,
			req: &dmpbv1.SetKongAuditLogSettingRequest{
				Enabled:          true,
				MaxSize:          50,
				MaxLifetime:      ptr.To(int32(3)),
				BucketUrl:        "s3://minimal-bucket",
				Region:           "ap-south-1",
				ProviderIdentity: nil,
			},
			wantErr: false,
			validate: func(t *testing.T, configStr string) {
				var config map[string]interface{}
				err := yaml.Unmarshal([]byte(configStr), &config)
				require.NoError(t, err)
				assert.Equal(t, true, config["enableLog"])
				assert.Equal(t, 50, config["maxSize"])
				assert.Equal(t, 3, config["maxLifetime"])
				_, hasAWS := config["aws"]
				assert.False(t, hasAWS, "should not have AWS config when provider identity is nil")
			},
		},
		{
			name:     "valid config with AKS provider and SAS token",
			provider: consts.ProviderTypeAKS,
			req: &dmpbv1.SetKongAuditLogSettingRequest{
				Enabled:     true,
				MaxSize:     100,
				MaxLifetime: ptr.To(int32(7)),
				BucketUrl:   "https://account.blob.core.windows.net/container/prefix",
				Region:      "eastus",
				ProviderIdentity: &dmpbv1.SetKongAuditLogSettingRequest_ProviderIdentity{
					CloudRoleIdentity: "sp=acw&sig=test-sas-token",
				},
			},
			wantErr: false,
			validate: func(t *testing.T, configStr string) {
				var config map[string]interface{}
				err := yaml.Unmarshal([]byte(configStr), &config)
				require.NoError(t, err)
				assert.Equal(t, "https://account.blob.core.windows.net/container/prefix", config["bucketURL"])
				assert.Equal(t, "eastus", config["region"])

				azureConfig, ok := config["azure"].(map[string]interface{})
				require.True(t, ok)
				assert.Equal(t, "sp=acw&sig=test-sas-token", azureConfig["sasToken"])
			},
		},
		{
			name:     "valid config with AKS provider and managed identity",
			provider: consts.ProviderTypeAKS,
			req: &dmpbv1.SetKongAuditLogSettingRequest{
				Enabled:     true,
				MaxSize:     100,
				MaxLifetime: ptr.To(int32(7)),
				BucketUrl:   "https://account.blob.core.windows.net/container/prefix",
				Region:      "eastus",
				ProviderIdentity: &dmpbv1.SetKongAuditLogSettingRequest_ProviderIdentity{
					CloudRoleIdentity: "",
				},
			},
			wantErr: false,
			validate: func(t *testing.T, configStr string) {
				var config map[string]interface{}
				err := yaml.Unmarshal([]byte(configStr), &config)
				require.NoError(t, err)
				assert.Equal(t, "https://account.blob.core.windows.net/container/prefix", config["bucketURL"])
				assert.Equal(t, "eastus", config["region"])
				_, hasAzure := config["azure"]
				assert.False(t, hasAzure, "managed identity mode should not emit azure.sasToken")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svc := &DataPlaneService{
				config: rsconfig.RootConfig{
					ControllerConfig: rsconfig.ControllerConfig{
						Provider: tt.provider,
					},
				},
			}

			configStr, err := svc.buildKongAuditLogConfigData(tt.req, tt.req.BucketUrl)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}

			require.NoError(t, err)
			assert.NotEmpty(t, configStr)
			if tt.validate != nil {
				tt.validate(t, configStr)
			}
		})
	}
}

func TestDataPlaneService_buildKongAuditLogConfigData_InvalidProvider(t *testing.T) {
	svc := &DataPlaneService{
		config: rsconfig.RootConfig{
			ControllerConfig: rsconfig.ControllerConfig{
				Provider: "invalid-provider",
			},
		},
	}

	req := &dmpbv1.SetKongAuditLogSettingRequest{
		Enabled:     true,
		MaxSize:     100,
		MaxLifetime: ptr.To(int32(7)),
		BucketUrl:   "s3://test-bucket",
		Region:      "us-west-2",
		ProviderIdentity: &dmpbv1.SetKongAuditLogSettingRequest_ProviderIdentity{
			CloudRoleIdentity: "test-role",
			ExternalId:        "test-id",
		},
	}

	_, err := svc.buildKongAuditLogConfigData(req, req.BucketUrl)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "unsupported provider type")
}

func TestDataPlaneService_getDataPlaneK8sClient(t *testing.T) {
	tests := []struct {
		name       string
		kubeconfig string
		wantErr    bool
		errMsg     string
	}{
		{
			name:       "valid kubeconfig",
			kubeconfig: generateTestKubeconfig("test-cluster"),
			wantErr:    false,
		},
		{
			name:       "invalid base64",
			kubeconfig: "not-valid-base64!@#$",
			wantErr:    true,
			errMsg:     "failed to decode kubeconfig",
		},
		{
			name:       "invalid kubeconfig content",
			kubeconfig: base64.StdEncoding.EncodeToString([]byte("invalid yaml content")),
			wantErr:    true,
			errMsg:     "failed to get rest config from kubeconfig",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svc := &DataPlaneService{}
			cli, err := svc.getDataPlaneK8sClient(tt.kubeconfig)

			if tt.wantErr {
				assert.Error(t, err)
				assert.Nil(t, cli)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			require.NoError(t, err)
			assert.NotNil(t, cli)
		})
	}
}

func Test_serviceProxyPackage(t *testing.T) {
	config := &rsconfig.InfraComponentConfig{
		ReleaseName: "service-proxy",
		Namespace:   "tidb-admin",
		HelmValues: map[string]interface{}{
			"config": map[string]interface{}{
				"auditLog": true,
			},
		},
	}
	values := map[string]interface{}{
		"auditSidecar": map[string]interface{}{
			"config": map[string]interface{}{
				"bucketURL": "s3://test-bucket",
				"region":    "us-west-2",
			},
		},
	}

	pkg := &serviceProxyPackage{
		config: config,
		values: values,
	}

	t.Run("GetName", func(t *testing.T) {
		assert.Equal(t, "service-proxy", pkg.GetName())
	})

	t.Run("GetNamespace", func(t *testing.T) {
		assert.Equal(t, "tidb-admin", pkg.GetNamespace())
	})

	t.Run("GetCRDs", func(t *testing.T) {
		crds, err := pkg.GetCRDs()
		assert.NoError(t, err)
		assert.Nil(t, crds)
	})

	t.Run("ToHelmValues", func(t *testing.T) {
		result := pkg.ToHelmValues()
		assert.Equal(t, values, result)
		auditSidecar, ok := result["auditSidecar"].(map[string]interface{})
		require.True(t, ok)
		auditSidecarConfig, ok := auditSidecar["config"].(map[string]interface{})
		require.True(t, ok)
		assert.Equal(t, "s3://test-bucket", auditSidecarConfig["bucketURL"])
		assert.Equal(t, "us-west-2", auditSidecarConfig["region"])
	})
}

func TestDataPlaneService_updateServiceProxyHelmRelease(t *testing.T) {
	tests := []struct {
		name        string
		tmplConfig  rsconfig.TemplatesConfig
		k8sVersion  string
		kubeconfig  string
		bucketURL   string
		dataplaneID string
		req         *dmpbv1.SetKongAuditLogSettingRequest
		wantErr     bool
		errContains string
	}{
		{
			name:       "template not found - empty template config",
			tmplConfig: rsconfig.TemplatesConfig{},
			k8sVersion: "1.28",
			kubeconfig: generateTestKubeconfig("test-cluster"),
			bucketURL:  "s3://test-bucket",
			req: &dmpbv1.SetKongAuditLogSettingRequest{
				Enabled: true,
				Region:  "us-west-2",
			},
			wantErr:     true,
			errContains: "dedicated template  not found for version 1.28",
		},
		{
			name: "template found but kubeconfig invalid",
			tmplConfig: rsconfig.TemplatesConfig{
				Dedicated: []rsconfig.DedicatedTemplateConfig{
					{
						TemplateConfigMeta: rsconfig.TemplateConfigMeta{
							TemplateId:        "default",
							Default:           true,
							SupportedVersions: []string{"1.28"},
						},
						ServiceProxy: rsconfig.InfraComponentConfig{
							ReleaseName: "service-proxy",
							Namespace:   "tidb-admin",
							HelmValues: map[string]interface{}{
								"config": map[string]interface{}{
									"auditLog": false,
								},
							},
						},
					},
				},
			},
			k8sVersion:  "1.28",
			kubeconfig:  "invalid-base64!@#$",
			bucketURL:   "s3://test-bucket",
			dataplaneID: "dp-123",
			req: &dmpbv1.SetKongAuditLogSettingRequest{
				Enabled: true,
				Region:  "us-west-2",
			},
			wantErr:     true,
			errContains: "failed to create kubeconfig client getter",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svc := &DataPlaneService{
				config: rsconfig.RootConfig{
					ControllerConfig: rsconfig.ControllerConfig{
						APPBoxConfig: rsconfig.APPBoxConfig{
							K8sVersion: tt.k8sVersion,
						},
					},
				},
				tmplConfig: tt.tmplConfig,
			}

			err := svc.updateServiceProxyHelmRelease(
				context.Background(),
				tt.kubeconfig,
				tt.bucketURL,
				tt.dataplaneID,
				tt.req,
			)

			if tt.wantErr {
				assert.Error(t, err)
				if tt.errContains != "" {
					assert.Contains(t, err.Error(), tt.errContains)
				}
				return
			}

			require.NoError(t, err)
		})
	}
}
