package task

import (
	"context"
	"testing"

	vmoperatorv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	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 makeVMServiceScrapeTask(agent *agentv2.KubernetesAgent, opts testkits.TaskOptions) *VMServiceScrapeTask {
	ctx := &reconciler.Context[*agentv2.KubernetesAgent]{
		Agent:     agent,
		Local:     testkits.BuildClient(agent),
		Remote:    testkits.NewClientGetterWithOpts(opts),
		Config:    &config.RootConfig{},
		Templates: opts.Templates,
	}
	return NewVMServiceScrapeTask(ctx)
}

func TestVMServiceScrapeTask(t *testing.T) {
	tests := []struct {
		name   string
		verify func(t *testing.T)
	}{
		{
			name: "Name",
			verify: func(t *testing.T) {
				task := &VMServiceScrapeTask{}
				assert.Equal(t, "VMServiceScrape", task.Name())
			},
		},
		{
			name: "ClusterMeta",
			verify: func(t *testing.T) {
				agent := testkits.NewKubernetesAgentBuilder().Build()
				task := makeVMServiceScrapeTask(agent, testkits.TaskOptions{})
				meta := task.ClusterMeta()

				assert.Equal(t, consts.AgentKubernetes, meta.Agent)
				assert.Equal(t, "test-cluster", meta.K8sClusterName)
				assert.Equal(t, "tenant-1", meta.TenantID)
				assert.Equal(t, "app-1", meta.ApplicationID)
			},
		},
		{
			name: "buildNamespace returns serverless namespace",
			verify: func(t *testing.T) {
				agent := testkits.NewKubernetesAgentBuilder().Build()
				task := makeVMServiceScrapeTask(agent, testkits.TaskOptions{})
				assert.Equal(t, consts.ServerlessObservabilityNamespace, task.buildNamespace())
			},
		},
		{
			name: "build returns the expected VMServiceScrape spec",
			verify: func(t *testing.T) {
				agent := testkits.NewKubernetesAgentBuilder().Build()
				task := makeVMServiceScrapeTask(agent, testkits.TaskOptions{})

				got, err := task.build()
				assert.NoError(t, err)
				assert.Equal(t, agentControlServerName, got.Name)
				assert.Equal(t, consts.ServerlessObservabilityNamespace, got.Namespace)
				assert.Equal(t,
					map[string]string{"app": "o11y-agent-control-server"},
					got.Spec.Selector.MatchLabels)
				assert.Len(t, got.Spec.Endpoints, 1)
				assert.Equal(t, "http", got.Spec.Endpoints[0].Port)
				assert.Equal(t, "/metrics", got.Spec.Endpoints[0].Path)
				assert.Equal(t, "http", got.Spec.Endpoints[0].Scheme)
			},
		},
	}

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

func TestVMServiceScrapeTask_Create(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.KubernetesAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:    "create fails with remote client error",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{ClientError: true},
			wantErr: true,
		},
		{
			name:    "create succeeds with empty client",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{Client: testkits.BuildClient()},
			wantErr: false,
		},
		{
			name:  "create succeeds when resource already exists",
			agent: testkits.NewKubernetesAgentBuilder().Build(),
			opts: testkits.TaskOptions{Client: testkits.BuildClient(
				&vmoperatorv1beta1.VMServiceScrape{
					ObjectMeta: metav1.ObjectMeta{
						Name:      agentControlServerName,
						Namespace: consts.ServerlessObservabilityNamespace,
					},
				},
			)},
			wantErr: false,
		},
	}

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

func TestVMServiceScrapeTask_PostCreate(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.KubernetesAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:    "post create fails with remote client error",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{ClientError: true},
			wantErr: true,
		},
		{
			name:  "post create succeeds when vmservicescrape is operational",
			agent: testkits.NewKubernetesAgentBuilder().Build(),
			opts: testkits.TaskOptions{Client: testkits.BuildClient(
				&vmoperatorv1beta1.VMServiceScrape{
					ObjectMeta: metav1.ObjectMeta{
						Name:      agentControlServerName,
						Namespace: consts.ServerlessObservabilityNamespace,
					},
					Status: vmoperatorv1beta1.ScrapeObjectStatus{
						StatusMetadata: vmoperatorv1beta1.StatusMetadata{
							UpdateStatus: vmoperatorv1beta1.UpdateStatusOperational,
						},
					},
				},
			)},
			wantErr: false,
		},
		{
			name:    "post create fails when vmservicescrape is missing",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{Client: testkits.BuildClient()},
			wantErr: true,
		},
	}

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

func TestVMServiceScrapeTask_Delete(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.KubernetesAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:    "delete fails with remote client error",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{ClientError: true},
			wantErr: true,
		},
		{
			name:    "delete succeeds when resource is absent",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{Client: testkits.BuildClient()},
			wantErr: false,
		},
		{
			name:  "delete succeeds when resource exists",
			agent: testkits.NewKubernetesAgentBuilder().Build(),
			opts: testkits.TaskOptions{Client: testkits.BuildClient(
				&vmoperatorv1beta1.VMServiceScrape{
					ObjectMeta: metav1.ObjectMeta{
						Name:      agentControlServerName,
						Namespace: consts.ServerlessObservabilityNamespace,
					},
				},
			)},
			wantErr: false,
		},
	}

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

func TestVMServiceScrapeTask_PostDelete(t *testing.T) {
	tests := []struct {
		name    string
		agent   *agentv2.KubernetesAgent
		opts    testkits.TaskOptions
		wantErr bool
	}{
		{
			name:    "post delete fails with remote client error",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{ClientError: true},
			wantErr: true,
		},
		{
			name:    "post delete succeeds when resource is gone",
			agent:   testkits.NewKubernetesAgentBuilder().Build(),
			opts:    testkits.TaskOptions{Client: testkits.BuildClient()},
			wantErr: false,
		},
		{
			name:  "post delete fails when resource still exists",
			agent: testkits.NewKubernetesAgentBuilder().Build(),
			opts: testkits.TaskOptions{Client: testkits.BuildClient(
				&vmoperatorv1beta1.VMServiceScrape{
					ObjectMeta: metav1.ObjectMeta{
						Name:      agentControlServerName,
						Namespace: consts.ServerlessObservabilityNamespace,
					},
				},
			)},
			wantErr: true,
		},
	}

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