package mastermgr

import (
	"context"
	"fmt"

	"github.com/pingcap/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	agentv1 "github.com/tidbcloud/observability/agent-control-server/api/v1"

	v1 "github.com/tidbcloud/infra-api/api/v1"

	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	compatcst "github.com/tidbcloud/infra-provider/pkg/compat/consts"
	k8s "github.com/tidbcloud/infra-provider/pkg/compat/k8s/dataplane"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	o11ycluster "github.com/tidbcloud/infra-provider/pkg/o11y/cluster"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	tiflowBizTypeValue         = "datamigration"
	tiflowBizTypeValue4Dev     = "dev-datamigration"
	tiflowBizTypeValue4Staging = "staging-datamigration"
	DefaultTenantLabelValue    = "unknown"
)

type O11yCluster struct {
	*BaseTask
}

func NewO11yCluster(ctx *Context) *O11yCluster {
	return &O11yCluster{BaseTask: &BaseTask{Context: ctx}}
}

func (t *O11yCluster) Name() string {
	return "O11yCluster"
}

func (t *O11yCluster) Create(ctx context.Context) deploy.Result {
	if t.HasActiveMaster() {
		var cloudTopology agentv1.CloudTopologyType
		var dataplaneConfig agentv1.DataPlaneConfigType
		switch t.Network.Spec.Provider {
		case consts.ProviderTypeEKS:
			var sgID string
			var subnets []string
			eksNetwork, err := v1alpha1.EKSNetworkSpecForNetwork(t.Network)
			if err != nil {
				return deploy.Error(errors.Annotate(err, "failed to get eks network spec"))
			}
			if dataplaneConfig, err = o11ycluster.BuildEKSDataplaneConfig(t.Network, eksNetwork); err != nil {
				return deploy.Error(errors.Annotate(err, "failed to build data plane config"))
			}
			if cloudTopology, err = o11ycluster.BuildEKSCloudTopology(ctx, t.Local, t.Network, eksNetwork, sgID, subnets); err != nil {
				return deploy.Error(errors.Annotate(err, "failed to build cloud topology"))
			}
		case consts.ProviderTypeGKE:
			gkeNetwork, err := v1alpha1.GKENetworkSpecForNetwork(t.Network)
			if err != nil {
				return deploy.Error(errors.Annotate(err, "failed to get gke network spec"))
			}
			if dataplaneConfig, err = o11ycluster.BuildGKEDataplaneConfig(t.Network, gkeNetwork); err != nil {
				return deploy.Error(errors.Annotate(err, "failed to build data plane config"))
			}
			cloudTopology = o11ycluster.BuildGKECloudTopology(gkeNetwork)
		default:
			return deploy.Error(fmt.Errorf("unknown provider type %s", t.Network.Spec.Provider))
		}

		labels := util.DeepCopyMap(t.TiflowClusterMasterManager.Labels)
		tenantID, projectID, clusterID := GetTiflowIDs(t.Network)
		labels[consts.LabelO11yBizType] = GetO11yBizType(&t.Config)
		labels[v1.ClusterTagKey] = clusterID
		labels[v1.ProjectTagKey] = projectID
		labels[v1.TenantTagKey] = tenantID
		annos := util.DeepCopyMap(t.TiflowClusterMasterManager.DeepCopy().Annotations)
		annos[consts.O11yAgentReleaseNamespace] = t.Namer.GetTiflowNamespace()
		cluster := &agentv1.O11yCluster{
			ObjectMeta: metav1.ObjectMeta{
				Name:        t.TiflowClusterMasterManager.ObjectMeta.Name,
				Namespace:   t.TiflowClusterMasterManager.ObjectMeta.Namespace,
				Labels:      labels,
				Annotations: annos,
			},
			Spec: agentv1.O11yClusterSpec{
				CloudTopology:   cloudTopology,
				DataPlaneConfig: dataplaneConfig,
			},
		}
		if _, ok := t.Network.Annotations[compatcst.CompatFlagAnnotation]; ok {
			//in compat mode we should pass more info to o11y infra
			networkannos := util.DeepCopyMap(t.Network.Annotations)
			cluster.Annotations[consts.AnnotationKeyDataplaneName] = networkannos[consts.AnnotationKeyDataplaneName]
			cluster.Annotations[compatcst.CompatK8SEndpointAnnotation] = networkannos[compatcst.CompatK8SEndpointAnnotation]
			cluster.Annotations[compatcst.CompatK8SCAAnnotation] = networkannos[compatcst.CompatK8SCAAnnotation]
			cluster.Annotations[compatcst.CompatAWSAccountIDAnnotation] = t.Network.Status.IAM.AccountID
			cluster.Annotations[compatcst.CompatFlagAnnotation] = "true"
		}
		// using a three-way merge Apply to ensure that we do not clobber any
		// changes that have been made to the object, since agent-controller
		// will change o11ycluster.
		return deploy.ErrorOrDone(k8s.Apply(ctx, t.Local, cluster))
	}
	return deploy.Done()
}

func (t *O11yCluster) PostCreate(ctx context.Context) (res deploy.Result) {
	cluster := &agentv1.O11yCluster{}
	if _, ok := t.Network.Annotations[compatcst.CompatFlagAnnotation]; ok {
		//in compat mode we should pass more info to o11y infra
		tenantID, projectID, clusterID := GetTiflowIDs(t.Network)
		cluster.SetLabels(map[string]string{
			v1.TenantTagKey:  tenantID,
			v1.ProjectTagKey: projectID,
			v1.ClusterTagKey: clusterID,
		})
	}
	err := t.Local.Get(ctx, client.ObjectKeyFromObject(t.TiflowClusterMasterManager), cluster)
	if err != nil {
		return deploy.Error(errors.Annotate(err, "failed to get o11y cluster"))
	}
	// resume or pause o11y cluster
	pause := !t.HasActiveMaster()
	if cluster.Spec.Pause != pause {
		cluster.Spec.Pause = pause
		if err := t.Local.Update(ctx, cluster); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to update o11y cluster"))
		}
	}

	expectedPhase := agentv1.O11yClusterPhaseNormal
	if cluster.Spec.Pause {
		expectedPhase = agentv1.O11yClusterPhaseHibernated
	}
	if o11ycluster.ShouldWaitForReady(cluster, expectedPhase, t.Config.Network.O11yConfig.MaxWaitSeconds) {
		return deploy.Error(errors.Errorf("o11y cluster expected phase: %s, actual phase: %s", expectedPhase, cluster.Status.Phase))
	}

	return deploy.Done()
}

func (t *O11yCluster) Delete(ctx context.Context) (res deploy.Result) {
	cluster := &agentv1.O11yCluster{}
	cluster.ObjectMeta.Name = t.TiflowClusterMasterManager.ObjectMeta.Name
	cluster.ObjectMeta.Namespace = t.TiflowClusterMasterManager.ObjectMeta.Namespace
	if _, ok := t.Network.Annotations[compatcst.CompatFlagAnnotation]; ok {
		//in compat mode we should pass more info to o11y infra
		tenantID, projectID, clusterID := GetTiflowIDs(t.Network)
		cluster.SetLabels(map[string]string{
			v1.TenantTagKey:  tenantID,
			v1.ProjectTagKey: projectID,
			v1.ClusterTagKey: clusterID,
		})
	}
	if err := k8s.DeleteObject(ctx, t.Local, cluster); err != nil {
		return deploy.Error(errors.Annotate(err, "failed to delete o11y cluster"))
	}
	return deploy.Done()
}

// GetTiflowIDs get ids for tiflow o11y.
// refer to: https://github.com/tidbcloud/infra-provider/blob/ff7d94ad0c6983c0bea869886ddff754852ffbc1/infra-api/pkg/o11y/metric_agent/deploy_helper.go#L276
func GetTiflowIDs(network *v1.Network) (tenantID, projectID, clusterID string) {
	// since we have one o11yInfra for one project, we use 'tiflow' as clusterID here
	clusterID = tiflowBizTypeValue
	if v, ok := network.Labels[v1.ProjectTagKey]; ok && v != "" {
		projectID = v
	} else {
		projectID = network.Name
	}
	if v, ok := network.Labels[v1.TenantTagKey]; ok && v != "" {
		tenantID = v
	} else {
		tenantID = DefaultTenantLabelValue
	}
	return
}

func GetO11yBizType(config *config.RootConfig) string {
	switch config.Network.O11yConfig.Env {
	case "dev":
		return tiflowBizTypeValue4Dev
	case "staging":
		return tiflowBizTypeValue4Staging
	default:
		return tiflowBizTypeValue
	}
}
