package o11ycluster

import (
	"context"
	"encoding/base64"
	"errors"
	"strconv"
	"strings"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	compatcst "github.com/tidbcloud/infra-provider/pkg/compat/consts"
	api "github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/o11ycluster/api"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	agentv1 "github.com/tidbcloud/observability/agent-control-server/api/v1"
	"github.com/tidbcloud/tiinf/transport/crossregion"
	apperror "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	errNoneO11yClusterType     = "none o11ycluster type"
	errWrongSpecialName        = "wrong special name"
	AnnotationKeyO11yMetricsID = "o11y.cloud.pingcap.com/metrics-id"
)

type CompatO11yClusterClienter interface {
	GetO11yCluster(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error
	CreateO11yCluster(ctx context.Context, obj client.Object, opts ...client.CreateOption) error
	DeleteO11yCluster(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error
	PauseO11yCluster(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error
	ResumeO11yCluster(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error
}

type CompatO11yClusterClient struct {
	Env      crossregion.EnvType
	Provider string
	Region   string
	cli      O11yInfraAPI
}

func NewCompatO11yClusterClient(provider, region string, env crossregion.EnvType, cli O11yInfraAPI) *CompatO11yClusterClient {
	return &CompatO11yClusterClient{
		Env:      env,
		Provider: mapProviderType(provider),
		Region:   region,
		cli:      cli,
	}
}

func (coc CompatO11yClusterClient) GetO11yCluster(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
	o11ycluster, ok := obj.(*agentv1.O11yCluster)
	if !ok {
		return apperror.NewInternalError(errors.New(errNoneO11yClusterType))
	}
	tenantID := o11ycluster.Labels[cloudv1.TenantTagKey]
	projectID := o11ycluster.Labels[cloudv1.ProjectTagKey]
	clusterID := o11ycluster.Labels[cloudv1.ClusterTagKey]
	//get o11ycluster from o11y infra api
	resps, err := coc.cli.List(Params{
		ctx:       ctx,
		provider:  coc.Provider,
		region:    coc.Region,
		tenantID:  tenantID,
		projectID: projectID,
		clusterID: clusterID,
		bizType:   getO11yBizType(string(coc.Env)),
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	if len(resps) == 0 {
		return apperror.NewNotFound(schema.GroupResource{
			Group:    obj.GetObjectKind().GroupVersionKind().Group,
			Resource: obj.GetObjectKind().GroupVersionKind().Kind,
		}, key.Name)
	}
	detail, err := coc.cli.GetByID(Params{
		ctx:         ctx,
		provider:    coc.Provider,
		region:      coc.Region,
		o11yInfraID: resps[0].ID,
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	//fill the o11ycluster,we only need Phase to make spec.pause work
	o11ycluster.Status.Phase = mapO11yServerStatusAgentPhase(detail.Status)
	o11ycluster.Spec.Pause = false
	if o11ycluster.Status.Phase == agentv1.O11yClusterPhaseHibernated {
		o11ycluster.Spec.Pause = true
	}
	//set create time
	o11ycluster.CreationTimestamp = metav1.Now()
	//set labels
	o11ycluster.SetLabels(map[string]string{
		cloudv1.TenantTagKey:  tenantID,
		cloudv1.ProjectTagKey: projectID,
		cloudv1.ClusterTagKey: clusterID,
	})
	//set o11y cluster id to annotations
	o11ycluster.SetAnnotations(map[string]string{
		AnnotationKeyO11yMetricsID:     resps[0].ID,
		compatcst.CompatFlagAnnotation: strconv.FormatBool(true),
	})
	return nil
}

func (coc CompatO11yClusterClient) CreateO11yCluster(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
	o11ycluster, ok := obj.(*agentv1.O11yCluster)
	if !ok {
		return apperror.NewInternalError(errors.New(errNoneO11yClusterType))
	}
	//get tiflow ids
	tenantID := o11ycluster.Labels[cloudv1.TenantTagKey]
	projectID := o11ycluster.Labels[cloudv1.ProjectTagKey]
	clusterID := o11ycluster.Labels[cloudv1.ClusterTagKey]
	releaseNS := o11ycluster.Annotations[consts.O11yAgentReleaseNamespace]
	clusterName := o11ycluster.Annotations[consts.AnnotationKeyDataplaneName]
	endpoint := o11ycluster.Annotations[compatcst.CompatK8SEndpointAnnotation]
	ca := o11ycluster.Annotations[compatcst.CompatK8SCAAnnotation]
	account := o11ycluster.Annotations[compatcst.CompatAWSAccountIDAnnotation]
	// encoding ca to base64
	ca64 := base64.StdEncoding.EncodeToString([]byte(ca))
	agent := api.AgentEnvironment{
		ReleaseNameSpace: releaseNS,
		Dataplane: api.Dataplane{
			ClusterName:     clusterName,
			ClusterEndpoint: endpoint,
			ClusterCAData:   &ca64,
		},
	}
	if coc.Provider == mapProviderType(cloudv1.ProviderTypeEKS) {
		agent.AWS = &api.AWSAgentEnvironment{
			VpcID:           o11ycluster.Spec.CloudTopology.EKS.VpcID,
			NodeSubnetIDs:   o11ycluster.Spec.CloudTopology.EKS.NodeSubnetIDs,
			SecurityGroupID: o11ycluster.Spec.CloudTopology.EKS.SecurityGroupID,
			Account:         account,
			OIDC:            "", //dm no need oidc, because we don't create iam for dm o11y agent
		}
	} else if coc.Provider == mapProviderType(cloudv1.ProviderTypeGKE) {
		// in gcp ClusterEndpoint is the master ip with scheme https
		// so we need to remove the scheme
		agent.Dataplane.ClusterEndpoint = strings.TrimPrefix(agent.Dataplane.ClusterEndpoint, "https://")
		agent.GCP = &api.GCPAgentEnvironment{
			NetworkName: o11ycluster.Spec.CloudTopology.GKE.Network,
			GCPProject:  o11ycluster.Spec.CloudTopology.GKE.ProjectID,
			Subnets:     o11ycluster.Spec.CloudTopology.GKE.Subnets,
		}
	}
	//create o11ycluster in o11y infra api
	_, err := coc.cli.Create(Params{
		ctx:         ctx,
		provider:    coc.Provider,
		region:      coc.Region,
		tenantID:    tenantID,
		projectID:   projectID,
		clusterID:   clusterID,
		bizType:     getO11yBizType(string(coc.Env)),
		alertTempID: api.GetAlertTmplByClusterAndEnv(clusterID, string(coc.Env)),
		mode:        api.SHARED,
		muteAlert:   false,
		agent:       agent,
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	return nil
}

func (coc CompatO11yClusterClient) DeleteO11yCluster(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
	o11ycluster, ok := obj.(*agentv1.O11yCluster)
	if !ok {
		return apperror.NewInternalError(errors.New(errNoneO11yClusterType))
	}
	//get tiflow ids
	tenantID := o11ycluster.Labels[cloudv1.TenantTagKey]
	projectID := o11ycluster.Labels[cloudv1.ProjectTagKey]
	clusterID := o11ycluster.Labels[cloudv1.ClusterTagKey]
	//get o11ycluster from o11y infra api
	resps, err := coc.cli.List(Params{
		ctx:       ctx,
		provider:  coc.Provider,
		region:    coc.Region,
		tenantID:  tenantID,
		projectID: projectID,
		clusterID: clusterID,
		bizType:   getO11yBizType(string(coc.Env)),
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	if len(resps) == 0 {
		//if not found, we think it's already deleted
		return nil
	}
	//delete o11ycluster in o11y infra api
	_, err = coc.cli.Delete(Params{
		ctx:         ctx,
		provider:    coc.Provider,
		region:      coc.Region,
		o11yInfraID: resps[0].ID,
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	return nil
}

func (coc CompatO11yClusterClient) PauseO11yCluster(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
	o11ycluster, ok := obj.(*agentv1.O11yCluster)
	if !ok {
		return apperror.NewInternalError(errors.New(errNoneO11yClusterType))
	}
	o11yid := o11ycluster.Annotations[AnnotationKeyO11yMetricsID]
	//pause o11ycluster in o11y infra api
	_, err := coc.cli.Pause(Params{
		ctx:         ctx,
		provider:    coc.Provider,
		region:      coc.Region,
		o11yInfraID: o11yid,
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	return nil
}

func (coc CompatO11yClusterClient) ResumeO11yCluster(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
	o11ycluster, ok := obj.(*agentv1.O11yCluster)
	if !ok {
		return apperror.NewInternalError(errors.New(errNoneO11yClusterType))
	}
	o11yid := o11ycluster.Annotations[AnnotationKeyO11yMetricsID]
	//resume o11ycluster in o11y infra api
	_, err := coc.cli.Resume(Params{
		ctx:         ctx,
		provider:    coc.Provider,
		region:      coc.Region,
		o11yInfraID: o11yid,
	})
	if err != nil {
		return apperror.NewInternalError(err)
	}
	return nil
}

func mapO11yServerStatusAgentPhase(status api.ResourceStatus) string {
	switch status {
	case api.CREATING:
		return agentv1.O11yClusterPhaseCreating
	case api.ACTIVE:
		return agentv1.O11yClusterPhaseNormal
	case api.DELETING:
		return agentv1.O11yClusterPhaseDeleting
	case api.TO_BE_DELETED:
		return agentv1.O11yClusterPhasePendingDelete
	case api.PAUSED, api.PAUSING:
		return agentv1.O11yClusterPhaseHibernated
	case api.RESUMING:
		return agentv1.O11yClusterPhaseWakeUp
	}
	return string(status)
}

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

func getO11yBizType(env string) string {
	switch env {
	case "dev":
		return tiflowBizTypeValue4Dev
	case "staging":
		return tiflowBizTypeValue4Staging
	default:
		return tiflowBizTypeValue
	}
}

func mapProviderType(provider string) string {
	switch provider {
	case cloudv1.ProviderTypeEKS:
		return "aws"
	case cloudv1.ProviderTypeGKE:
		return "gcp"
	}
	return provider
}
