package ack

import (
	"context"
	"fmt"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
)

// daemonSetRequests adjusts some built-in DaemonSet pods' resource requests,
// like kube-proxy, csi-plugin, terway-eniip, etc.
// Because these DaemonSets are created by ACK automatically with high resource requests,
// which results in insufficient resources on admin nodes for our workloads, like o11y pods.
// Actually, these DaemonSets do not need that many resources to run stably.
// So we lower their resource requests to make sure our workloads can be scheduled successfully.
type daemonSetRequests struct {
	*task.BaseTask
}

func NewDaemonSetRequests(ctx *task.Context) deploy.Task {
	return &daemonSetRequests{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func (t *daemonSetRequests) Name() string {
	return "DaemonSetRequests"
}

func (t *daemonSetRequests) Create(ctx context.Context) deploy.Result {
	// If DaemonSetRequests config is not set or empty, skip modification
	if len(t.Config.APPBoxConfig.DaemonSetRequests) == 0 {
		t.Logger.Info("DaemonSetRequests config not set or empty, skipping DaemonSet modification")
		return deploy.Done()
	}

	// Get remote Kubernetes client
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	// Modify each DaemonSet specified in the config
	// Collect all errors and continue processing remaining DaemonSets
	var errs []error
	successCount := 0
	for daemonSetName, cpuRequest := range t.Config.APPBoxConfig.DaemonSetRequests {
		if err := t.modifyDaemonSetCPURequests(ctx, cli, daemonSetName, cpuRequest); err != nil {
			t.Logger.Error(err, "Failed to modify DaemonSet, continuing with next",
				"daemonset", daemonSetName,
				"cpuRequest", cpuRequest)
			errs = append(errs, fmt.Errorf("DaemonSet %s: %w", daemonSetName, err))
		} else {
			successCount++
		}
	}

	// Log summary
	t.Logger.Info("DaemonSet CPU requests modification completed",
		"total", len(t.Config.APPBoxConfig.DaemonSetRequests),
		"success", successCount,
		"failed", len(errs))

	// Return error if any DaemonSet modification failed
	if len(errs) > 0 {
		return deploy.Error(fmt.Errorf("failed to modify %d DaemonSet(s): %v", len(errs), errs))
	}

	return deploy.Done()
}

// modifyDaemonSetCPURequests modifies the CPU requests for all containers in a DaemonSet
func (t *daemonSetRequests) modifyDaemonSetCPURequests(ctx context.Context, cli client.Client, daemonSetName, cpuRequest string) error {
	const namespace = "kube-system"

	// Get the DaemonSet
	ds := &appsv1.DaemonSet{}
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: namespace,
		Name:      daemonSetName,
	}, ds); err != nil {
		if apierrors.IsNotFound(err) {
			t.Logger.Info("DaemonSet not found, skipping",
				"namespace", namespace,
				"name", daemonSetName)
			return nil
		}
		return fmt.Errorf("failed to get DaemonSet %s/%s: %w", namespace, daemonSetName, err)
	}

	// Parse the CPU request value
	cpuQuantity, err := resource.ParseQuantity(cpuRequest)
	if err != nil {
		return fmt.Errorf("invalid CPU request value %s: %w", cpuRequest, err)
	}

	// Create a deep copy for comparison
	origin := ds.DeepCopy()
	modified := false

	// Update CPU requests for all regular containers
	if t.updateContainersCPURequests(ds.Spec.Template.Spec.Containers, cpuQuantity, daemonSetName) {
		modified = true
	}

	// Update CPU requests for all init containers
	if t.updateContainersCPURequests(ds.Spec.Template.Spec.InitContainers, cpuQuantity, daemonSetName) {
		modified = true
	}

	// If no changes needed, skip patch
	if !modified {
		t.Logger.Info("DaemonSet already up to date",
			"namespace", namespace,
			"name", daemonSetName)
		return nil
	}

	// Patch the DaemonSet
	if err := cli.Patch(ctx, ds, client.MergeFrom(origin)); err != nil {
		return fmt.Errorf("failed to patch DaemonSet %s/%s: %w", namespace, daemonSetName, err)
	}

	t.Logger.Info("Successfully updated DaemonSet CPU requests",
		"namespace", namespace,
		"name", daemonSetName,
		"cpuRequest", cpuRequest)
	return nil
}

// updateContainersCPURequests updates CPU requests for a list of containers
// Only modifies containers that already have CPU requests set
// Returns true if any container was modified
func (t *daemonSetRequests) updateContainersCPURequests(
	containers []corev1.Container,
	cpuQuantity resource.Quantity,
	daemonSetName string,
) bool {
	modified := false

	for i := range containers {
		container := &containers[i]

		// Skip if Resources.Requests is nil or CPU request is not set
		if container.Resources.Requests == nil {
			continue
		}

		currentCPU, exists := container.Resources.Requests[corev1.ResourceCPU]
		if !exists {
			// CPU request not set, skip this container
			continue
		}

		// Only update if the value is different from the target
		if currentCPU.Cmp(cpuQuantity) != 0 {
			t.Logger.Info("Updating CPU request",
				"daemonset", daemonSetName,
				"name", container.Name,
				"from", currentCPU.String(),
				"to", cpuQuantity.String())
			container.Resources.Requests[corev1.ResourceCPU] = cpuQuantity
			modified = true
		}
	}

	return modified
}
