package action

import (
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	o11yv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	CreateO11yTiDBGroupAgentActionName = "create-o11y-tidbgroup-agent"
	tidbNodeExporterScrapeAnnotation   = "o11y.cloud.pingcap.com/tidb-node-exporter-scrape"
	tiproxyDeployTypeAnnotation        = "o11y.cloud.pingcap.com/tiproxy-deploy-type"
)

type CreateO11yTiDBGroupAgentAction struct {
	o11yBizType    string
	disableO11y    bool
	dpmgrClient    dplanemgrv1.DataPlaneServiceClient
	kubeClient     client.Client
	sharedPoolRepo repo.SharedPoolRepo
	agentNamespace string
}

func NewCreateO11yTiDBGroupAgentAction(
	o11yBizType string,
	disableO11y bool,
	dpmgrClient dplanemgrv1.DataPlaneServiceClient,
	kubeClient client.Client,
	sharedPoolRepo repo.SharedPoolRepo,
	agentNamespace ...string,
) *CreateO11yTiDBGroupAgentAction {
	return &CreateO11yTiDBGroupAgentAction{
		o11yBizType:    o11yBizType,
		disableO11y:    disableO11y,
		dpmgrClient:    dpmgrClient,
		kubeClient:     kubeClient,
		sharedPoolRepo: sharedPoolRepo,
		agentNamespace: resolveO11yAgentNamespace(agentNamespace...),
	}
}

func (a *CreateO11yTiDBGroupAgentAction) Name() string {
	return CreateO11yTiDBGroupAgentActionName
}

// we do not check o11y agent status since it is not a core step
func (a *CreateO11yTiDBGroupAgentAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	if a.disableO11y {
		tilog.Info(ctx, "o11y is disabled, skip create o11y tidbgroup agent")
		return nil
	}
	tilog.Info(ctx, "create o11y tidbgroup agent action start")
	tidbGroupId, err := args.GetString(workflow.TiDBGroupIdKey)
	if err != nil {
		return fmt.Errorf("failed to get tidbgroup id: %w", err)
	}
	tidbGroupStr, err := args.GetString(workflow.TiDBGroupKey)
	if err != nil {
		return fmt.Errorf("failed to get tidbgroup: %w", err)
	}

	var tidbGroup entity.TiDBGroup
	err = protojson.Unmarshal([]byte(tidbGroupStr), &tidbGroup)
	if err != nil {
		tilog.Error(ctx, "failed to unmarshal tidb group", zap.Error(err))
		return fmt.Errorf("failed to unmarshal tidb group: %w", err)
	}
	clusterId := tidbGroup.GetClusterId()
	if tidbGroup.GetType() == apiv1.TiDBGroupType_INTERNAL {
		tilog.Info(ctx, "skip create o11y tidbgroup agent for internal tidb group",
			zap.String("tidbGroupId", tidbGroupId),
			zap.String("tidbClusterId", clusterId),
		)
		return nil
	}
	dataplaneId, err := args.GetString(workflow.DataplaneIdKey)
	if err != nil {
		return fmt.Errorf("failed to get dataplane id: %w", err)
	}
	out, err := a.dpmgrClient.GetDataPlane(ctx, &dplanemgrv1.GetDataPlaneRequest{DataPlaneId: dataplaneId})
	if err != nil {
		tilog.Error(ctx, "failed to get dataplane", zap.String("dataplaneId", dataplaneId), zap.Error(err))
		return err
	}
	// From @mornyx
	// For biztype, hardcode it as dev-tidbcloud-nextgen.
	// For tenant id, use the tenant ID.
	// Regarding tidbgroup: use the cluster ID as the application ID.
	// For sharedpool, use the formats sharedpool-<ID>.
	agent := new(o11yv2.TiDBGroupAgent)
	agent.Name = tidbGroupId
	agent.Namespace = a.agentNamespace
	setTiDBNodeExporterScrapeAnnotation := false
	tiproxyDeployType := ""
	mutateAgent := func() {
		if agent.Labels == nil {
			agent.Labels = make(map[string]string)
		}
		agent.Labels[entity.O11yTiDBGroupAgentClusterIDLabelKey] = clusterId
		agent.Labels[entity.O11yTiDBGroupAgentGroupIDLabelKey] = tidbGroupId
		agent.Spec = o11yv2.TiDBGroupAgentSpec{
			KubernetesClusterName: out.GetKubernetes().GetName(),
			SharedPoolID:          tidbGroup.GetSharedPoolId(),
			TiDBGroup:             tidbGroupId,
			ClusterID:             clusterId,
			O11y: o11yv2.O11yConfig{
				BizType:       a.o11yBizType,
				TenantID:      tidbGroup.GetTenantId(),
				ApplicationID: clusterId,
			},
		}
		if setTiDBNodeExporterScrapeAnnotation {
			if agent.Annotations == nil {
				agent.Annotations = make(map[string]string)
			}
			agent.Annotations[tidbNodeExporterScrapeAnnotation] = "true"
		} else if agent.Annotations != nil {
			delete(agent.Annotations, tidbNodeExporterScrapeAnnotation)
		}

		if tiproxyDeployType != "" {
			if agent.Annotations == nil {
				agent.Annotations = make(map[string]string)
			}
			agent.Annotations[tiproxyDeployTypeAnnotation] = tiproxyDeployType
		} else if agent.Annotations != nil {
			delete(agent.Annotations, tiproxyDeployTypeAnnotation)
		}
	}
	sharedPool, err := a.sharedPoolRepo.Load(ctx, tidbGroup.GetSharedPoolId(), false)
	if err != nil {
		return fmt.Errorf("failed to load shared pool %s: %w", tidbGroup.GetSharedPoolId(), err)
	}
	if sharedPool.GetCapacityMode() == apiv1.CapacityMode_TOPOLOGY_DRIVEN {
		setTiDBNodeExporterScrapeAnnotation = true
	}
	if entity.HasSharedTiProxy(sharedPool) {
		tiproxyDeployType = schema.SharedPoolDeployTypeSharedTiProxy
	}

	if _, err := controllerutil.CreateOrUpdate(ctx, a.kubeClient, agent, func() error {
		mutateAgent()
		return nil
	}); err != nil {
		tilog.Error(ctx, "failed to create o11y tidbgroup agent", zap.Error(err))
		return err
	}
	return nil
}
