// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package defaults

import (
	"context"
	"encoding/json"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	schedulev1 "k8s.io/api/scheduling/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	"github.com/projectcapsule/capsule/internal/webhook/utils"
	"github.com/projectcapsule/capsule/pkg/api"
	caperrors "github.com/projectcapsule/capsule/pkg/api/errors"
	ad "github.com/projectcapsule/capsule/pkg/runtime/admission"
	"github.com/projectcapsule/capsule/pkg/tenant"
)

func mutatePodDefaults(
	ctx context.Context,
	req admission.Request,
	c client.Client,
	decoder admission.Decoder,
	namespace string,
) *admission.Response {
	var pod corev1.Pod
	if err := decoder.Decode(req, &pod); err != nil {
		return ad.ErroredResponse(err)
	}

	pod.SetNamespace(namespace)

	tnt, tErr := tenant.TenantByStatusNamespace(ctx, c, pod.Namespace)
	if tErr != nil {
		return ad.ErroredResponse(tErr)
	} else if tnt == nil {
		return nil
	}

	var err error

	pcMutated, pcErr := handlePriorityClassDefault(ctx, c, tnt.Spec.PriorityClasses, &pod)
	if pcErr != nil {
		return ad.ErroredResponse(pcErr)
	}

	rcMutated := handleRuntimeClassDefault(tnt.Spec.RuntimeClasses, &pod)
	if !rcMutated && !pcMutated {
		return nil
	}

	var marshaled []byte

	if marshaled, err = json.Marshal(pod); err != nil {
		return ad.ErroredResponse(err)
	}

	resp := admission.PatchResponseFromRaw(req.Object.Raw, marshaled)

	return &resp
}

func handleRuntimeClassDefault(allowed *api.DefaultAllowedListSpec, pod *corev1.Pod) (mutated bool) {
	if allowed == nil || allowed.Default == "" {
		return false
	}

	runtimeClass := pod.Spec.RuntimeClassName

	switch {
	case allowed.Default == "":
		return false
	case runtimeClass != nil && *runtimeClass != "":
		return false
	case runtimeClass != nil && *runtimeClass != allowed.Default:
		return false
	default:
		pod.Spec.RuntimeClassName = &allowed.Default

		return true
	}
}

func handlePriorityClassDefault(
	ctx context.Context,
	c client.Reader,
	allowed *api.DefaultAllowedListSpec,
	pod *corev1.Pod,
) (mutated bool, err error) {
	if allowed == nil || allowed.Default == "" {
		return false, nil
	}

	priorityClassPod := pod.Spec.PriorityClassName

	var cpc *schedulev1.PriorityClass
	// PriorityClass name is empty, if no GlobalDefault is set and no PriorityClass was given on pod
	if len(priorityClassPod) > 0 && priorityClassPod != allowed.Default {
		cpc, err = utils.GetPriorityClassByName(ctx, c, priorityClassPod)
		// Should not happen, since API already checks if PC present
		if err != nil {
			return false, caperrors.NewPriorityClassError(priorityClassPod, err)
		}
	} else {
		mutated = true
	}

	if mutated = mutated || (utils.IsDefaultPriorityClass(cpc) && cpc.GetName() != allowed.Default); !mutated {
		return false, nil
	}

	pc, err := utils.GetPriorityClassByName(ctx, c, allowed.Default)
	if err != nil {
		return false, fmt.Errorf("failed to assign tenant default Priority Class: %w", err)
	}

	pod.Spec.PreemptionPolicy = pc.PreemptionPolicy
	pod.Spec.Priority = &pc.Value
	pod.Spec.PriorityClassName = pc.Name

	return true, nil
}
