package task

import (
	"context"
	"fmt"

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

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/controllers/sharedpoolagent/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.SharedPoolAgent]
}

func NewVMAgentTask(ctx *reconciler.Context[*agentv2.SharedPoolAgent]) *VMAgentTask {
	return &VMAgentTask{
		BaseTask: &reconciler.BaseTask[*agentv2.SharedPoolAgent]{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.Error(err)
	}
	return deploy.PanicOrDone(agentbuild.CreateOrUpdateVMAgent(ctx, cli, t.build))
}

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, t.buildName(), 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, t.buildName(), 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.SharedPool.BuildConfig().VMAgent
	imageRepo, imageTag, err := agentbuild.BuildVMAgentImage(t.Agent, cfg.Image)
	if err != nil {
		return nil, err
	}

	desiredSpec := util.GetComponentDesiredSpec(t.Agent.GetAnnotations(), "default", consts.ComponentVMAgent)
	spec := t.Config.O11yConfig.CustomerConfigsV2.NextGen.SharedPool.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 := "sharedpool/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: t.Agent.Spec.SharedPoolID,
	}
	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":              t.buildName(),
			"Namespace":         t.buildNamespace(),
			"ImageRepo":         imageRepo,
			"ImageTag":          imageTag,
			"SharedPoolID":      t.Agent.Spec.SharedPoolID,
			"LabelScrapeBy":     fmt.Sprintf("tidb%s-o11y-vmagent", t.Agent.Spec.SharedPoolID),
			"RemoteWriteURL":    fmt.Sprintf("http://%s/multitenant/api/v1/write", util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yServiceDNSName)),
			"PriorityClassName": consts.PriorityClassNameClusterCritical,
			"VMAccountID":       util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yVMAccountID),
			"ClusterName":       t.Agent.Spec.KubernetesClusterName,
			"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) buildNamespace() string {
	if t.Config.IsServerlessMode() {
		return consts.ServerlessObservabilityNamespace
	}
	return fmt.Sprintf("tidb%s", t.Agent.Spec.SharedPoolID)
}

func (t *VMAgentTask) buildName() string {
	if t.Config.IsServerlessMode() {
		return consts.O11yNameVMAgent + "-sharedpool"
	}
	return consts.O11yNameVMAgent
}

func (t *VMAgentTask) vmagentLabels() map[string]string {
	lbs := consts.VMAgentLabels(consts.AgentSharedPool)
	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": t.buildName(),
		"app.kubernetes.io/name":     "vmagent",
	}
}
