package interpreter

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/klog/v2"

	configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
)

var admissionScheme = runtime.NewScheme()
var admissionCodecs = serializer.NewCodecFactory(admissionScheme)

// ServeHTTP write reply headers and data to the ResponseWriter and then return.
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var body []byte
	var err error
	ctx := r.Context()

	var res Response
	if r.Body == nil {
		err = errors.New("request body is empty")
		klog.Errorf("bad request: %w", err)
		res = Errored(http.StatusBadRequest, err)
		wh.writeResponse(w, res)
		return
	}

	defer r.Body.Close()
	if body, err = ioutil.ReadAll(r.Body); err != nil {
		klog.Errorf("unable to read the body from the incoming request: %w", err)
		res = Errored(http.StatusBadRequest, err)
		wh.writeResponse(w, res)
		return
	}

	// verify the content type is accurate
	if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
		err = fmt.Errorf("contentType=%s, expected application/json", contentType)
		klog.Errorf("unable to process a request with an unknown content type: %w", err)
		res = Errored(http.StatusBadRequest, err)
		wh.writeResponse(w, res)
		return
	}

	request := Request{}
	interpreterContext := configv1alpha1.ResourceInterpreterContext{}
	// avoid an extra copy
	interpreterContext.Request = &request.ResourceInterpreterRequest
	_, _, err = admissionCodecs.UniversalDeserializer().Decode(body, nil, &interpreterContext)
	if err != nil {
		klog.Errorf("unable to decode the request: %w", err)
		res = Errored(http.StatusBadRequest, err)
		wh.writeResponse(w, res)
		return
	}
	klog.V(1).Infof("received request UID: %q, kind: %s", request.UID, request.Kind)

	res = wh.Handle(ctx, request)
	wh.writeResponse(w, res)
}

// writeResponse writes response to w generically, i.e. without encoding GVK information.
func (wh *Webhook) writeResponse(w io.Writer, response Response) {
	wh.writeResourceInterpreterResponse(w, configv1alpha1.ResourceInterpreterContext{
		Response: &response.ResourceInterpreterResponse,
	})
}

// writeResourceInterpreterResponse writes ar to w.
func (wh *Webhook) writeResourceInterpreterResponse(w io.Writer, interpreterContext configv1alpha1.ResourceInterpreterContext) {
	if err := json.NewEncoder(w).Encode(interpreterContext); err != nil {
		klog.Errorf("unable to encode the response: %w", err)
		wh.writeResponse(w, Errored(http.StatusInternalServerError, err))
	} else {
		response := interpreterContext.Response
		if response.Successful {
			klog.V(4).Infof("wrote response UID: %q, successful: %t", response.UID, response.Successful)
		} else {
			klog.V(4).Infof("wrote response UID: %q, successful: %t, response.status.code: %d, response.status.message: %s",
				response.UID, response.Successful, response.Status.Code, response.Status.Message)
		}
	}
}
