package gcp

import (
	"context"

	"github.com/pingcap/errors"
	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/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"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util/namer"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util/tags"
)

// PrivateLinkTask creates VPC interface endpoint to send o11y data to o11y cluster.
type PrivateLinkTask struct {
	*reconciler.BaseTask[*agentv2.O11yTiDBAgent]
}

func NewPrivateLinkTask(ctx *reconciler.Context[*agentv2.O11yTiDBAgent]) *PrivateLinkTask {
	return &PrivateLinkTask{
		BaseTask: &reconciler.BaseTask[*agentv2.O11yTiDBAgent]{Context: ctx},
	}
}

func (t *PrivateLinkTask) Name() string {
	return "PrivateLink"
}

func (t *PrivateLinkTask) Create(ctx context.Context) deploy.Result {
	needUpdate := util.ShouldUpdateO11yVersion(t.Agent.GetAnnotations(), t.Config.O11yConfig.Version)
	endpointName := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointName)
	endpointIP := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointIP)

	// If endpoint already exists and IP is set, skip unless update is needed
	if endpointName != "" && endpointIP != "" && !needUpdate {
		return deploy.Done()
	}

	// If endpoint exists but IP is not set, just fetch and store the IP
	if endpointName != "" && endpointIP == "" {
		ip, err := t.GCP.GetPscEndpointIP(ctx, t.Agent.Spec.GKE.Project, t.Config.O11yConfig.Region, endpointName)
		if err != nil {
			return deploy.Panic(errors.Annotate(err, "failed to get psc endpoint ip"))
		}
		util.SetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointIP, ip)
		return deploy.Done()
	}

	endpointServiceName := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yEndpointServiceName)
	if endpointServiceName == "" {
		return deploy.Panic(errors.New("endpoint service name not found"))
	}
	serviceDnsName := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yServiceDNSName)
	if serviceDnsName == "" {
		return deploy.Panic(errors.New("service dns name not found"))
	}

	subnets := t.Agent.Spec.GKE.Subnets
	if len(subnets) == 0 {
		return deploy.Panic(errors.New("subnet not found"))
	}
	subnet := subnets[0]

	name := pscEndpointName(t.Agent.ObjectMeta, consts.O11yEndpointNamePrefixDataplane)
	tagMap := tags.FromLabels(t.Agent.Labels)

	// Check if private DNS should be disabled (default: disabled unless explicitly set to false)
	disablePrivateDNS := util.GetAnnotation(t.Agent, consts.AnnotationKeyDisablePrivateEndpointPrivateDNS) != "false"

	var err error
	if disablePrivateDNS {
		// Create PSC endpoint without Service Directory registration (no DNS)
		err = t.GCP.CreatePublishedServicePscEndpointWithoutDNS(
			ctx,
			t.Agent.Spec.GKE.Project,
			t.Config.O11yConfig.Region,
			t.Agent.Spec.GKE.Network,
			subnet,
			endpointServiceName,
			name,
			tagMap)
	} else {
		namespace := util.DomainNamespace(serviceDnsName)
		err = t.GCP.CreatePublishedServicePscEndpoint(
			ctx,
			t.Agent.Spec.GKE.Project,
			t.Config.O11yConfig.Region,
			t.Agent.Spec.GKE.Network,
			subnet,
			endpointServiceName,
			name,
			namespace,
			tagMap)
	}
	if err != nil {
		return deploy.Panic(errors.Annotate(err, "failed to create psc endpoint"))
	}
	// update o11y psc endpoint name to cluster's annotation
	util.SetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointName, name)

	// Always get and store the endpoint IP for later use
	ip, err := t.GCP.GetPscEndpointIP(ctx, t.Agent.Spec.GKE.Project, t.Config.O11yConfig.Region, name)
	if err != nil {
		return deploy.Panic(errors.Annotate(err, "failed to get psc endpoint ip"))
	}
	util.SetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointIP, ip)
	return deploy.Done()
}

func (t *PrivateLinkTask) Delete(ctx context.Context) deploy.Result {
	// GCP endpoints are not shared across clusters, so each agent deletes its
	// own. Prefer the name recorded at creation, which may differ from the
	// recomputed name for migrated agents.
	name := util.GetAnnotation(t.Agent, consts.AnnotationKeyO11ySvcPscEndpointName)
	if name == "" {
		name = pscEndpointName(t.Agent.ObjectMeta, consts.O11yEndpointNamePrefixDataplane)
	}
	err := t.GCP.DeletePublishedServicePscEndpoint(ctx, t.Agent.Spec.GKE.Project, t.Config.O11yConfig.Region, name)
	return deploy.PanicOrDone(errors.Annotate(err, "failed to delete psc endpoint"))
}

func pscEndpointName(meta metav1.ObjectMeta, prefix string) string {
	return namer.UniqueIdentifier(meta, namer.WithPrefix(prefix), namer.WithLimit(namer.GcpPscNameLimit))
}
