package task

import (
	"context"
	"fmt"

	vmoperatorv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	"github.com/pingcap/log"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/controllers/kubernetesagent/metadata"
	"github.com/tidbcloud/observability/agent-control-server/pkg/agentbuild"
	"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"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

type VMAgentTask struct {
	*reconciler.BaseTask[*agentv2.KubernetesAgent]
}

func NewVMAgentTask(ctx *reconciler.Context[*agentv2.KubernetesAgent]) *VMAgentTask {
	return &VMAgentTask{
		BaseTask: &reconciler.BaseTask[*agentv2.KubernetesAgent]{Context: ctx},
	}
}

func (t *VMAgentTask) Name() string {
	return "VMAgent"
}

func (t *VMAgentTask) ClusterMeta() deploy.ClusterMeta {
	return metadata.ClusterMeta(t.Agent)
}

func (t *VMAgentTask) Create(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	err = agentbuild.CreateOrUpdateVMAgent(ctx, cli, t.build)
	if err != nil {
		log.Error(fmt.Sprintf("create or update vmagent failed, %s", err))
	}
	return deploy.PanicOrDone(err)
}

func (t *VMAgentTask) PostCreate(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	if err := agentbuild.CheckStatefulSetReadyWithLabels(ctx, cli, t.buildNamespace(), t.statefulsetLabels()); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

func (t *VMAgentTask) Delete(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	return deploy.PanicOrDone(agentbuild.DeleteVMAgent(ctx, cli, consts.O11yNameVMAgent, t.buildNamespace()))
}

func (t *VMAgentTask) PostDelete(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	if err := agentbuild.CheckVMAgentsDeleted(ctx, cli, consts.O11yNameVMAgent, t.buildNamespace()); err != nil {
		return deploy.Panic(err)
	}
	return deploy.PanicOrDone(agentbuild.CheckStatefulSetDeletedWithLabels(ctx, cli, t.buildNamespace(), t.statefulsetLabels()))
}

func (t *VMAgentTask) build() (*vmoperatorv1beta1.VMAgent, error) {
	cfg := t.Config.O11yConfig.CustomerConfigsV2.NextGen.K8s.BuildConfig().VMAgent
	imageRepo, imageTag, err := agentbuild.BuildVMAgentImage(t.Agent, cfg.Image)
	if err != nil {
		return nil, err
	}

	desiredSpec := util.GetAnnotationWithDefault(t.Agent, consts.AnnotationKeyO11yDesiredSpec, "default")
	spec := t.Config.O11yConfig.CustomerConfigsV2.NextGen.K8s.Specs.Select(desiredSpec)
	if spec == nil || spec.VMAgent == nil {
		return nil, fmt.Errorf("desired spec not found: %s", desiredSpec)
	}
	resources := spec.VMAgent.DeepCopy()
	if err := agentbuild.BuildVMAgentResources(t.Agent, resources); err != nil {
		return nil, err
	}

	configTemplate := "k8s/vmagent.yaml"
	tmpl, err := agentbuild.BuildVMAgentTemplate(t.Agent, t.Templates, configTemplate)
	if err != nil {
		return nil, err
	}
	// label to pvc
	labels := map[string]string{
		consts.TagKeyComponent:    consts.TagValueComponentO11y,
		consts.TagKeyTenant:       t.Agent.Spec.O11y.TenantID,
		consts.TagKeyResourcePool: util.GetLabel(t.Agent, consts.TagKeySharedPool),
	}
	storage := cfg.StatefulStorage.DeepCopy()
	storage.VolumeClaimTemplate.Labels = labels
	if err := agentbuild.BuildVMAgentStorage(t.Agent, storage); err != nil {
		return nil, err
	}
	return agentbuild.BuildVMAgentWithPodLabels(tmpl, resources, storage, cfg.Affinity, cfg.Tolerations, cfg.Args,
		map[string]string{
			"Name":              consts.O11yNameVMAgent,
			"Namespace":         t.buildNamespace(),
			"ImageRepo":         imageRepo,
			"ImageTag":          imageTag,
			"ClusterName":       t.Agent.Spec.KubernetesClusterName,
			"VMAccountID":       util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yVMAccountID),
			"VMProjectID":       util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yVMProjectID),
			"RemoteWriteURL":    fmt.Sprintf("http://%s/multitenant/api/v1/write", util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yServiceDNSName)),
			"PriorityClassName": consts.PriorityClassNameNodeCritical,
			"ProjectID":         util.GetLabel(t.Agent, consts.LabelKeyO11yProjectID),
			"Vendor":            t.Config.O11yConfig.Vendor,
			"Provider":          consts.VendorToProvider(t.Config.O11yConfig.Vendor),
			"Region":            t.Config.O11yConfig.Region,
		},
		t.vmagentLabels(),
	)
}

func (t *VMAgentTask) vmagentLabels() map[string]string {
	lbs := consts.VMAgentLabels(consts.AgentKubernetes)
	lbs[consts.AgentKeyRefNamespace] = t.Agent.Namespace
	lbs[consts.AgentKeyRefName] = t.Agent.Name
	return lbs
}

func (t *VMAgentTask) statefulsetLabels() map[string]string {
	return map[string]string{
		"app.kubernetes.io/instance": consts.O11yNameVMAgent,
		"app.kubernetes.io/name":     "vmagent",
	}
}

func (t *VMAgentTask) buildNamespace() string {
	if t.Config.IsServerlessMode() {
		return consts.ServerlessObservabilityNamespace
	}
	return consts.O11ySystemNamespace
}
