package cluster

import (
	"fmt"
	"reflect"

	"github.com/google/go-cmp/cmp"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	reconcileerr "github.com/tidbcloud/infra-api/controllers/common/error"
	"github.com/tidbcloud/infra-api/controllers/utils"
	"github.com/tidbcloud/infra-api/pkg/util"
)

type mutationFilter interface {
	Allow(*cloudv1.Cluster) bool
	Reason(*cloudv1.Cluster) string
}

type whenPhaseIn []cloudv1.ClusterPhase

func (f whenPhaseIn) Allow(c *cloudv1.Cluster) bool {
	for _, phase := range f {
		if c.Status.Phase == phase {
			return true
		}
	}
	return false
}

func (f whenPhaseIn) Reason(c *cloudv1.Cluster) string {
	return fmt.Sprintf("cluster phase %s should be in %+v", c.Status.Phase, f)
}

// mutateTidbCluster mutate the tidbcluster resource is necessary and possible
func (r *reconcileContext) mutateTidbCluster(mutateFn func(tc *v1alpha1.TidbCluster) error, filters ...mutationFilter) (mutated bool, err error) {
	beforeMutation := r.tc.DeepCopy()
	if err := mutateFn(r.tc); err != nil {
		return false, err
	}
	if reflect.DeepEqual(beforeMutation, r.tc) {
		// no changed, skip updating
		return false, nil
	}
	for _, filter := range filters {
		if !filter.Allow(r.Target) {
			r.Log.V(4).Info("skip updating tc", "reason", filter.Reason(r.Target))
			return false, &reconcileerr.RequeueAfterError{
				RequeueAfter: defaultWaitInterval,
				Caused:       errors.Errorf("skip updating tc: %s %s", util.GetFunctionName(mutateFn), filter.Reason(r.Target)),
			}
		}
	}
	if utils.HasDryrunAnnotation(r.Target) {
		diff := cmp.Diff(beforeMutation, r.tc)
		r.Log.Info("[Dryrun] update tc", "diff", diff)
		return false, nil
	}
	r.Log.Info("update mutated TiDB Cluster from " + util.GetFunctionName(mutateFn))
	if err := r.shootClient.Update(r.ctx, r.tc); err != nil {
		return false, err
	}
	return true, nil
}
