/*
Copyright 2022 The KubeVela Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cli

import (
	"context"
	_ "embed"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"cuelang.org/go/cue"
	"github.com/kubevela/pkg/cue/cuex"
	"github.com/spf13/cobra"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/release"
	"helm.sh/helm/v3/pkg/releaseutil"
	"helm.sh/helm/v3/pkg/storage"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	apitypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog/v2"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
	"k8s.io/utils/strings/slices"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/yaml"

	"github.com/kubevela/pkg/util/k8s"
	"github.com/kubevela/pkg/util/resourcetopology"
	velaslices "github.com/kubevela/pkg/util/slices"

	"github.com/kubevela/pkg/multicluster"

	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
	"github.com/oam-dev/kubevela/apis/types"
	velacmd "github.com/oam-dev/kubevela/pkg/cmd"
	cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
	"github.com/oam-dev/kubevela/pkg/oam"
	"github.com/oam-dev/kubevela/pkg/utils/apply"
	"github.com/oam-dev/kubevela/pkg/utils/env"
	"github.com/oam-dev/kubevela/pkg/utils/util"
)

const (
	adoptTypeNative   = "native"
	adoptTypeHelm     = "helm"
	adoptModeReadOnly = v1alpha1.ReadOnlyPolicyType
	adoptModeTakeOver = v1alpha1.TakeOverPolicyType
	helmDriverEnvKey  = "HELM_DRIVER"
	defaultHelmDriver = "secret"
	adoptCUETempVal   = "adopt"
	adoptCUETempFunc  = "#Adopt"
)

//go:embed adopt-templates/default.cue
var defaultAdoptTemplate string

//go:embed resource-topology/builtin-rule.cue
var defaultResourceTopologyRule string

var (
	adoptTypes = []string{adoptTypeNative, adoptTypeHelm}
	adoptModes = []string{adoptModeReadOnly, adoptModeTakeOver}
)

type resourceRef struct {
	schema.GroupVersionKind
	apitypes.NamespacedName
	Cluster string
	Arg     string
}

// AdoptOptions options for vela adopt command
type AdoptOptions struct {
	Type         string `json:"type"`
	Mode         string `json:"mode"`
	AppName      string `json:"appName"`
	AppNamespace string `json:"appNamespace"`

	HelmReleaseName      string
	HelmReleaseNamespace string
	HelmDriver           string
	HelmConfig           *action.Configuration
	HelmStore            *storage.Storage
	HelmRelease          *release.Release
	HelmReleaseRevisions []*release.Release

	NativeResourceRefs []*resourceRef

	Apply   bool
	Recycle bool
	Yes     bool
	All     bool

	AdoptTemplateFile     string
	AdoptTemplate         string
	AdoptTemplateCUEValue cue.Value

	ResourceTopologyRuleFile string
	ResourceTopologyRule     string
	AllGVKs                  []schema.GroupVersionKind

	Resources []*unstructured.Unstructured `json:"resources"`

	util.IOStreams
}

func (opt *AdoptOptions) parseResourceGVK(f velacmd.Factory, arg string) (schema.GroupVersionKind, error) {
	_, gr := schema.ParseResourceArg(arg)
	gvks, err := f.Client().RESTMapper().KindsFor(gr.WithVersion(""))
	if err != nil {
		return schema.GroupVersionKind{}, fmt.Errorf("failed to find types for resource %s: %w", arg, err)
	}
	if len(gvks) == 0 {
		return schema.GroupVersionKind{}, fmt.Errorf("no schema found for resource %s: %w", arg, err)
	}
	return gvks[0], nil
}

func (opt *AdoptOptions) parseResourceRef(f velacmd.Factory, cmd *cobra.Command, arg string) (*resourceRef, error) {
	parts := strings.Split(arg, "/")
	gvk, err := opt.parseResourceGVK(f, parts[0])
	if err != nil {
		return nil, err
	}
	mappings, err := f.Client().RESTMapper().RESTMappings(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil, fmt.Errorf("failed to find mappings for resource %s: %w", arg, err)
	}
	if len(mappings) == 0 {
		return nil, fmt.Errorf("no mappings found for resource %s: %w", arg, err)
	}
	mapping := mappings[0]
	or := &resourceRef{GroupVersionKind: gvk, Cluster: multicluster.Local, Arg: arg}
	switch len(parts) {
	case 2:
		or.Name = parts[1]
		if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
			or.Namespace = velacmd.GetNamespace(f, cmd)
			if or.Namespace == "" {
				or.Namespace = env.DefaultEnvNamespace
			}
		}
	case 3:
		or.Namespace = parts[1]
		or.Name = parts[2]
	case 4:
		or.Cluster = parts[1]
		or.Namespace = parts[2]
		or.Name = parts[3]
	default:
		return nil, fmt.Errorf("resource should be like <type>/<name> or <type>/<namespace>/<name> or <type>/<cluster>/<namespace>/<name>")
	}
	return or, nil
}

// Init .
func (opt *AdoptOptions) Init(f velacmd.Factory, cmd *cobra.Command, args []string) (err error) {
	if opt.All {
		if len(args) > 0 {
			for _, arg := range args {
				gvk, err := opt.parseResourceGVK(f, arg)
				if err != nil {
					return err
				}
				opt.AllGVKs = append(opt.AllGVKs, gvk)
				apiVersion, kind := gvk.ToAPIVersionAndKind()
				_, _ = fmt.Fprintf(opt.Out, "Adopt all %s/%s resources\n", apiVersion, kind)
			}
		}
		if len(opt.AllGVKs) == 0 {
			opt.AllGVKs = []schema.GroupVersionKind{
				appsv1.SchemeGroupVersion.WithKind("Deployment"),
				appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
				appsv1.SchemeGroupVersion.WithKind("DaemonSet"),
			}
			_, _ = opt.Out.Write([]byte("No arguments specified, adopt all Deployment/StatefulSet/DaemonSet resources by default\n"))
		}
	}
	if opt.AdoptTemplateFile != "" {
		bs, err := os.ReadFile(opt.AdoptTemplateFile)
		if err != nil {
			return fmt.Errorf("failed to load file %s", opt.AdoptTemplateFile)
		}
		opt.AdoptTemplate = string(bs)
	} else {
		opt.AdoptTemplate = defaultAdoptTemplate
	}
	if opt.ResourceTopologyRuleFile != "" {
		bs, err := os.ReadFile(opt.ResourceTopologyRuleFile)
		if err != nil {
			return fmt.Errorf("failed to load file %s", opt.ResourceTopologyRuleFile)
		}
		opt.ResourceTopologyRule = string(bs)
	} else {
		opt.ResourceTopologyRule = defaultResourceTopologyRule
	}
	opt.AppNamespace = velacmd.GetNamespace(f, cmd)
	opt.AdoptTemplateCUEValue, err = cuex.CompileString(cmd.Context(), fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
	if err != nil {
		return fmt.Errorf("failed to compile template: %w", err)
	}
	switch opt.Type {
	case adoptTypeNative:
		if opt.Recycle {
			return fmt.Errorf("native resource adoption does not support --recycle flag")
		}
	case adoptTypeHelm:
		if len(opt.HelmDriver) == 0 {
			opt.HelmDriver = os.Getenv(helmDriverEnvKey)
		}
		if len(opt.HelmDriver) == 0 {
			opt.HelmDriver = defaultHelmDriver
		}
		actionConfig := new(action.Configuration)
		opt.HelmReleaseNamespace = opt.AppNamespace
		if err := actionConfig.Init(
			util.NewRestConfigGetterByConfig(f.Config(), opt.HelmReleaseNamespace),
			opt.HelmReleaseNamespace,
			opt.HelmDriver,
			klog.Infof); err != nil {
			return err
		}
		opt.HelmConfig = actionConfig
	default:
		return fmt.Errorf("invalid adopt type: %s, available types: [%s]", opt.Type, strings.Join(adoptTypes, ", "))
	}
	if slices.Index(adoptModes, opt.Mode) < 0 {
		return fmt.Errorf("invalid adopt mode: %s, available modes: [%s]", opt.Mode, strings.Join(adoptModes, ", "))
	}
	if opt.Recycle && !opt.Apply {
		return fmt.Errorf("old data can only be recycled when the adoption application is applied")
	}
	return nil
}

// MultipleRun .
func (opt *AdoptOptions) MultipleRun(f velacmd.Factory, cmd *cobra.Command) error {
	resources := make([][]*unstructured.Unstructured, 0)
	releases := make([]*release.Release, 0)
	var err error
	ctx := context.Background()

	matchLabels := metav1.LabelSelector{
		MatchExpressions: []metav1.LabelSelectorRequirement{
			{
				Key:      oam.LabelAppName,
				Operator: metav1.LabelSelectorOpDoesNotExist,
			},
		},
	}
	selector, err := metav1.LabelSelectorAsSelector(&matchLabels)
	if err != nil {
		return err
	}

	switch opt.Type {
	case adoptTypeNative:
		for _, gvk := range opt.AllGVKs {
			list := &unstructured.UnstructuredList{}
			list.SetGroupVersionKind(gvk)
			if err := f.Client().List(ctx, list, &client.ListOptions{Namespace: opt.AppNamespace, LabelSelector: selector}); err != nil {
				apiVersion, kind := gvk.ToAPIVersionAndKind()
				_, _ = fmt.Fprintf(opt.Out, "Warning: failed to list resources from %s/%s: %s\n", apiVersion, kind, err.Error())
				continue
			}
			dedup := make([]k8s.ResourceIdentifier, 0)
			for _, item := range list.Items {
				engine := resourcetopology.New(opt.ResourceTopologyRule)
				itemIdentifier := k8s.ResourceIdentifier{
					Name:       item.GetName(),
					Namespace:  item.GetNamespace(),
					Kind:       item.GetKind(),
					APIVersion: item.GetAPIVersion(),
				}
				if velaslices.Contains(dedup, itemIdentifier) {
					continue
				}
				firstElement := item
				r := []*unstructured.Unstructured{&firstElement}
				peers, err := engine.GetPeerResources(ctx, itemIdentifier)
				if err != nil {
					_, _ = fmt.Fprintf(opt.Out, "Warning: failed to get peer resources for %s/%s: %s\n", itemIdentifier.APIVersion, itemIdentifier.Kind, err.Error())
					resources = append(resources, r)
					continue
				}
				dedup = append(dedup, peers...)
				for _, peer := range peers {
					gvk, err := k8s.GetGVKFromResource(peer)
					if err != nil {
						_, _ = fmt.Fprintf(opt.Out, "Warning: failed to get gvk from resource %s/%s: %s\n", peer.APIVersion, peer.Kind, err.Error())
						continue
					}
					peerResource := &unstructured.Unstructured{}
					peerResource.SetGroupVersionKind(gvk)
					if err := f.Client().Get(ctx, apitypes.NamespacedName{Namespace: peer.Namespace, Name: peer.Name}, peerResource); err != nil {
						_, _ = fmt.Fprintf(opt.Out, "Warning: failed to get resource %s/%s: %s\n", peer.Namespace, peer.Name, err.Error())
						continue
					}
					r = append(r, peerResource)
				}
				resources = append(resources, r)
			}
		}
	case adoptTypeHelm:
		releases, err = opt.HelmConfig.Releases.List(func(release *release.Release) bool {
			return true
		})
		if err != nil {
			return err
		}
	}
	for _, r := range resources {
		opt.Resources = r
		opt.AppName = r[0].GetName()
		opt.AppNamespace = r[0].GetNamespace()
		if err := opt.Run(f, cmd); err != nil {
			_, _ = fmt.Fprintf(opt.Out, "Error: failed to adopt %s/%s: %s", opt.AppNamespace, opt.AppName, err.Error())
			continue
		}
	}
	for _, r := range releases {
		opt.AppName = r.Name
		opt.AppNamespace = r.Namespace
		opt.HelmReleaseName = r.Name
		opt.HelmReleaseNamespace = r.Namespace
		// TODO(fog): filter the helm that already adopted by vela
		if err := opt.loadHelm(); err != nil {
			_, _ = fmt.Fprintf(opt.Out, "Error: failed to load helm for %s/%s: %s", opt.AppNamespace, opt.AppName, err.Error())
			continue
		}
		if err := opt.Run(f, cmd); err != nil {
			_, _ = fmt.Fprintf(opt.Out, "Error: failed to adopt %s/%s: %s", opt.AppNamespace, opt.AppName, err.Error())
			continue
		}
	}
	return nil
}

// Complete autofill fields in opts
func (opt *AdoptOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []string) (err error) {
	opt.AppNamespace = velacmd.GetNamespace(f, cmd)
	switch opt.Type {
	case adoptTypeNative:
		for _, arg := range args {
			or, err := opt.parseResourceRef(f, cmd, arg)
			if err != nil {
				return err
			}
			opt.NativeResourceRefs = append(opt.NativeResourceRefs, or)
		}
		if opt.AppName == "" && velaslices.All(opt.NativeResourceRefs, func(ref *resourceRef) bool {
			return ref.Name == opt.NativeResourceRefs[0].Name
		}) {
			opt.AppName = opt.NativeResourceRefs[0].Name
		}
		if opt.AppNamespace == "" {
			opt.AppNamespace = opt.NativeResourceRefs[0].Namespace
		}
		if err := opt.loadNative(f, cmd); err != nil {
			return err
		}
	case adoptTypeHelm:
		if len(args) > 0 {
			opt.HelmReleaseName = args[0]
		}
		if len(args) > 1 {
			return fmt.Errorf("helm type adoption only support one helm release by far")
		}
		if opt.AppName == "" {
			opt.AppName = opt.HelmReleaseName
		}
		if err := opt.loadHelm(); err != nil {
			return err
		}
	default:
	}
	if opt.AppName != "" {
		app := &v1beta1.Application{}
		err := f.Client().Get(cmd.Context(), apitypes.NamespacedName{Namespace: opt.AppNamespace, Name: opt.AppName}, app)
		if err == nil && app != nil {
			if !opt.Yes && opt.Apply {
				userInput := NewUserInput()
				confirm := userInput.AskBool(
					fmt.Sprintf("Application '%s' already exists, apply will override the existing app with the adopted one, please confirm [Y/n]: ", opt.AppName),
					&UserInputOptions{AssumeYes: false})
				if !confirm {
					return nil
				}
			}
		}
	}
	opt.AdoptTemplateCUEValue, err = cuex.CompileString(cmd.Context(), fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
	if err != nil {
		return fmt.Errorf("failed to compile cue template: %w", err)
	}
	return err
}

// Validate if opts is valid
func (opt *AdoptOptions) Validate() error {
	switch opt.Type {
	case adoptTypeNative:
		if len(opt.NativeResourceRefs) == 0 {
			return fmt.Errorf("at least one resource should be specified")
		}
		if opt.AppName == "" {
			return fmt.Errorf("app-name flag must be set for native resource adoption when multiple resources have different names")
		}
	case adoptTypeHelm:
		if len(opt.HelmReleaseName) == 0 {
			return fmt.Errorf("helm release name must not be empty")
		}
	}
	return nil
}

func (opt *AdoptOptions) loadNative(f velacmd.Factory, cmd *cobra.Command) error {
	for _, ref := range opt.NativeResourceRefs {
		obj := &unstructured.Unstructured{}
		obj.SetGroupVersionKind(ref.GroupVersionKind)
		if err := f.Client().Get(multicluster.WithCluster(cmd.Context(), ref.Cluster), apitypes.NamespacedName{Namespace: ref.Namespace, Name: ref.Name}, obj); err != nil {
			return fmt.Errorf("fail to get resource for %s: %w", ref.Arg, err)
		}
		_ = k8s.AddLabel(obj, oam.LabelAppCluster, ref.Cluster)
		opt.Resources = append(opt.Resources, obj)
	}
	return nil
}

func (opt *AdoptOptions) loadHelm() error {
	opt.HelmStore = opt.HelmConfig.Releases
	revisions, err := opt.HelmStore.History(opt.HelmReleaseName)
	if err != nil {
		return fmt.Errorf("helm release %s/%s not loaded: %w", opt.HelmReleaseNamespace, opt.HelmReleaseName, err)
	}
	if len(revisions) == 0 {
		return fmt.Errorf("helm release %s/%s not found", opt.HelmReleaseNamespace, opt.HelmReleaseName)
	}
	releaseutil.SortByRevision(revisions)
	opt.HelmRelease = revisions[len(revisions)-1]
	opt.HelmReleaseRevisions = revisions
	manifests := releaseutil.SplitManifests(opt.HelmRelease.Manifest)
	var objs []*unstructured.Unstructured
	for _, val := range manifests {
		obj := &unstructured.Unstructured{}
		if err = yaml.Unmarshal([]byte(val), obj); err != nil {
			klog.Warningf("unable to decode object %s: %s", val, err)
			continue
		}
		_ = k8s.AddLabel(obj, oam.LabelAppCluster, multicluster.Local)
		objs = append(objs, obj)
	}
	opt.Resources = objs
	return nil
}

func (opt *AdoptOptions) render() (*v1beta1.Application, error) {
	app := &v1beta1.Application{}
	val := opt.AdoptTemplateCUEValue.FillPath(cue.ParsePath(adoptCUETempVal+".$args"), opt)
	bs, err := val.LookupPath(cue.ParsePath(adoptCUETempVal + ".$returns")).MarshalJSON()
	if err != nil {
		return nil, fmt.Errorf("failed to parse adoption template: %w", err)
	}
	if err = json.Unmarshal(bs, app); err != nil {
		return nil, fmt.Errorf("failed to parse template $returns into application: %w", err)
	}
	if app.Name == "" {
		app.Name = opt.AppName
	}
	if app.Namespace == "" {
		app.Namespace = opt.AppNamespace
	}
	return app, nil
}

// Run collect resources, assemble into application and print/apply
func (opt *AdoptOptions) Run(f velacmd.Factory, cmd *cobra.Command) error {
	app, err := opt.render()
	if err != nil {
		return fmt.Errorf("failed to make adoption application for resources: %w", err)
	}
	if opt.Apply {
		if err = apply.NewAPIApplicator(f.Client()).Apply(cmd.Context(), app); err != nil {
			return fmt.Errorf("failed to apply application %s/%s: %w", app.Namespace, app.Name, err)
		}
		_, _ = fmt.Fprintf(opt.Out, "resources adopted in app %s/%s\n", app.Namespace, app.Name)
	} else {
		var bs []byte
		if bs, err = yaml.Marshal(app); err != nil {
			return fmt.Errorf("failed to encode application into YAML format: %w", err)
		}
		if opt.All {
			_, _ = opt.Out.Write([]byte("\n---\n"))
		}
		_, _ = opt.Out.Write(bs)
	}
	if opt.Recycle && opt.Apply {
		spinner := newTrackingSpinner("")
		spinner.Writer = opt.Out
		spinner.Start()
		err = wait.PollImmediate(time.Second, time.Minute, func() (done bool, err error) {
			_app := &v1beta1.Application{}
			if err = f.Client().Get(cmd.Context(), client.ObjectKeyFromObject(app), _app); err != nil {
				return false, err
			}
			spinner.UpdateCharSet([]string{fmt.Sprintf("waiting application %s/%s to be running, current status: %s", app.Namespace, app.Name, _app.Status.Phase)})
			return _app.Status.Phase == common.ApplicationRunning, nil
		})
		spinner.Stop()
		if err != nil {
			return fmt.Errorf("failed to wait application %s/%s to be running: %w", app.Namespace, app.Name, err)
		}
		switch opt.Type {
		case adoptTypeHelm:
			for _, r := range opt.HelmReleaseRevisions {
				if _, err = opt.HelmStore.Delete(r.Name, r.Version); err != nil {
					return fmt.Errorf("failed to clean up helm release: %w", err)
				}
			}
			_, _ = fmt.Fprintf(opt.Out, "successfully clean up old helm release\n")
		default:
		}
	}
	return nil
}

var (
	adoptLong = templates.LongDesc(i18n.T(`
		Adopt resources into applications

		Adopt resources into a KubeVela application. This command is useful when you already
		have resources applied in your Kubernetes cluster. These resources could be applied
		natively or with other tools, such as Helm. This command will automatically find out
		the resources to be adopted and assemble them into a new application which won't 
		trigger any damage such as restart on the adoption.

		There are two types of adoption supported by far, 'native' Kubernetes resources (by
		default) and 'helm' releases.
		1. For 'native' type, you can specify a list of resources you want to adopt in the
		application. Only resources in local cluster are supported for now.
		2. For 'helm' type, you can specify a helm release name. This helm release should
		be already published in the local cluster. The command will find the resources
		managed by the helm release and convert them into an adoption application.

		There are two working mechanism (called 'modes' here) for the adoption by far, 
		'read-only' mode (by default) and 'take-over' mode.
		1. In 'read-only' mode, adopted resources will not be touched. You can leverage vela 
		tools (like Vela CLI or VelaUX) to observe those resources and attach traits to add 
		new capabilities. The adopted resources will not be recycled or updated. This mode
		is recommended if you still want to keep using other tools to manage resources updates
		or deletion, like Helm.
		2. In 'take-over' mode, adopted resources are completely managed by application which 
		means they can be modified. You can use traits or directly modify the component to make
		edits to those resources. This mode can be helpful if you want to migrate existing 
		resources into KubeVela system and let KubeVela to handle the life-cycle of target
		resources.

		The adopted application can be customized. You can provide a CUE template file to
		the command and make your own assemble rules for the adoption application. You can
		refer to https://github.com/kubevela/kubevela/blob/master/references/cli/adopt-templates/default.cue
		to see the default implementation of adoption rules.

		If you want to adopt all resources with resource topology rule to Applications,
		you can use: 'vela adopt --all'. The resource topology rule can be customized by
		'--resource-topology-rule' flag.
	`))
	adoptExample = templates.Examples(i18n.T(`
		# Native Resources Adoption

		## Adopt resources into new application

		## Adopt all resources to Applications with resource topology rule
		## Use: vela adopt <resources-type> --all
		vela adopt --all
		vela adopt deployment --all --resource-topology-rule myrule.cue

		## Use: vela adopt <resources-type>[/<resource-cluster>][/<resource-namespace>]/<resource-name> <resources-type>[/<resource-cluster>][/<resource-namespace>]/<resource-name> ...
		vela adopt deployment/my-app configmap/my-app

		## Adopt resources into new application with specified app name
		vela adopt deployment/my-deploy configmap/my-config --app-name my-app

		## Adopt resources into new application in specified namespace
		vela adopt deployment/my-app configmap/my-app -n demo

		## Adopt resources into new application across multiple namespace
		vela adopt deployment/ns-1/my-app configmap/ns-2/my-app

		## Adopt resources into new application with take-over mode
		vela adopt deployment/my-app configmap/my-app --mode take-over

		## Adopt resources into new application and apply it into cluster
		vela adopt deployment/my-app configmap/my-app --apply

		-----------------------------------------------------------

		# Helm Chart Adoption

		## Adopt all helm releases to Applications with resource topology rule
		## Use: vela adopt <resources-type> --all
		vela adopt --all --type helm
		vela adopt my-chart --all --resource-topology-rule myrule.cue --type helm

		## Adopt resources in a deployed helm chart
		vela adopt my-chart -n my-namespace --type helm
		
		## Adopt resources in a deployed helm chart with take-over mode
		vela adopt my-chart --type helm --mode take-over

		## Adopt resources in a deployed helm chart in an application and apply it into cluster
		vela adopt my-chart --type helm --apply

		## Adopt resources in a deployed helm chart in an application, apply it into cluster, and recycle the old helm release after the adoption application successfully runs
		vela adopt my-chart --type helm --apply --recycle

		-----------------------------------------------------------

		## Customize your adoption rules
		vela adopt my-chart -n my-namespace --type helm --adopt-template my-rules.cue
	`))
)

// NewAdoptCommand command for adopt resources into KubeVela Application
func NewAdoptCommand(f velacmd.Factory, order string, streams util.IOStreams) *cobra.Command {
	o := &AdoptOptions{
		Type:      adoptTypeNative,
		Mode:      adoptModeReadOnly,
		IOStreams: streams,
	}
	cmd := &cobra.Command{
		Use:     "adopt",
		Short:   i18n.T("Adopt resources into new application."),
		Long:    adoptLong,
		Example: adoptExample,
		Annotations: map[string]string{
			types.TagCommandType:  types.TypeCD,
			types.TagCommandOrder: order,
		},
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Init(f, cmd, args))
			if o.All {
				cmdutil.CheckErr(o.MultipleRun(f, cmd))
				return
			}
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.Run(f, cmd))
		},
	}
	cmd.Flags().StringVarP(&o.Type, "type", "t", o.Type, fmt.Sprintf("The type of adoption. Available values: [%s]", strings.Join(adoptTypes, ", ")))
	cmd.Flags().StringVarP(&o.Mode, "mode", "m", o.Mode, fmt.Sprintf("The mode of adoption. Available values: [%s]", strings.Join(adoptModes, ", ")))
	cmd.Flags().StringVarP(&o.AppName, "app-name", "", o.AppName, "The name of application for adoption. If empty for helm type adoption, it will inherit the helm chart's name.")
	cmd.Flags().StringVarP(&o.AdoptTemplateFile, "adopt-template", "", o.AdoptTemplate, "The CUE template for adoption. If not provided, the default template will be used when --auto is switched on.")
	cmd.Flags().StringVarP(&o.ResourceTopologyRuleFile, "resource-topology-rule", "", o.ResourceTopologyRule, "The CUE template for specify the rule of the resource topology. If not provided, the default rule will be used.")
	cmd.Flags().StringVarP(&o.HelmDriver, "driver", "d", o.HelmDriver, "The storage backend of helm adoption. Only take effect when --type=helm.")
	cmd.Flags().BoolVarP(&o.Apply, "apply", "", o.Apply, "If true, the application for adoption will be applied. Otherwise, it will only be printed.")
	cmd.Flags().BoolVarP(&o.Recycle, "recycle", "", o.Recycle, "If true, when the adoption application is successfully applied, the old storage (like Helm secret) will be recycled.")
	cmd.Flags().BoolVarP(&o.Yes, "yes", "y", o.Yes, "Skip confirmation prompt")
	cmd.Flags().BoolVarP(&o.All, "all", "", o.All, "Adopt all resources in the namespace")
	return velacmd.NewCommandBuilder(f, cmd).
		WithNamespaceFlag().
		WithResponsiveWriter().
		Build()
}
