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/o11ytidbagent/metadata"
	"github.com/tidbcloud/observability/agent-control-server/pkg/agentbuild"
	"github.com/tidbcloud/observability/agent-control-server/pkg/cloud/aws/awscrossplane"
	"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/o11y"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

const vmagentStsName = "vmagent-" + consts.O11yNameVMAgent

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

func NewVMAgentTask(ctx *reconciler.Context[*agentv2.O11yTiDBAgent]) *VMAgentTask {
	return &VMAgentTask{
		BaseTask: &reconciler.BaseTask[*agentv2.O11yTiDBAgent]{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)
	}
	if err := agentbuild.CheckNamespaceExists(ctx, cli, t.buildNamespace()); err != nil {
		return deploy.Panic(err) // this might happen when the tidb namespace is not created
	}
	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)
	}
	return deploy.PanicOrDone(agentbuild.CheckStatefulSetReady(ctx, cli, vmagentStsName, t.buildNamespace()))
}

func (t *VMAgentTask) Delete(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	err = agentbuild.DeleteVMAgent(ctx, cli, consts.O11yNameVMAgent, t.buildNamespace())
	if err != nil {
		return deploy.Panic(err)
	}
	// delete pvc associated with vmagent statefulset
	err = agentbuild.DeletePVCWithLabels(ctx, cli, map[string]string{"app.kubernetes.io/instance": consts.O11yNameVMAgent}, t.buildNamespace())
	if err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

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

func (t *VMAgentTask) build() (*vmoperatorv1beta1.VMAgent, error) {
	cfg := t.Config.O11yConfig.CustomerConfigsV2.Dedicated.TiDBCluster.BuildConfig().VMAgent
	replicaCount := 1
	if t.Agent.Paused() {
		replicaCount = 0
	}

	imageRepo, imageTag, err := agentbuild.BuildVMAgentImage(t.Agent, cfg.Image)
	if err != nil {
		return nil, err
	}

	desiredSpec := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yDesiredSpec)
	switch util.GetAnnotation(t.Agent, consts.AnnotationKeyO11ySpecScaleStatus) {
	case consts.SpecScaleStatusPendingScale:
		desiredSpec = util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yPreviousSpec)
	}
	if desiredSpec == "" {
		desiredSpec = "default"
	}

	spec := t.Config.O11yConfig.CustomerConfigsV2.Dedicated.Specs.TiDBCluster.Select(desiredSpec)
	if spec == 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 := "dedicated/tidbcluster/vmagent.yaml"
	tmpl, err := agentbuild.BuildVMAgentTemplate(t.Agent, t.Templates, configTemplate)
	if err != nil {
		return nil, err
	}
	remoteWriteURL, err := t.buildRemoteWriteURL()
	if err != nil {
		return nil, err
	}
	storage := cfg.StatefulStorage.DeepCopy()
	if err := agentbuild.BuildVMAgentStorage(t.Agent, storage); err != nil {
		return nil, err
	}
	return agentbuild.BuildVMAgent(tmpl, resources, storage, cfg.Args, map[string]string{
		"Name":           consts.O11yNameVMAgent,
		"Namespace":      t.buildNamespace(),
		"ImageRepo":      imageRepo,
		"ImageTag":       imageTag,
		"Vendor":         t.Config.O11yConfig.Vendor,
		"Region":         t.Config.O11yConfig.Region,
		"TenantID":       t.Agent.Spec.O11y.TenantID,
		"ProjectID":      t.Agent.Spec.O11y.ProjectID,
		"ClusterID":      t.Agent.Spec.O11y.ApplicationID,
		"ClusterName":    t.Agent.Spec.KubernetesClusterName,
		"O11yID":         util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yID),
		"RemoteWriteURL": remoteWriteURL,
		"ReplicaCount":   fmt.Sprintf("%d", replicaCount),
	})
}

func (t *VMAgentTask) buildRemoteWriteURL() (string, error) {
	o11yID := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yID)

	// For AWS, get DNS from VPC endpoint if private DNS is disabled
	if t.Config.O11yConfig.Vendor == consts.VendorTypeAWS {
		if util.GetAnnotation(t.Agent, consts.AnnotationKeyDisablePrivateEndpointPrivateDNS) != "false" {
			endpointName := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcInterfaceEndpointName)
			if endpointName == "" {
				return "", fmt.Errorf("o11y service interface endpoint name annotation is empty")
			}
			dns, err := awscrossplane.GetVPCEndpointDNSByEndpointName(context.Background(), t.Local, endpointName)
			if err != nil {
				return "", err
			}
			// Use WriteURLBuilder to construct the URL with proper format
			builder := o11y.NewWriteURLBuilder(dns)
			return builder.ForAWS().BuildRemoteWrite(o11yID), nil
		}
	}
	// For GCP, get IP from PSC endpoint if private DNS is disabled
	if t.Config.O11yConfig.Vendor == consts.VendorTypeGCP {
		if util.GetAnnotation(t.Agent, consts.AnnotationKeyDisablePrivateEndpointPrivateDNS) != "false" {
			ip := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointIP)
			if ip == "" {
				return "", fmt.Errorf("o11y service psc endpoint ip annotation is empty, waiting for privatelink task to populate it")
			}
			// Use IP address directly for remote write URL
			return fmt.Sprintf("http://%s/%s/api/v1/write", ip, o11yID), nil
		}
	}
	// Fallback to standard build method
	return agentbuild.BuildRemoteWriteURL(t.Config.O11yConfig.Vendor, t.Agent), nil
}

func (t *VMAgentTask) buildNamespace() string {
	return fmt.Sprintf("tidb%s", t.Agent.Spec.O11y.ApplicationID)
}
