package common

import (
	"context"
	"fmt"

	"github.com/pingcap/errors"
	agentv1 "github.com/tidbcloud/observability/agent-control-server/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	o11ycluster "github.com/tidbcloud/infra-provider/pkg/o11y/cluster"
	o11yutils "github.com/tidbcloud/infra-provider/pkg/o11y/metric_agent"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

// O11yCluster deploys o11y cluster which controlled by agent-control-server.
// The agent-control-server is a k8s controller which manages the lifecycle of o11y cluster.
// It will create o11y cluster, endpoints and agents.
type O11yCluster struct {
	*task.BaseTask
}

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

var _ deploy.Task = &O11yCluster{}

const (
	AnnotationKeyTiDBVersion = "o11y.cloud.pingcap.com/tidb-version"
)

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

func (t *O11yCluster) Create(ctx context.Context) deploy.Result {
	// It's not implemented in NEW cluster ctrl
	// TODO: Wait for o11y to implement it
	if t.IsRegionalManaged() {
		return deploy.Done()
	}
	if t.Tidbcluster == nil {
		return deploy.Fatal(fmt.Errorf("tidb cluster is not ready yet"))
	}

	var err error

	var dataplaneConfig agentv1.DataPlaneConfigType
	var cloudTopology agentv1.CloudTopologyType
	var clusterTopology *agentv1.ClusterTopologyType
	var pause bool

	switch t.LegacyNetwork.Spec.Provider {
	case consts.ProviderTypeEKS:
		var sgID string
		var subnets []string

		if dataplaneConfig, err = o11ycluster.BuildEKSDataplaneConfig(t.LegacyNetwork, t.LegacyEKSNetworkSpec); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to build data plane config"))
		}
		if cloudTopology, err = o11ycluster.BuildEKSCloudTopology(ctx, t.Local, t.LegacyNetwork, t.LegacyEKSNetworkSpec, sgID, subnets); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to build cloud topology"))
		}
	case consts.ProviderTypeGKE:
		if dataplaneConfig, err = o11ycluster.BuildGKEDataplaneConfig(t.LegacyNetwork, t.LegacyGKENetworkSpec); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to build data plane config"))
		}
		cloudTopology = o11ycluster.BuildGKECloudTopology(t.LegacyGKENetworkSpec)
	default:
		return deploy.Error(fmt.Errorf("unknown provider type %s", t.LegacyNetwork.Spec.Provider))
	}

	if clusterTopology, err = o11ycluster.BuildClusterTopology(t.Cluster.Spec.Cluster, &t.Config); err != nil {
		return deploy.Error(errors.Annotate(err, "failed to build cluster topology"))
	}
	pause = o11ycluster.TiDBClusterPaused(t.Cluster, t.Tidbcluster)

	cluster := &agentv1.O11yCluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.Cluster.Name,
			Namespace: t.Cluster.Namespace,
			Labels:    util.DeepCopyMap(t.Cluster.Labels),
			Annotations: map[string]string{
				AnnotationKeyTiDBVersion: t.Tidbcluster.Spec.Version,
			},
		},
		Spec: agentv1.O11yClusterSpec{
			CloudTopology:   cloudTopology,
			DataPlaneConfig: dataplaneConfig,
			ClusterTopology: clusterTopology,
			Pause:           pause,
		},
	}
	//add biztype for o11y cluster
	cluster.Labels[consts.LabelO11yBizType] = o11yutils.GetTidbCloudBizTypeByEnv(t.Config.Network.O11yConfig.Env)
	return deploy.ErrorOrDone(k8s.Apply(ctx, t.Local, cluster))
}

func (t *O11yCluster) PostCreate(ctx context.Context) deploy.Result {
	// It's not implemented in NEW cluster ctrl
	// TODO: Wait for o11y to implement it
	if t.IsRegionalManaged() {
		return deploy.Done()
	}
	cluster := &agentv1.O11yCluster{}
	err := t.Local.Get(ctx, types.NamespacedName{
		Name:      t.Cluster.ObjectMeta.Name,
		Namespace: t.Cluster.ObjectMeta.Namespace,
	}, cluster)
	if err != nil {
		return deploy.Error(errors.Annotate(err, "failed to get 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) deploy.Result {
	// It's not implemented in NEW cluster ctrl
	// TODO: Wait for o11y to implement it
	if t.IsRegionalManaged() {
		return deploy.Done()
	}
	cluster := &agentv1.O11yCluster{}
	cluster.ObjectMeta.Name = t.Cluster.ObjectMeta.Name
	cluster.ObjectMeta.Namespace = t.Cluster.ObjectMeta.Namespace

	if err := k8s.DeleteObject(ctx, t.Local, cluster); err != nil {
		return deploy.Error(errors.Annotate(err, "failed to delete o11y cluster"))
	}
	return deploy.Done()
}

func (t *O11yCluster) PostDelete(ctx context.Context) deploy.Result {
	// It's not implemented in NEW cluster ctrl
	// TODO: Wait for o11y to implement it
	if t.IsRegionalManaged() {
		return deploy.Done()
	}
	cluster := &agentv1.O11yCluster{}
	err := k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{
		Name:      t.Cluster.ObjectMeta.Name,
		Namespace: t.Cluster.ObjectMeta.Namespace,
	}, cluster)
	return deploy.ErrorOrDone(err)
}
