package common

import (
	"context"
	"fmt"

	"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"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"
	"k8s.io/utils/pointer"

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/applier"
	"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/resource"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	fakeClusterID = "k8s-infra"
)

// 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{}

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

func (t *O11yCluster) Create(ctx context.Context) deploy.Result {
	var err error

	var dataplaneConfig agentv1.DataPlaneConfigType
	var cloudTopology agentv1.CloudTopologyType

	switch t.Network.Spec.Provider {
	case consts.ProviderTypeEKS:
		var sgID string
		var subnets []string
		if dataplaneConfig, err = o11ycluster.BuildEKSDataplaneConfig(t.Network, t.EKSNetworkSpec); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to build dataplane config"))
		}
		if cloudTopology, err = o11ycluster.BuildEKSCloudTopology(ctx, t.Local, t.Network, t.EKSNetworkSpec, sgID, subnets); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to build cloud topology"))
		}
	case consts.ProviderTypeGKE:
		if dataplaneConfig, err = o11ycluster.BuildGKEDataplaneConfig(t.Network, t.GKENetworkSpec); err != nil {
			return deploy.Error(errors.Annotate(err, "failed to build dataplane config"))
		}
		cloudTopology = o11ycluster.BuildGKECloudTopology(t.GKENetworkSpec)
	default:
		return deploy.Error(fmt.Errorf("unknown provider type %s", t.Network.Spec.Provider))
	}

	cluster := &agentv1.O11yCluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.Network.ObjectMeta.Name,
			Namespace: t.Network.ObjectMeta.Namespace,
			Labels:    util.DeepCopyMap(t.Network.ObjectMeta.Labels),
		},
		Spec: agentv1.O11yClusterSpec{
			CloudTopology:   cloudTopology,
			DataPlaneConfig: dataplaneConfig,
		},
	}
	//network create o11y cluster with fakecluster id and biztype
	cluster.Labels[cloudv1.ClusterTagKey] = fakeClusterID
	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 {
	cluster := &agentv1.O11yCluster{}
	err := t.Local.Get(ctx, types.NamespacedName{
		Name:      t.Network.ObjectMeta.Name,
		Namespace: t.Network.ObjectMeta.Namespace,
	}, cluster)
	if err != nil {
		return deploy.Error(errors.Annotate(err, "failed to get o11y cluster"))
	}

	if o11ycluster.ShouldWaitForReady(cluster, agentv1.O11yClusterPhaseNormal, t.Config.Network.O11yConfig.MaxWaitSeconds) {
		return deploy.Error(errors.New("o11y cluster is not ready"))
	}

	return deploy.Done()
}

func (t *O11yCluster) Delete(ctx context.Context) deploy.Result {
	cluster := &agentv1.O11yCluster{}
	cluster.ObjectMeta.Name = t.Network.ObjectMeta.Name
	cluster.ObjectMeta.Namespace = t.Network.ObjectMeta.Namespace

	if err := k8s.DeleteObject(ctx, t.Local, cluster); err != nil {
		return deploy.Error(errors.Annotate(err, "failed to delete o11y cluster"))
	}
	if err := k8s.DeleteObject(ctx, t.Local, applier.NewClusterObject[*v1beta1.SecurityGroup](resource.SecurityGroupO11y.Namer(t.Network).Name())); err != nil {
		return deploy.Error(errors.Annotate(err, "failed to delete security group"))
	}
	return deploy.Done()
}

func (t *O11yCluster) PostDelete(ctx context.Context) deploy.Result {
	cluster := &agentv1.O11yCluster{}
	err := k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{
		Name:      t.Network.ObjectMeta.Name,
		Namespace: t.Network.ObjectMeta.Namespace,
	}, cluster)
	// panic if o11ycluster still present to block the nodegroup deletion, thus avoid pv leak.
	return deploy.PanicOrDone(err)
}

func (t *O11yCluster) NewSecurityGroup() *v1beta1.SecurityGroup {
	name := resource.SecurityGroupO11y.Namer(t.Network).Name()
	return &v1beta1.SecurityGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1beta1.SecurityGroupSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: resource.ProviderConfig.Namer(t.Network).Name(),
				},
			},
			ForProvider: v1beta1.SecurityGroupParameters{
				Region:      t.Network.Spec.Region,
				Description: "securit group for o11y vpc endpoint",
				GroupName:   name,
				VPCIDRef: &xpv1.Reference{
					Name: resource.VPC.Namer(t.Network).Name(),
				},
				// allow all egress traffic
				Egress: []v1beta1.IPPermission{
					{
						IPProtocol: "-1",
						IPRanges: []v1beta1.IPRange{
							{
								CIDRIP: "0.0.0.0/0",
							},
						},
					},
				},
				Ingress: []v1beta1.IPPermission{
					{
						IPProtocol: "tcp",
						FromPort:   pointer.Int32(80),
						ToPort:     pointer.Int32(80),
						IPRanges: []v1beta1.IPRange{
							{
								CIDRIP: t.Network.Spec.CIDR,
							},
						},
					},
					{
						IPProtocol: "tcp",
						FromPort:   pointer.Int32(9090),
						ToPort:     pointer.Int32(9090),
						IPRanges: []v1beta1.IPRange{
							{
								CIDRIP: t.Network.Spec.CIDR,
							},
						},
					},
				},
			},
		},
	}
}
