func (a ApplicationType) String() string {
	switch a {
	case ApplicationTypeKustomize:
		return "Kustomize"
	case ApplicationTypeHelm:
		return "Helm"
	case ApplicationTypeUnsupported:
		return "Unsupported"
	default:
		return "Unknown"
	}
}
String returns a string representation of the application type
package argocd
import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"github.com/argoproj-labs/argocd-image-updater/pkg/common"
	"github.com/argoproj-labs/argocd-image-updater/pkg/image"
	"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
	"github.com/argoproj-labs/argocd-image-updater/pkg/log"
	"github.com/argoproj-labs/argocd-image-updater/pkg/metrics"
	argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
	"k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type k8sClient struct {
	kubeClient *kube.KubernetesClient
}
func (client *k8sClient) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) {
	return client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, appName, v1.GetOptions{})
}
func (client *k8sClient) ListApplications() ([]v1alpha1.Application, error) {
	list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).List(context.TODO(), v1.ListOptions{})
	if err != nil {
		return nil, err
	}
	return list.Items, nil
}
func (client *k8sClient) UpdateSpec(ctx context.Context, spec *application.ApplicationUpdateSpecRequest) (*v1alpha1.ApplicationSpec, error) {
	for {
		app, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, spec.GetName(), v1.GetOptions{})
		if err != nil {
			return nil, err
		}
		app.Spec = spec.Spec
		updatedApp, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Update(ctx, app, v1.UpdateOptions{})
		if err != nil {
			if errors.IsConflict(err) {
				continue
			}
			return nil, err
		}
		return &updatedApp.Spec, nil
	}
}
func NewK8SClient(kubeClient *kube.KubernetesClient) (ArgoCD, error) {
	return &k8sClient{kubeClient: kubeClient}, nil
}
type argoCD struct {
	Client argocdclient.Client
}
type ArgoCD interface {
	GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error)
	ListApplications() ([]v1alpha1.Application, error)
	UpdateSpec(ctx context.Context, spec *application.ApplicationUpdateSpecRequest) (*v1alpha1.ApplicationSpec, error)
}
type ApplicationType int
const (
	ApplicationTypeUnsupported ApplicationType = 0
	ApplicationTypeHelm        ApplicationType = 1
	ApplicationTypeKustomize   ApplicationType = 2
)
type ClientOptions struct {
	ServerAddr      string
	Insecure        bool
	Plaintext       bool
	Certfile        string
	GRPCWeb         bool
	GRPCWebRootPath string
	AuthToken       string
}
func NewAPIClient(opts *ClientOptions) (ArgoCD, error) {
	envAuthToken := os.Getenv("ARGOCD_TOKEN")
	if envAuthToken != "" && opts.AuthToken == "" {
		opts.AuthToken = envAuthToken
	}
	rOpts := argocdclient.ClientOptions{
		ServerAddr:      opts.ServerAddr,
		PlainText:       opts.Plaintext,
		Insecure:        opts.Insecure,
		CertFile:        opts.Certfile,
		GRPCWeb:         opts.GRPCWeb,
		GRPCWebRootPath: opts.GRPCWebRootPath,
		AuthToken:       opts.AuthToken,
	}
	client, err := argocdclient.NewClient(&rOpts)
	if err != nil {
		return nil, err
	}
	return &argoCD{Client: client}, nil
}
type ApplicationImages struct {
	Application v1alpha1.Application
	Images      image.ContainerImageList
}
type ImageList map[string]ApplicationImages
func nameMatchesPattern(name string, patterns []string) bool {
	if len(patterns) == 0 {
		return true
	}
	for _, p := range patterns {
		log.Tracef("Matching application name %s against pattern %s", name, p)
		if m, err := filepath.Match(p, name); err != nil {
			log.Warnf("Invalid application name pattern '%s': %v", p, err)
		} else if m {
			return true
		}
	}
	return false
}
func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string) (map[string]ApplicationImages, error) {
	var appsForUpdate = make(map[string]ApplicationImages)
	for _, app := range apps {
		if !IsValidApplicationType(&app) {
			log.Tracef("skipping app '%s' of type '%s' because it's not of supported source type", app.GetName(), app.Status.SourceType)
			continue
		}
		if !nameMatchesPattern(app.GetName(), patterns) {
			log.Tracef("Skipping app '%s' because it does not match requested patterns", app.GetName())
			continue
		}
		annotations := app.GetAnnotations()
		if _, ok := annotations[common.ImageUpdaterAnnotation]; !ok {
			log.Tracef("skipping app '%s' of type '%s' because required annotation is missing", app.GetName(), app.Status.SourceType)
			continue
		} else {
			log.Tracef("processing app '%s' of type '%v'", app.GetName(), app.Status.SourceType)
			imageList := parseImageList(annotations)
			appImages := ApplicationImages{}
			appImages.Application = app
			appImages.Images = *imageList
			appsForUpdate[app.GetName()] = appImages
		}
	}
	return appsForUpdate, nil
}
func parseImageList(annotations map[string]string) *image.ContainerImageList {
	results := make(image.ContainerImageList, 0)
	if updateImage, ok := annotations[common.ImageUpdaterAnnotation]; ok {
		splits := strings.Split(updateImage, ",")
		for _, s := range splits {
			img := image.NewFromIdentifier(strings.TrimSpace(s))
			if kustomizeImage := img.GetParameterKustomizeImageName(annotations); kustomizeImage != "" {
				img.KustomizeImage = image.NewFromIdentifier(kustomizeImage)
			}
			results = append(results, img)
		}
	}
	return &results
}
func (client *argoCD) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) {
	conn, appClient, err := client.Client.NewApplicationClient()
	metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
	if err != nil {
		metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
		return nil, err
	}
	defer conn.Close()
	metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
	app, err := appClient.Get(ctx, &application.ApplicationQuery{Name: &appName})
	if err != nil {
		metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
		return nil, err
	}
	return app, nil
}
func (client *argoCD) ListApplications() ([]v1alpha1.Application, error) {
	conn, appClient, err := client.Client.NewApplicationClient()
	metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
	if err != nil {
		metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
		return nil, err
	}
	defer conn.Close()
	metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
	apps, err := appClient.List(context.TODO(), &application.ApplicationQuery{})
	if err != nil {
		metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
		return nil, err
	}
	return apps.Items, nil
}
func (client *argoCD) UpdateSpec(ctx context.Context, in *application.ApplicationUpdateSpecRequest) (*v1alpha1.ApplicationSpec, error) {
	conn, appClient, err := client.Client.NewApplicationClient()
	metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
	if err != nil {
		metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
		return nil, err
	}
	defer conn.Close()
	metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
	spec, err := appClient.UpdateSpec(ctx, in)
	if err != nil {
		metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
		return nil, err
	}
	return spec, nil
}
func getHelmParamNamesFromAnnotation(annotations map[string]string, symbolicName string) (string, string) {
	if symbolicName == "" {
		return "image.name", "image.tag"
	}
	var annotationName, helmParamName, helmParamVersion string
	annotationName = fmt.Sprintf(common.HelmParamImageSpecAnnotation, symbolicName)
	if param, ok := annotations[annotationName]; ok {
		log.Tracef("found annotation %s", annotationName)
		return strings.TrimSpace(param), ""
	}
	annotationName = fmt.Sprintf(common.HelmParamImageNameAnnotation, symbolicName)
	if param, ok := annotations[annotationName]; ok {
		log.Tracef("found annotation %s", annotationName)
		helmParamName = param
	}
	annotationName = fmt.Sprintf(common.HelmParamImageTagAnnotation, symbolicName)
	if param, ok := annotations[annotationName]; ok {
		log.Tracef("found annotation %s", annotationName)
		helmParamVersion = param
	}
	return helmParamName, helmParamVersion
}
func getHelmParam(params []v1alpha1.HelmParameter, name string) *v1alpha1.HelmParameter {
	for _, param := range params {
		if param.Name == name {
			return &param
		}
	}
	return nil
}
func mergeHelmParams(src []v1alpha1.HelmParameter, merge []v1alpha1.HelmParameter) []v1alpha1.HelmParameter {
	retParams := make([]v1alpha1.HelmParameter, 0)
	merged := make(map[string]interface{})
	for _, srcParam := range src {
		found := false
		for _, mergeParam := range merge {
			if srcParam.Name == mergeParam.Name {
				retParams = append(retParams, mergeParam)
				merged[mergeParam.Name] = true
				found = true
				break
			}
		}
		if !found {
			retParams = append(retParams, srcParam)
		}
	}
	for _, mergeParam := range merge {
		if _, ok := merged[mergeParam.Name]; !ok {
			retParams = append(retParams, mergeParam)
		}
	}
	return retParams
}
func SetHelmImage(app *v1alpha1.Application, newImage *image.ContainerImage) error {
	if appType := getApplicationType(app); appType != ApplicationTypeHelm {
		return fmt.Errorf("cannot set Helm params on non-Helm application")
	}
	appName := app.GetName()
	var hpImageName, hpImageTag, hpImageSpec string
	hpImageSpec = newImage.GetParameterHelmImageSpec(app.Annotations)
	hpImageName = newImage.GetParameterHelmImageName(app.Annotations)
	hpImageTag = newImage.GetParameterHelmImageTag(app.Annotations)
	if hpImageSpec == "" {
		if hpImageName == "" {
			hpImageName = common.DefaultHelmImageName
		}
		if hpImageTag == "" {
			hpImageTag = common.DefaultHelmImageTag
		}
	}
	log.WithContext().
		AddField("application", appName).
		AddField("image", newImage.GetFullNameWithoutTag()).
		Debugf("target parameters: image-spec=%s image-name=%s, image-tag=%s", hpImageSpec, hpImageName, hpImageTag)
	mergeParams := make([]v1alpha1.HelmParameter, 0)
	if hpImageSpec != "" {
		p := v1alpha1.HelmParameter{Name: hpImageSpec, Value: newImage.GetFullNameWithTag(), ForceString: true}
		mergeParams = append(mergeParams, p)
	} else {
		if hpImageName != "" {
			p := v1alpha1.HelmParameter{Name: hpImageName, Value: newImage.GetFullNameWithoutTag(), ForceString: true}
			mergeParams = append(mergeParams, p)
		}
		if hpImageTag != "" {
			p := v1alpha1.HelmParameter{Name: hpImageTag, Value: newImage.ImageTag.String(), ForceString: true}
			mergeParams = append(mergeParams, p)
		}
	}
	if app.Spec.Source.Helm == nil {
		app.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{}
	}
	if app.Spec.Source.Helm.Parameters == nil {
		app.Spec.Source.Helm.Parameters = make([]v1alpha1.HelmParameter, 0)
	}
	app.Spec.Source.Helm.Parameters = mergeHelmParams(app.Spec.Source.Helm.Parameters, mergeParams)
	return nil
}
func SetKustomizeImage(app *v1alpha1.Application, newImage *image.ContainerImage) error {
	if appType := getApplicationType(app); appType != ApplicationTypeKustomize {
		return fmt.Errorf("cannot set Kustomize image on non-Kustomize application")
	}
	var ksImageParam string
	ksImageName := newImage.GetParameterKustomizeImageName(app.Annotations)
	if ksImageName != "" {
		ksImageParam = fmt.Sprintf("%s=%s", ksImageName, newImage.GetFullNameWithTag())
	} else {
		ksImageParam = newImage.GetFullNameWithTag()
	}
	log.Tracef("Setting Kustomize parameter %s", ksImageParam)
	if app.Spec.Source.Kustomize == nil {
		app.Spec.Source.Kustomize = &v1alpha1.ApplicationSourceKustomize{}
	}
	for i, kImg := range app.Spec.Source.Kustomize.Images {
		curr := image.NewFromIdentifier(string(kImg))
		override := image.NewFromIdentifier(ksImageParam)
		if curr.ImageName == override.ImageName {
			curr.ImageAlias = override.ImageAlias
			app.Spec.Source.Kustomize.Images[i] = v1alpha1.KustomizeImage(override.String())
		}
	}
	app.Spec.Source.Kustomize.MergeImage(v1alpha1.KustomizeImage(ksImageParam))
	return nil
}
func GetImagesFromApplication(app *v1alpha1.Application) image.ContainerImageList {
	images := make(image.ContainerImageList, 0)
	for _, imageStr := range app.Status.Summary.Images {
		image := image.NewFromIdentifier(imageStr)
		images = append(images, image)
	}
	annotations := app.Annotations
	for _, img := range *parseImageList(annotations) {
		if img.HasForceUpdateOptionAnnotation(annotations) {
			img.ImageTag = nil  
			images = append(images, img)
		}
	}
	return images
}
func GetApplicationTypeByName(client ArgoCD, appName string) (ApplicationType, error) {
	app, err := client.GetApplication(context.TODO(), appName)
	if err != nil {
		return ApplicationTypeUnsupported, err
	}
	return getApplicationType(app), nil
}
func GetApplicationType(app *v1alpha1.Application) ApplicationType {
	return getApplicationType(app)
}
func IsValidApplicationType(app *v1alpha1.Application) bool {
	return getApplicationType(app) != ApplicationTypeUnsupported
}
func getApplicationType(app *v1alpha1.Application) ApplicationType {
	if app.Status.SourceType == v1alpha1.ApplicationSourceTypeKustomize {
		return ApplicationTypeKustomize
	} else if app.Status.SourceType == v1alpha1.ApplicationSourceTypeHelm {
		return ApplicationTypeHelm
	} else {
		return ApplicationTypeUnsupported
	}
}