// Copyright 2023 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package remove

import (
	"errors"
	"fmt"
	"log"
	"slices"
	"strings"

	"github.com/spf13/cobra"
	"sigs.k8s.io/kustomize/api/konfig"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile"
	"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/util"
	"sigs.k8s.io/kustomize/kyaml/filesys"
)

type removeConfigMapOptions struct {
	configMapNamesToRemove []string
	namespace              string
}

// newCmdRemoveConfigMap removes configMapGenerator(s) with the specified name(s).
func newCmdRemoveConfigMap(fSys filesys.FileSystem) *cobra.Command {
	var flags removeConfigMapOptions

	cmd := &cobra.Command{
		Use: "configmap NAME [,NAME] [--namespace=namespace-name]",
		Short: "Removes the specified configmap(s) from " +
			konfig.DefaultKustomizationFileName(),
		Long: `Removes the specified configmap(s) from the ` + konfig.DefaultKustomizationFileName() + ` file in the specified namespace.
If multiple configmap names are specified, the command will not fail on secret names that were not found in the file,
but will issue a warning for each name that wasn't found.`,
		Example: `
    # Removes a single configmap named 'my-configmap' in the default namespace from the ` + konfig.DefaultKustomizationFileName() + ` file
    kustomize edit remove configmap my-configmap

    # Removes configmaps named 'my-configmap' and 'other-configmap' in namespace 'test-namespace' from the ` + konfig.DefaultKustomizationFileName() + ` file
    kustomize edit remove configmap my-configmap,other-configmap --namespace=test-namespace
`,
		RunE: func(cmd *cobra.Command, args []string) error {
			err := flags.Validate(args)
			if err != nil {
				return err
			}
			return flags.RunRemoveConfigMap(fSys)
		},
	}

	cmd.Flags().StringVar(
		&flags.namespace,
		util.NamespaceFlag,
		"",
		"Namespace to remove ConfigMap(s) from",
	)

	return cmd
}

// Validate validates removeConfigMap command.
func (o *removeConfigMapOptions) Validate(args []string) error {
	switch {
	case len(args) == 0:
		return errors.New("at least one configmap name must be specified")
	case len(args) > 1:
		return fmt.Errorf("too many arguments: %s; to provide multiple configmaps to remove, please separate configmap names by commas", args)
	}

	o.configMapNamesToRemove = strings.Split(args[0], ",")
	return nil
}

// RunRemoveConfigMap runs ConfigMap command (do real work).
func (o *removeConfigMapOptions) RunRemoveConfigMap(fSys filesys.FileSystem) error {
	mf, err := kustfile.NewKustomizationFile(fSys)
	if err != nil {
		return fmt.Errorf("could not read kustomization file: %w", err)
	}

	m, err := mf.Read()
	if err != nil {
		return fmt.Errorf("could not read kustomization file contents: %w", err)
	}

	foundConfigMaps := make(map[string]struct{})
	remainingConfigMaps := make([]types.ConfigMapArgs, 0, len(m.ConfigMapGenerator))

	for _, currentConfigMap := range m.ConfigMapGenerator {
		if slices.Contains(o.configMapNamesToRemove, currentConfigMap.Name) &&
			util.NamespaceEqual(currentConfigMap.Namespace, o.namespace) {
			foundConfigMaps[currentConfigMap.Name] = struct{}{}
			continue
		}

		remainingConfigMaps = append(remainingConfigMaps, currentConfigMap)
	}

	if len(foundConfigMaps) == 0 {
		return fmt.Errorf("no specified configmap(s) were found in the %s file",
			konfig.DefaultKustomizationFileName())
	}

	for _, name := range o.configMapNamesToRemove {
		if _, found := foundConfigMaps[name]; !found {
			log.Printf("configmap %s doesn't exist in kustomization file", name)
		}
	}

	m.ConfigMapGenerator = remainingConfigMaps
	err = mf.Write(m)
	if err != nil {
		return fmt.Errorf("failed to write kustomization file: %w", err)
	}
	return nil
}
