package webhooks

import (
	"context"
	"encoding/json"
	"net/http"

	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	logf "sigs.k8s.io/controller-runtime/pkg/log"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/controllers/clusterconfig"
	"github.com/tidbcloud/infra-api/controllers/revision"
	gitopsv2 "github.com/tidbcloud/infra-api/pkg/gitops-v2"
)

// Note: do not change the webhook name, we are controlling the order of different webhooks by webhook name (alphabetical order).

//+kubebuilder:webhook:path=/mutate-cloud-pingcap-com-v1-cluster-revision,mutating=true,failurePolicy=fail,sideEffects=None,groups=cloud.pingcap.com,resources=clusters,verbs=create,versions=v1,name=revision.kb.io,admissionReviewVersions={v1,v1beta1}

var logger = logf.Log.WithName("cluster-revision")
var _ admission.Handler = &ClusterRevisionMutator{}

type ClusterRevisionMutator struct {
	g       gitopsv2.Interface
	rv      revision.Interface
	decoder *admission.Decoder
}

func NewClusterRevisionMutator(mgr ctrl.Manager) *ClusterRevisionMutator {
	return &ClusterRevisionMutator{
		g:  &gitopsv2.Gitops{Cli: mgr.GetClient()},
		rv: revision.NewRevisioner(mgr.GetClient()),
	}
}

func (c *ClusterRevisionMutator) Handle(ctx context.Context, req admission.Request) admission.Response {
	cluster := &cloudv1.Cluster{}
	err := c.decoder.Decode(req, cluster)
	if err != nil {
		return admission.Errored(http.StatusBadRequest, err)
	}

	if cluster.Spec.ConfigRevisionRef != nil {
		return admission.ValidationResponse(true, "ConfigRevisionRef already set")
	}

	if cluster.Spec.ConfigRevisionRef == nil {
		logger.V(4).Info("calculate configRevision for cluster", "name", req.Name)
		cc, err := c.g.DescribeClusterConfiguration(cluster)
		if err != nil {
			return admission.Errored(http.StatusInternalServerError, err)
		}
		// use clusterconfig for newly created cluster no matter if updatepolicy is dryrun.
		cr, err := clusterconfig.GetClusterRevision(c.rv, cc)
		if err != nil {
			return admission.Errored(http.StatusInternalServerError, err)
		}
		cluster.Spec.ConfigRevisionRef = &cloudv1.Reference{Name: cr.Name}
	}

	marshaledCluster, err := json.Marshal(cluster)
	if err != nil {
		return admission.Errored(http.StatusInternalServerError, err)
	}
	return admission.PatchResponseFromRaw(req.Object.Raw, marshaledCluster)
}

// InjectDecoder implements DecoderInjector interface,
// ControllerManager will inject decoder into webhook handlers during registering.
func (c *ClusterRevisionMutator) InjectDecoder(d *admission.Decoder) error {
	c.decoder = d
	return nil
}
