package clusterconfig

import (
	"reflect"

	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/predicate"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

var _ predicate.Predicate = omitConfigMetaChangePredicate{}
var _ predicate.Predicate = instanceTypeChangedPredicate{}

// omitConfigMetaChangePredicate will skip update events that only spec.ConfigMeta changed.
// Since this field is re-generated everytime CD job executed even though the config remains unchanged.
type omitConfigMetaChangePredicate struct {
	predicate.Funcs
}

func (p omitConfigMetaChangePredicate) Update(e event.UpdateEvent) bool {
	logger := log.Log.WithName("omitConfigMetaChangePredicate")
	if e.ObjectOld == nil || e.ObjectNew == nil {
		logger.Error(nil, "Update event has no old or new object to update", "event", e)
		return false
	}

	if _, ok := e.ObjectOld.(*cloudv1.ClusterConfig); !ok {
		logger.Error(nil, "Update event's old object is not ClusterConfig", "event", e)
	}
	if _, ok := e.ObjectNew.(*cloudv1.ClusterConfig); !ok {
		logger.Error(nil, "Update event's new object is not ClusterConfig", "event", e)
	}

	cOld, cNew := e.ObjectOld.(*cloudv1.ClusterConfig).DeepCopy(), e.ObjectNew.(*cloudv1.ClusterConfig).DeepCopy()
	// omit configMeta because this field is re-generated everytime CD job executed even though the config remains unchanged.
	cOld.Spec.ConfigMeta, cNew.Spec.ConfigMeta = nil, nil

	return !reflect.DeepEqual(cOld.Spec, cNew.Spec)
}

// instanceTypeChangedPredicate will skip update event that has no change on instance type.
// for now, only instance type is changed, the clusterRevision might change.
type instanceTypeChangedPredicate struct {
	predicate.Funcs
}

func (p instanceTypeChangedPredicate) Update(e event.UpdateEvent) bool {
	logger := log.Log.WithName("instanceTypeChangePredicate")
	if e.ObjectOld == nil || e.ObjectNew == nil {
		logger.Error(nil, "Update event has no old or new object to update", "event", e)
		return false
	}

	if _, ok := e.ObjectOld.(*cloudv1.Cluster); !ok {
		logger.Error(nil, "Update event's old object is not ClusterConfig", "event", e)
	}
	if _, ok := e.ObjectNew.(*cloudv1.Cluster); !ok {
		logger.Error(nil, "Update event's new object is not ClusterConfig", "event", e)
	}

	cOld, cNew := e.ObjectOld.(*cloudv1.Cluster).DeepCopy(), e.ObjectNew.(*cloudv1.Cluster).DeepCopy()

	return cOld.Spec.Cluster.PD.InstanceType != cNew.Spec.Cluster.PD.InstanceType ||
		cOld.Spec.Cluster.TiDB.InstanceType != cNew.Spec.Cluster.TiDB.InstanceType ||
		cOld.Spec.Cluster.TiKV.InstanceType != cNew.Spec.Cluster.TiKV.InstanceType ||
		cOld.Spec.Cluster.TiFlash.InstanceType != cNew.Spec.Cluster.TiFlash.InstanceType
}
