package cluster

import (
	"fmt"
	"strconv"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/aws-shared-provider/pkg/o11y"
	"github.com/tidbcloud/aws-shared-provider/pkg/o11y/metric_agent"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
	reconcileerr "github.com/tidbcloud/infra-api/controllers/common/error"
	"github.com/tidbcloud/infra-api/pkg/consts"
	o11ymetric "github.com/tidbcloud/infra-api/pkg/o11y/metric_agent"
	infrao11y "github.com/tidbcloud/observability/agent-control-server/pkg/o11y/api"
)

// Metric agent is the replacement of db-monitor in TiDB cluster.

func (r *reconcileContext) getCloudProvider() string {
	if r.ProviderType == types.ProviderTypeAliCloudFreeTier {
		return metric_agent.AliCloud
	}
	return metric_agent.AWSFreeTier
}

func (r *reconcileContext) ensureO11yCluster() (errResult error) {
	r.Logger.Info("ensure cluster o11y cluster")
	if r.IsO11yClusterAlready() {
		if _, ok := r.cluster.Annotations[types.AnnoKeyEnableCustomerAlert]; ok &&
			r.cluster.Annotations[types.AnnoKeyPrevEnableCustomerAlert] !=
				r.cluster.Annotations[types.AnnoKeyEnableCustomerAlert] {
			_ = r.updateO11yCluster()
		}
		r.Logger.Info("o11y cluster is ready, skip to ensure")
		return nil
	}

	o11yCluster, err := r.createO11yInfraCluster()
	if err != nil {
		return err
	}

	if err = r.ensureO11yRemoteWrite(o11yCluster); err != nil {
		return err
	}
	r.Logger.Info("ensure o11y cluster successful")
	return
}

func (r *reconcileContext) createO11yInfraCluster() (*infrao11y.ObservabilityCluster, error) {
	muteAlert := true
	if r.cluster.Spec.TiDB.MinRcu > 0 ||
		r.cluster.Spec.TiDB.MaxRcu > 0 {
		muteAlert = false
	}
	// Create o11y infra via autonomous global openapi.
	o11yCluster, err := r.O11yApi.CreateO11yInfraV2Serverless(
		infrao11y.WithKubernetesID(r.ctx, r.cluster.TagsKubernetes), r.O11yAgentAccessArn, o11y.AgentManagerARN, r.getCloudProvider(),
		r.cluster.Region, r.cluster.TagsTenant, r.cluster.TagsProject, r.cluster.Name,
		generateO11yBizType(o11y.DevTier, r.O11yEnv), generateAlterTempID(r.O11yEnv), r.O11yGlobalClusterID,
		strconv.FormatUint(uint64(r.keyspaceID), 10), infrao11y.SHARED, muteAlert /*r.serverlessEnabled()*/)

	if err != nil {
		return o11yCluster, errors.Annotate(err, "failed to create o11y infra cluster")
	}

	if o11yCluster.Status == infrao11y.CREATING {
		return o11yCluster, errors.Errorf("o11y cluster is %s, not ready, please try again later", o11yCluster.Status)
	}

	return o11yCluster, nil
}

func (r *reconcileContext) ensureO11yRemoteWrite(o11yCluster *infrao11y.ObservabilityCluster) error {
	clusterRemoteWrite := o11ymetric.NewWriteURLBuilder(*o11yCluster.MonitorInfo.DnsName).ForAWS().
		BuildRemoteWrite(o11yCluster.ID)

	metric_agent.AddClusterRemoteWriteToClusterAnn(r.cluster, clusterRemoteWrite, o11yCluster.ID, r.O11yConfig.Version)
	if r.cluster.Spec.TiDB.MinRcu > 0 ||
		r.cluster.Spec.TiDB.MaxRcu > 0 {
		r.cluster.Annotations[types.AnnoKeyEnableCustomerAlert] = "true"
		r.cluster.Annotations[types.AnnoKeyPrevEnableCustomerAlert] = "true"
	}
	if err := r.InfraClusterCli.Update(r.ctx, r.cluster); err != nil {
		return errors.Annotate(err, "failed add o11y cluster remote write to cluster CRD annotations")
	}

	return nil
}

func (r *reconcileContext) IsO11yClusterAlready() bool {
	// get o11y cluster id
	if _, ok := r.cluster.Annotations[consts.O11yIDAnnotationKey]; !ok {
		r.Logger.Info("failed to get o11y id in cluster CRD annotation")
		return false
	}

	// get o11y cluster remote write
	if _, ok := r.cluster.Annotations[o11y.O11yClusterRemoteWriteAnnotationKey]; !ok {
		r.Logger.Info("failed to get o11y cluster remote write in cluster CRD annotation")
		return false
	}
	return true
}

// deleteO11yCluster delete o11y cluster
func (r *reconcileContext) deleteO11yCluster() error {
	r.Logger.Info("delete o11y cluster")
	// delete o11y cluster
	if err := r.deleteO11yInfraCluster(); err != nil {
		return reconcileerr.New(defaultWaitInterval, errors.Annotate(err, "failed to delete o11y infra cluster"))
	}
	r.Logger.Info("delete o11y cluster successful")
	return nil
}

func (r *reconcileContext) deleteO11yInfraCluster() error {
	var err1 error
	ctx := infrao11y.WithKubernetesID(r.ctx, r.cluster.TagsKubernetes)
	o11yID, ok := r.cluster.Annotations[consts.O11yIDAnnotationKey]
	if ok && o11yID != "" {
		// If o11yID annotation is not empty, delete o11y infra.
		if _, err := r.O11yApi.DeleteO11yInfra(ctx, r.getCloudProvider(),
			r.cluster.Region, r.cluster.TagsTenant, r.cluster.TagsProject, r.cluster.Name, generateO11yBizType(o11y.DevTier, r.O11yEnv), o11yID); err != nil {
			err1 = errors.Annotate(err, "failed to delete o11y infra cluster with o11y openapi")
		}
	} else {
		// Else o11yID is empty, try to fetch o11y id from api.
		//nolint:lll // ignore
		o11yInfra, err := r.O11yApi.ListO11yInfraV2(
			ctx, r.getCloudProvider(), r.cluster.Region, r.cluster.TagsTenant, r.cluster.TagsProject, r.cluster.Name, generateO11yBizType(o11y.DevTier, r.O11yEnv))
		switch {
		case err != nil:
			// Retry if api returns non 200 error.
			err1 = errors.Annotate(err, "failed to get o11y infra cluster with o11y openapi")
		case len(o11yInfra) == 1:
			// Only if exactly one o11y infra is found, delete o11y infra.

			if _, err := r.O11yApi.DeleteO11yInfra(ctx, r.getCloudProvider(), r.cluster.Region, r.cluster.TagsTenant, r.cluster.TagsProject, r.cluster.Name, generateO11yBizType(o11y.DevTier, r.O11yEnv), o11yInfra[0].ID); err != nil {
				err1 = errors.Annotate(err, "failed to delete o11y infra cluster with o11y openapi")
			}
		default:
			// If err is nil and len(o11yInfra) is not 1, do nothing but log error.
			r.Logger.Info(
				"GetO11yInfra returns zero or more than one o11y infra cluster, should be exactly one" +
					" This could means that the o11y infra cluster is already deleted.")
		}
	}

	return err1
}

func generateO11yBizType(tierType, env string) string { //nolint:unparam // by design
	return fmt.Sprintf("%s-%s", env, tierType)
}

func generateAlterTempID(env string) string {
	if env == "prod" {
		return o11y.ClusterAlterTempID
	}
	return fmt.Sprintf("%s-%s", o11y.ClusterAlterTempID, "dev")
}

// updateO11yCluster updates o11y cluster with new muteAlert setting
func (r *reconcileContext) updateO11yCluster() error {
	r.Logger.Info("update cluster o11y cluster")
	muteAlert := true
	if r.cluster.Annotations[types.AnnoKeyEnableCustomerAlert] == "true" {
		muteAlert = false
	}

	o11yID, _ := r.cluster.Annotations[consts.O11yIDAnnotationKey]
	_, err := r.O11yApi.UpdateO11yInfraV2(infrao11y.WithKubernetesID(r.ctx, r.cluster.TagsKubernetes),
		r.getCloudProvider(),
		r.cluster.Region,
		r.cluster.TagsTenant,
		r.cluster.TagsProject,
		r.cluster.Name,
		o11yID,
		generateO11yBizType(o11y.DevTier, r.O11yEnv),
		infrao11y.UpdateO11yInfraOptions{
			MuteAlert:      &muteAlert,
			RuleTemplateID: generateAlterTempID(r.O11yEnv),
		})
	if err != nil {
		r.Logger.Error(err, "failed to update o11y infra cluster",
			"provider", r.getCloudProvider(),
			"region", r.cluster.Region,
			"tenant", r.cluster.TagsTenant,
			"o11yID", o11yID,
			"bizType", generateO11yBizType(o11y.DevTier, r.O11yEnv),
			"muteAlert", muteAlert)
		return errors.Annotate(err, "failed to update o11y infra cluster")
	}

	r.cluster.Annotations[types.AnnoKeyPrevEnableCustomerAlert] = strconv.FormatBool(!muteAlert)
	r.cluster.Annotations[types.AnnoKeyEnableCustomerAlert] = strconv.FormatBool(!muteAlert)
	if err := r.InfraClusterCli.Update(r.ctx, r.cluster); err != nil {
		return errors.Annotate(err, "failed to update customer alert config to cluster CRD annotations")
	}
	r.Logger.Info("update o11y cluster successful", "muteAlert", muteAlert)
	return nil
}
