package overridemanager

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/klog/v2"

	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
	"github.com/karmada-io/karmada/pkg/util"
)

const (
	// CommandString command string
	CommandString = "command"
	// ArgsString args string
	ArgsString = "args"
)

// buildCommandArgsPatches build JSON patches for the resource object according to override declaration.
func buildCommandArgsPatches(target string, rawObj *unstructured.Unstructured, commandRunOverrider *policyv1alpha1.CommandArgsOverrider) ([]overrideOption, error) {
	switch rawObj.GetKind() {
	case util.PodKind:
		return buildCommandArgsPatchesWithPath(target, "spec/containers", rawObj, commandRunOverrider)
	case util.ReplicaSetKind:
		fallthrough
	case util.DeploymentKind:
		fallthrough
	case util.DaemonSetKind:
		fallthrough
	case util.StatefulSetKind:
		return buildCommandArgsPatchesWithPath(target, "spec/template/spec/containers", rawObj, commandRunOverrider)
	}
	return nil, nil
}
func buildCommandArgsPatchesWithPath(target string, specContainersPath string, rawObj *unstructured.Unstructured, commandRunOverrider *policyv1alpha1.CommandArgsOverrider) ([]overrideOption, error) {
	patches := make([]overrideOption, 0)
	containers, ok, err := unstructured.NestedSlice(rawObj.Object, strings.Split(specContainersPath, pathSplit)...)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieves path(%s) from rawObj, error: %v", specContainersPath, err)
	}
	if !ok || len(containers) == 0 {
		return nil, nil
	}
	klog.V(4).Infof("buildCommandArgsPatchesWithPath containers info (%+v)", containers)
	for index, container := range containers {
		if container.(map[string]interface{})["name"] == commandRunOverrider.ContainerName {
			commandArgsPath := fmt.Sprintf("/%s/%d/%s", specContainersPath, index, target)
			commandArgsValue := make([]string, 0)
			var patch overrideOption
			// if target is nil, to add new [target]
			if container.(map[string]interface{})[target] == nil {
				patch, _ = acquireAddOverrideOption(commandArgsPath, commandRunOverrider)
			} else {
				for _, val := range container.(map[string]interface{})[target].([]interface{}) {
					commandArgsValue = append(commandArgsValue, fmt.Sprintf("%s", val))
				}
				patch, _ = acquireReplaceOverrideOption(commandArgsPath, commandArgsValue, commandRunOverrider)
			}

			klog.V(4).Infof("[buildCommandArgsPatchesWithPath] containers patch info (%+v)", patch)
			patches = append(patches, patch)
		}
	}
	return patches, nil
}

func acquireAddOverrideOption(commandArgsPath string, commandOverrider *policyv1alpha1.CommandArgsOverrider) (overrideOption, error) {
	if !strings.HasPrefix(commandArgsPath, pathSplit) {
		return overrideOption{}, fmt.Errorf("internal error: [acquireCommandOverrideOption] commandRunPath should be start with / character")
	}
	newCommandArgs, err := overrideCommandArgs([]string{}, commandOverrider)
	if err != nil {
		return overrideOption{}, err
	}
	return overrideOption{
		Op:    string(policyv1alpha1.OverriderOpAdd),
		Path:  commandArgsPath,
		Value: newCommandArgs,
	}, nil
}

func acquireReplaceOverrideOption(commandArgsPath string, commandArgsValue []string, commandOverrider *policyv1alpha1.CommandArgsOverrider) (overrideOption, error) {
	if !strings.HasPrefix(commandArgsPath, pathSplit) {
		return overrideOption{}, fmt.Errorf("internal error: [acquireCommandOverrideOption] commandRunPath should be start with / character")
	}

	newCommandArgs, err := overrideCommandArgs(commandArgsValue, commandOverrider)
	if err != nil {
		return overrideOption{}, err
	}

	return overrideOption{
		Op:    string(policyv1alpha1.OverriderOpReplace),
		Path:  commandArgsPath,
		Value: newCommandArgs,
	}, nil
}

func overrideCommandArgs(curCommandArgs []string, commandArgsOverrider *policyv1alpha1.CommandArgsOverrider) ([]string, error) {
	var newCommandArgs []string
	switch commandArgsOverrider.Operator {
	case policyv1alpha1.OverriderOpAdd:
		newCommandArgs = append(curCommandArgs, commandArgsOverrider.Value...)
	case policyv1alpha1.OverriderOpRemove:
		newCommandArgs = commandArgsRemove(curCommandArgs, commandArgsOverrider.Value)
	default:
		newCommandArgs = curCommandArgs
		klog.V(4).Infof("[overrideCommandArgs], op: %s , op not supported, ignored.", policyv1alpha1.OverriderOpRemove)
	}
	return newCommandArgs, nil
}

func commandArgsRemove(curCommandArgs []string, removeValues []string) []string {
	newCommandArgs := make([]string, 0, len(curCommandArgs))
	currentSet := sets.NewString(removeValues...)
	for _, val := range curCommandArgs {
		if !currentSet.Has(val) {
			newCommandArgs = append(newCommandArgs, val)
		}
	}
	return newCommandArgs
}
