package tidbgroupagent

import (
	"context"
	"errors"
	"testing"

	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	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/deploy"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
)

func TestBuildTasks(t *testing.T) {
	tests := []struct {
		name        string
		vendor      string
		annotations map[string]string
		build       func(*AgentReconciler, *reconciler.Context[*agentv2.TiDBGroupAgent]) []deploy.Task
		want        []string
	}{
		{
			name:   "premium mode deploys scrape before vector",
			vendor: consts.VendorTypeAWS,
			build:  (*AgentReconciler).buildTasks,
			want: []string{
				"Guardian", "Backend", "VMPodScrape", "HelmRepo",
				"Vector", "Restart", "DrainNode", "UpdateVersion",
			},
		},
		{
			name:   "alicloud wires private link name before vector",
			vendor: consts.VendorTypeAliCloud,
			build:  (*AgentReconciler).buildTasks,
			want: []string{
				"Guardian", "Backend", "VMPodScrape", "PrivateLinkName",
				"HelmRepo", "Vector", "Restart", "DrainNode",
				"UpdateVersion",
			},
		},
		{
			name:   "azure wires kubelet identity and private link ip before vector",
			vendor: consts.VendorTypeAzure,
			build:  (*AgentReconciler).buildTasks,
			want: []string{
				"Guardian", "Backend", "VMPodScrape", "KubeletIdentity",
				"PrivateLinkIP", "HelmRepo", "Vector", "Restart",
				"DrainNode", "UpdateVersion",
			},
		},
		{
			name:   "skip dataplane delete keeps only guardian and backend",
			vendor: consts.VendorTypeAzure,
			annotations: map[string]string{
				consts.AnnotationKeySkipDataPlaneDelete: "true",
			},
			build: (*AgentReconciler).buildTasks,
			want:  []string{"Guardian", "Backend"},
		},
		{
			name:   "hibernated keeps only guardian task",
			vendor: consts.VendorTypeAWS,
			build: func(r *AgentReconciler, ctx *reconciler.Context[*agentv2.TiDBGroupAgent]) []deploy.Task {
				return r.buildHibernatedTasks(ctx)
			},
			want: []string{"Guardian"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := newTiDBGroupAgent(tt.annotations)
			ar := &AgentReconciler{Reconciler: reconciler.Reconciler{
				Config: newTiDBGroupRootConfig(tt.vendor),
			}}
			ctx := &reconciler.Context[*agentv2.TiDBGroupAgent]{
				Agent:  agent,
				Config: ar.Config,
			}

			got := tt.build(ar, ctx)

			assert.Len(t, got, len(tt.want))
			for i, want := range tt.want {
				assert.Equal(t, want, got[i].Name())
			}
		})
	}
}

func newTiDBGroupAgent(annotations map[string]string) *agentv2.TiDBGroupAgent {
	return &agentv2.TiDBGroupAgent{
		ObjectMeta: metav1.ObjectMeta{
			Name:        "agent-1",
			Namespace:   "default",
			Annotations: annotations,
		},
		Spec: agentv2.TiDBGroupAgentSpec{
			KubernetesClusterName: "cluster-1",
			SharedPoolID:          "pool-1",
			TiDBGroup:             "group-1",
			ClusterID:             "cluster-id-1",
			O11y: agentv2.O11yConfigV2{
				BizType:       "tidbcloud",
				TenantID:      "tenant-1",
				ApplicationID: "app-1",
			},
		},
	}
}

func newTiDBGroupRootConfig(vendor string) *config.RootConfig {
	return &config.RootConfig{
		O11yConfig: config.ObservabilityInfra{
			Vendor:  vendor,
			Version: 1,
			CustomerConfigsV2: config.CustomerConfigsV2{
				NextGen: config.CustomerNextGen{
					Repos: map[string]string{"vm": "https://charts.example.com"},
				},
			},
		},
	}
}

func TestUpdateStatusToNormalPanicPhase(t *testing.T) {
	tests := []struct {
		name       string
		startPhase string
		results    []deploy.Result
		wantPhase  string
	}{
		{
			name:       "panic in results sets phase to Panic",
			startPhase: agentv2.O11yAgentPhaseNormal,
			results: []deploy.Result{
				deploy.Done(),
				deploy.Panic(errors.New("vmpodscrape not operational")),
			},
			wantPhase: agentv2.O11yAgentPhasePanic,
		},
		{
			name:       "partial recovery with Error clears Panic",
			startPhase: agentv2.O11yAgentPhasePanic,
			results: []deploy.Result{
				deploy.Done(),
				deploy.Error(errors.New("transient")),
			},
			wantPhase: agentv2.O11yAgentPhaseNormal,
		},
		{
			name:       "full recovery returns phase to Normal",
			startPhase: agentv2.O11yAgentPhasePanic,
			results: []deploy.Result{
				deploy.Done(),
				deploy.Done(),
			},
			wantPhase: agentv2.O11yAgentPhaseNormal,
		},
		{
			name:       "Error from Normal leaves phase Normal",
			startPhase: agentv2.O11yAgentPhaseNormal,
			results: []deploy.Result{
				deploy.Error(errors.New("e")),
			},
			wantPhase: agentv2.O11yAgentPhaseNormal,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := newTiDBGroupAgent(nil)
			agent.Status.Phase = tt.startPhase

			scheme := runtime.NewScheme()
			require.NoError(t, agentv2.AddToScheme(scheme))
			cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(agent).Build()

			ctx := &reconciler.Context[*agentv2.TiDBGroupAgent]{
				Agent:  agent,
				Local:  cli,
				Config: newTiDBGroupRootConfig(consts.VendorTypeAWS),
			}

			_, _ = updateStatusToNormal(context.Background(), logr.Discard(), ctx, tt.results)
			assert.Equal(t, tt.wantPhase, agent.Status.Phase)
		})
	}
}
