package task

import (
	"context"
	"testing"
	"text/template"

	"github.com/stretchr/testify/assert"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/pkg/config"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
	"github.com/tidbcloud/observability/agent-control-server/pkg/testkits"
)

func makeVectorTask(agent *agentv2.TiDBGroupAgent, opts testkits.TaskOptions) *VectorTask {
	objs := []client.Object{agent}
	objs = append(objs, opts.LocalObjects...)
	ctx := &reconciler.Context[*agentv2.TiDBGroupAgent]{
		Agent:     agent,
		Local:     testkits.BuildClient(objs...),
		Remote:    testkits.NewClientGetterWithOpts(opts),
		Config:    &opts.Config,
		Templates: opts.Templates,
	}
	return NewVectorTask(ctx)
}

func baseVectorConfig() config.RootConfig {
	return config.RootConfig{
		O11yConfig: config.ObservabilityInfra{
			Vendor: "aws",
			CustomerConfigsV2: config.CustomerConfigsV2{
				NextGen: config.CustomerNextGen{
					TiDBGroup: config.CustomerNextGenTiDBGroup{
						Default: config.CustomerAgents{
							Vector: &config.VectorConfig{
								Chart: &config.CustomerHelmChart{
									Name:    "vector",
									Version: "1.0.0",
								},
								Image: "vectorio/vector:latest",
							},
						},
						Specs: config.CustomerAgentSpecs{
							{
								Name:   "default",
								Vector: &corev1.ResourceRequirements{},
							},
						},
					},
				},
			},
		},
	}
}

func TestVectorTask(t *testing.T) {
	tests := []struct {
		name   string
		verify func(t *testing.T)
	}{
		{
			name: "Name",
			verify: func(t *testing.T) {
				task := &VectorTask{}
				assert.Equal(t, "Vector", task.Name())
			},
		},
		{
			name: "ClusterMeta",
			verify: func(t *testing.T) {
				agent := testkits.NewTiDBGroupAgentBuilder().Build()
				task := makeVectorTask(agent, testkits.TaskOptions{})
				meta := task.ClusterMeta()

				assert.Equal(t, consts.AgentTidbGroup, meta.Agent)
				assert.Equal(t, "test-cluster", meta.K8sClusterName)
				assert.Equal(t, "tenant-1", meta.TenantID)
				assert.Equal(t, "app-1", meta.ApplicationID)
				assert.Equal(t, "tidbcloud", meta.BizType)
			},
		},
		{
			name: "buildName",
			verify: func(t *testing.T) {
				agent := testkits.NewTiDBGroupAgentBuilder().Build()
				task := makeVectorTask(agent, testkits.TaskOptions{})
				assert.Equal(t, "o11y-vector-tidb-group-123", task.buildName())
			},
		},
		{
			name: "buildNamespace",
			verify: func(t *testing.T) {
				agent := testkits.NewTiDBGroupAgentBuilder().Build()
				task := makeVectorTask(agent, testkits.TaskOptions{})
				assert.Equal(t, "tidbpool-456", task.buildNamespace())
			},
		},
		{
			name: "Role",
			verify: func(t *testing.T) {
				task := makeVectorTask(testkits.NewTiDBGroupAgentBuilder().Build(), testkits.TaskOptions{})

				role, err := task.buildRole()
				assert.NoError(t, err)
				assert.Equal(t, "o11y-vector-tidb-group-123", role.Name)
				assert.Equal(t, "tidbpool-456", role.Namespace)
				assert.Len(t, role.Rules, 1)
				assert.Equal(t, []string{""}, role.Rules[0].APIGroups)
				assert.Equal(t, []string{"pods"}, role.Rules[0].Resources)
				assert.Equal(t, []string{"get", "list", "watch"}, role.Rules[0].Verbs)
			},
		},
		{
			name: "RoleBinding",
			verify: func(t *testing.T) {
				task := makeVectorTask(testkits.NewTiDBGroupAgentBuilder().Build(), testkits.TaskOptions{})

				binding, err := task.buildRoleBinding()
				assert.NoError(t, err)
				assert.Len(t, binding.Subjects, 1)
				assert.Equal(t, "ServiceAccount", binding.Subjects[0].Kind)
				assert.Equal(t, "o11y-vector-tidb-group-123", binding.Subjects[0].Name)
				assert.Equal(t, "Role", binding.RoleRef.Kind)
				assert.Equal(t, "o11y-vector-tidb-group-123", binding.RoleRef.Name)
				assert.Equal(t, "rbac.authorization.k8s.io", binding.RoleRef.APIGroup)
			},
		},
	}

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

func TestVectorTask_Create(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.TiDBGroupAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:  "create fails with remote client error",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				ClientError: true,
				Config:      baseVectorConfig(),
			},
			wantErr: true,
		},
		{
			name:  "create fails with missing template",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				Client: testkits.BuildClient(),
				Config: baseVectorConfig(),
			},
			wantErr: true,
		},
		{
			name:  "create fails with alicloud vendor when secret not found",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: func() testkits.TaskOptions {
				cfg := baseVectorConfig()
				cfg.O11yConfig.Vendor = "alicloud"
				return testkits.TaskOptions{
					Client: testkits.BuildClient(),
					Config: cfg,
				}
			}(),
			wantErr: true,
		},
		{
			name: "create fails when spec not found",
			agent: testkits.NewTiDBGroupAgentBuilder().
				WithAnnotations(map[string]string{consts.AnnotationKeyO11yDesiredSpec: "nonexistent"}).
				Build(),
			opts: func() testkits.TaskOptions {
				cfg := baseVectorConfig()
				cfg.O11yConfig.CustomerConfigsV2.NextGen.TiDBGroup.Specs = nil
				return testkits.TaskOptions{
					ClientError: true,
					Config:      cfg,
				}
			}(),
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			task := makeVectorTask(tt.agent, tt.opts)
			result := task.Create(context.Background())
			if tt.wantErr {
				assert.Error(t, result.Error())
			} else {
				assert.NoError(t, result.Error())
			}
		})
	}
}

func TestVectorTask_PostCreate(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.TiDBGroupAgent
		opts    testkits.TaskOptions
		wantErr bool
		verify  func(t *testing.T, opts testkits.TaskOptions)
	}{
		{
			name:    "post create fails with remote client error",
			agent:   testkits.NewTiDBGroupAgentBuilder().Build(),
			opts:    testkits.TaskOptions{ClientError: true},
			wantErr: true,
		},
		{
			name:  "post create fails when statefulset not ready",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				Client: testkits.BuildClient(),
			},
			wantErr: true,
		},
		{
			name:  "post create succeeds when statefulset ready",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				Client: testkits.BuildClient(
					&appsv1.StatefulSet{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "o11y-vector-tidb-group-123",
							Namespace: "tidbpool-456",
						},
						Spec:   appsv1.StatefulSetSpec{Replicas: ptr.To(int32(1))},
						Status: appsv1.StatefulSetStatus{AvailableReplicas: 1},
					},
					&corev1.PersistentVolumeClaim{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "data-o11y-vector-tidb-group-123-0",
							Namespace: "tidbpool-456",
						},
					},
				),
			},
			wantErr: false,
			verify: func(t *testing.T, opts testkits.TaskOptions) {
				pvc := &corev1.PersistentVolumeClaim{}
				err := opts.Client.Get(context.Background(), types.NamespacedName{
					Name:      "data-o11y-vector-tidb-group-123-0",
					Namespace: "tidbpool-456",
				}, pvc)
				assert.NoError(t, err)
				assert.Equal(t, consts.TagValueComponentO11y, pvc.Labels[consts.TagKeyComponent])
				assert.Equal(t, "tenant-1", pvc.Labels[consts.TagKeyTenant])
				assert.Equal(t, "pool-456", pvc.Labels[consts.TagKeyResourcePool])
				assert.Equal(t, "cluster-1", pvc.Labels[consts.TagKeyCluster])
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			task := makeVectorTask(tt.agent, tt.opts)
			result := task.PostCreate(context.Background())
			if tt.wantErr {
				assert.Error(t, result.Error())
			} else {
				assert.NoError(t, result.Error())
			}
			if tt.verify != nil {
				tt.verify(t, tt.opts)
			}
		})
	}
}

func TestVectorTask_Delete(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.TiDBGroupAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:  "delete fails with remote client error",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				ClientError: true,
				Config:      baseVectorConfig(),
			},
			wantErr: true,
		},
		{
			name:  "delete fails with helm error",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				HelmError: true,
				Client:    testkits.BuildClient(),
				Config:    baseVectorConfig(),
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			task := makeVectorTask(tt.agent, tt.opts)
			result := task.Delete(context.Background())
			if tt.wantErr {
				assert.Error(t, result.Error())
			} else {
				assert.NoError(t, result.Error())
			}
		})
	}
}

func TestVectorTask_PostDelete(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.TiDBGroupAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:    "post delete fails with remote client error",
			agent:   testkits.NewTiDBGroupAgentBuilder().Build(),
			opts:    testkits.TaskOptions{ClientError: true},
			wantErr: true,
		},
		{
			name:  "post delete succeeds when statefulset deleted",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				Client: testkits.BuildClient(),
			},
			wantErr: false,
		},
		{
			name:  "post delete fails when statefulset still exists",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				Client: testkits.BuildClient(
					&appsv1.StatefulSet{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "o11y-vector-tidb-group-123",
							Namespace: "tidbpool-456",
						},
					},
				),
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			task := makeVectorTask(tt.agent, tt.opts)
			result := task.PostDelete(context.Background())
			if tt.wantErr {
				assert.Error(t, result.Error())
			} else {
				assert.NoError(t, result.Error())
			}
		})
	}
}

func TestVectorTask_build(t *testing.T) {
	tmpl, err := template.New("test").Parse(`
secretName: {{.SecretName}}
sharedPoolID: {{.SharedPoolID}}
image: {{.ImageRepo}}:{{.ImageTag}}
accessKey: {{.AccessKeyID}}
secretKey: {{.SecretAccessKey}}
tenantID: {{.TenantID}}
clusterID: {{.ClusterID}}
vmAccountID: {{.VMAccountID}}
vmProjectID: {{.VMProjectID}}
remoteWriteURL: {{.RemoteWriteURL}}
region: {{.Region}}
bucket: {{.Bucket}}
roleARN: {{.RoleARN}}
`)
	assert.NoError(t, err)
	tests := []struct {
		name        string
		agent       *agentv2.TiDBGroupAgent
		opts        testkits.TaskOptions
		wantErr     bool
		errMsg      string
		outContains []string
	}{
		{
			name:  "build succeeds with aws vendor",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: testkits.TaskOptions{
				Config:    baseVectorConfig(),
				Templates: config.Templates{"tidbgroup/vector-values-aws.yaml": tmpl},
			},
			outContains: []string{"o11y-vector-tidb-group-123", "pool-456"},
		},
		{
			name:    "build fails with missing template",
			agent:   testkits.NewTiDBGroupAgentBuilder().Build(),
			opts:    testkits.TaskOptions{Config: baseVectorConfig()},
			wantErr: true,
		},
		{
			name:  "build fails with alicloud vendor when secret not found",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: func() testkits.TaskOptions {
				cfg := baseVectorConfig()
				cfg.O11yConfig.Vendor = "alicloud"
				return testkits.TaskOptions{
					Config:    cfg,
					Templates: config.Templates{"tidbgroup/vector-values-alicloud.yaml": tmpl},
				}
			}(),
			wantErr: true,
			errMsg:  "secrets",
		},
		{
			name:  "build succeeds with alicloud vendor when secret exists",
			agent: testkits.NewTiDBGroupAgentBuilder().Build(),
			opts: func() testkits.TaskOptions {
				cfg := baseVectorConfig()
				cfg.O11yConfig.Vendor = "alicloud"
				secret := &corev1.Secret{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "o11y-oss-secret",
						Namespace: "observability",
					},
					Data: map[string][]byte{
						"access_key": []byte("test-ak"),
						"secret_key": []byte("test-sk"),
					},
				}
				return testkits.TaskOptions{
					Config:       cfg,
					Templates:    config.Templates{"tidbgroup/vector-values-alicloud.yaml": tmpl},
					LocalObjects: []client.Object{secret},
				}
			}(),
			outContains: []string{"test-ak", "test-sk"},
		},
		{
			name: "build fails when spec not found",
			agent: testkits.NewTiDBGroupAgentBuilder().
				WithAnnotations(map[string]string{consts.AnnotationKeyO11yDesiredSpec: "nonexistent"}).
				Build(),
			opts: func() testkits.TaskOptions {
				cfg := baseVectorConfig()
				cfg.O11yConfig.CustomerConfigsV2.NextGen.TiDBGroup.Specs = nil
				return testkits.TaskOptions{
					Config:    cfg,
					Templates: config.Templates{"tidbgroup/vector-values-aws.yaml": tmpl},
				}
			}(),
			wantErr: true,
			errMsg:  "desired spec not found",
		},
		{
			name: "build with custom annotations",
			agent: testkits.NewTiDBGroupAgentBuilder().
				WithAnnotations(map[string]string{
					consts.AnnotationKeyO11yVMAccountID:    "vm-account-123",
					consts.AnnotationKeyO11yVMProjectID:    "vm-project-456",
					consts.AnnotationKeyO11yServiceDNSName: "o11y-service.example.com",
					consts.AnnotationKeyO11yID:             "o11y-id-789",
					consts.AnnotationKeyO11yLogBucketName:  "log-bucket-name",
					consts.AnnotationKeyO11yAgentRoleArn:   "arn:aws:iam::123456789:role/agent-role",
					consts.AnnotationKeyO11yDesiredSpec:    "default",
				}).
				Build(),
			opts: func() testkits.TaskOptions {
				cfg := baseVectorConfig()
				cfg.O11yConfig.Region = "us-west-2"
				return testkits.TaskOptions{
					Config:    cfg,
					Templates: config.Templates{"tidbgroup/vector-values-aws.yaml": tmpl},
				}
			}(),
			outContains: []string{
				"vmAccountID: vm-account-123",
				"vmProjectID: vm-project-456",
				"remoteWriteURL: http://o11y-service.example.com/multitenant/api/v1/write",
				"region: us-west-2",
				"bucket: log-bucket-name",
				"roleARN: arn:aws:iam::123456789:role/agent-role",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			task := makeVectorTask(tt.agent, tt.opts)
			result, err := task.build()

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

			assert.NoError(t, err)
			for _, expected := range tt.outContains {
				assert.Contains(t, result, expected)
			}
		})
	}
}
