/*
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 config

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"cuelang.org/go/cue"
	"github.com/getkin/kin-openapi/openapi3"
	v1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	pkgtypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/klog/v2"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/yaml"

	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"

	"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"
	icontext "github.com/oam-dev/kubevela/pkg/config/context"
	"github.com/oam-dev/kubevela/pkg/config/writer"
	velacue "github.com/oam-dev/kubevela/pkg/cue"
	"github.com/oam-dev/kubevela/pkg/cue/script"
	"github.com/oam-dev/kubevela/pkg/oam"
	"github.com/oam-dev/kubevela/pkg/oam/util"
	"github.com/oam-dev/kubevela/pkg/utils/apply"
)

// SaveInputPropertiesKey define the key name for saving the input properties in the secret.
const SaveInputPropertiesKey = "input-properties"

// SaveObjectReferenceKey define the key name for saving the outputs objects reference metadata in the secret.
const SaveObjectReferenceKey = "objects-reference"

// SaveExpandedWriterKey define the key name for saving the expanded writer config
const SaveExpandedWriterKey = "expanded-writer"

// SaveSchemaKey define the key name for saving the API schema
const SaveSchemaKey = "schema"

// SaveTemplateKey define the key name for saving the config-template
const SaveTemplateKey = "template"

// TemplateConfigMapNamePrefix the prefix of the configmap name.
const TemplateConfigMapNamePrefix = "config-template-"

// TemplateValidationReturns define the key name for the config-template validation returns
const TemplateValidationReturns = SaveTemplateKey + ".validation.$returns"

// TemplateOutput define the key name for the config-template output
const TemplateOutput = SaveTemplateKey + ".output"

// TemplateOutputs define the key name for the config-template outputs
const TemplateOutputs = SaveTemplateKey + ".outputs"

// ErrSensitiveConfig means this config can not be read directly.
var ErrSensitiveConfig = errors.New("the config is sensitive")

// ErrNoConfigOrTarget means the config or the target is empty.
var ErrNoConfigOrTarget = errors.New("you must specify the config name and destination to distribute")

// ErrNotFoundDistribution means the app of the distribution does not exist.
var ErrNotFoundDistribution = errors.New("the distribution does not found")

// ErrConfigExist means the config does exist.
var ErrConfigExist = errors.New("the config does exist")

// ErrConfigNotFound means the config does not exist
var ErrConfigNotFound = errors.New("the config does not exist")

// ErrTemplateNotFound means the template does not exist
var ErrTemplateNotFound = errors.New("the template does not exist")

// ErrChangeTemplate means the template of the config can not be changed
var ErrChangeTemplate = errors.New("the template of the config can not be changed")

// ErrChangeSecretType means the secret type of the config can not be changed
var ErrChangeSecretType = errors.New("the secret type of the config can not be changed")

// NamespacedName the namespace and name model
type NamespacedName struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

// Template This is the spec of the config template, parse from the cue script.
type Template struct {
	NamespacedName
	Alias       string `json:"alias,omitempty"`
	Description string `json:"description,omitempty"`
	// Scope defines the usage scope of the configuration template. Provides two options: System or Namespace
	// System: The system users could use this template, and the config secret will save in the vela-system namespace.
	// Namespace: The config secret will save in the target namespace, such as this namespace belonging to one project.
	Scope string `json:"scope"`
	// Sensitive means this config can not be read from the API or the workflow step, only support the safe way, such as Secret.
	Sensitive bool `json:"sensitive"`

	CreateTime time.Time `json:"createTime"`

	Template script.CUE `json:"template"`

	ExpandedWriter writer.ExpandedWriterConfig `json:"expandedWriter"`

	Schema *openapi3.Schema `json:"schema"`

	ConfigMap *v1.ConfigMap `json:"-"`
}

// Metadata users should provide this model.
type Metadata struct {
	NamespacedName
	Alias       string                 `json:"alias,omitempty"`
	Description string                 `json:"description,omitempty"`
	Properties  map[string]interface{} `json:"properties"`
}

// TemplateMetadata This is the metadata of the config template
type TemplateMetadata struct {
	Name        string `json:"name"`
	Alias       string `json:"alias,omitempty"`
	Description string `json:"description,omitempty"`
	Sensitive   bool   `json:"sensitive,omitempty"`
	Scope       string `json:"scope,omitempty"`
}

// Config this is the config model, generated from the template and properties.
type Config struct {
	Metadata
	CreateTime time.Time
	Template   Template `json:"template"`
	// Secret this is default output way.
	Secret *v1.Secret `json:"secret"`

	// ExpandedWriterData
	ExpandedWriterData *writer.ExpandedWriterData `json:"expandedWriterData"`

	// OutputObjects this means users could define other objects.
	// This field assign value only on config render stage.
	OutputObjects map[string]*unstructured.Unstructured

	// ObjectReferences correspond OutputObjects
	ObjectReferences []v1.ObjectReference

	Targets []*ClusterTargetStatus
}

// ClusterTargetStatus merge the status of the distribution
type ClusterTargetStatus struct {
	ClusterTarget
	Status      string         `json:"status"`
	Application NamespacedName `json:"application"`
	Message     string         `json:"message"`
}

// ClusterTarget kubernetes delivery target
type ClusterTarget struct {
	ClusterName string `json:"clusterName"`
	Namespace   string `json:"namespace"`
}

// Distribution the config distribution model
type Distribution struct {
	Name        string                  `json:"name"`
	Namespace   string                  `json:"namespace"`
	CreatedTime time.Time               `json:"createdTime"`
	Configs     []*NamespacedName       `json:"configs"`
	Targets     []*ClusterTarget        `json:"targets"`
	Application pkgtypes.NamespacedName `json:"application"`
	Status      common.AppStatus        `json:"status"`
}

// CreateDistributionSpec the spec of the distribution
type CreateDistributionSpec struct {
	Configs []*NamespacedName `json:"configs"`
	Targets []*ClusterTarget  `json:"targets"`
}

// Validation the response of the validation
type Validation struct {
	Result  bool   `json:"result"`
	Message string `json:"message"`
}

// Error return the error message
func (e *Validation) Error() string {
	return fmt.Sprintf("failed to validate config: %s", e.Message)
}

// Factory handle the config
type Factory interface {
	ParseTemplate(ctx context.Context, defaultName string, content []byte) (*Template, error)
	ParseConfig(ctx context.Context, template NamespacedName, meta Metadata) (*Config, error)

	LoadTemplate(ctx context.Context, name, ns string) (*Template, error)
	CreateOrUpdateConfigTemplate(ctx context.Context, ns string, it *Template) error
	DeleteTemplate(ctx context.Context, ns, name string) error
	ListTemplates(ctx context.Context, ns, scope string) ([]*Template, error)

	ReadConfig(ctx context.Context, namespace, name string) (map[string]interface{}, error)
	GetConfig(ctx context.Context, namespace, name string, withStatus bool) (*Config, error)
	ListConfigs(ctx context.Context, namespace, template, scope string, withStatus bool) ([]*Config, error)
	DeleteConfig(ctx context.Context, namespace, name string) error
	CreateOrUpdateConfig(ctx context.Context, i *Config, ns string) error
	IsExist(ctx context.Context, namespace, name string) (bool, error)

	CreateOrUpdateDistribution(ctx context.Context, ns, name string, ads *CreateDistributionSpec) error
	ListDistributions(ctx context.Context, ns string) ([]*Distribution, error)
	DeleteDistribution(ctx context.Context, ns, name string) error
	MergeDistributionStatus(ctx context.Context, config *Config, namespace string) error
}

// Dispatcher is a client for apply resources.
type Dispatcher func(context.Context, []*unstructured.Unstructured, []apply.ApplyOption) error

// NewConfigFactory create a config factory instance
func NewConfigFactory(cli client.Client) Factory {
	return &kubeConfigFactory{cli: cli, apiApply: defaultDispatcher(cli)}
}

// NewConfigFactoryWithDispatcher create a config factory instance with a specified dispatcher
func NewConfigFactoryWithDispatcher(cli client.Client, ds Dispatcher) Factory {
	if ds == nil {
		ds = defaultDispatcher(cli)
	}
	return &kubeConfigFactory{cli: cli, apiApply: ds}
}

func defaultDispatcher(cli client.Client) Dispatcher {
	api := apply.NewAPIApplicator(cli)
	return func(ctx context.Context, manifests []*unstructured.Unstructured, ao []apply.ApplyOption) error {
		for _, m := range manifests {
			if err := api.Apply(ctx, m, ao...); err != nil {
				return err
			}
		}
		return nil
	}
}

type kubeConfigFactory struct {
	cli      client.Client
	apiApply Dispatcher
}

// ParseTemplate parse a config template instance form the cue script
func (k *kubeConfigFactory) ParseTemplate(ctx context.Context, defaultName string, content []byte) (*Template, error) {
	cueScript := script.BuildCUEScriptWithDefaultContext(icontext.DefaultContext, content)
	value, err := cueScript.ParseToTemplateValueWithCueX(ctx)
	if err != nil {
		return nil, fmt.Errorf("the cue script is invalid:%w", err)
	}
	// Render the metadata
	tm := TemplateMetadata{}
	metadata := value.LookupPath(cue.ParsePath("metadata"))
	if !metadata.Exists() && defaultName == "" {
		return nil, fmt.Errorf("failed to lookup value: var(path=metadata) not exist")
	}
	if err := metadata.Decode(&tm); err != nil {
		return nil, fmt.Errorf("failed to decode the template metadata: %w", err)
	}
	if defaultName != "" {
		tm.Name = defaultName
	}

	templateValue := value.LookupPath(cue.ParsePath("template"))
	if !templateValue.Exists() {
		return nil, fmt.Errorf("failed to lookup value: var(path=template) not exist")
	}
	schema, err := cueScript.ParsePropertiesToSchemaWithCueX(ctx, "template")
	if err != nil {
		return nil, fmt.Errorf("the properties of the cue script is invalid:%w", err)
	}
	template := &Template{
		NamespacedName: NamespacedName{
			Name: tm.Name,
		},
		Alias:          tm.Alias,
		Scope:          tm.Scope,
		Sensitive:      tm.Sensitive,
		Template:       cueScript,
		Schema:         schema,
		ExpandedWriter: writer.ParseExpandedWriterConfig(templateValue),
	}

	var configmap v1.ConfigMap
	configmap.Name = TemplateConfigMapNamePrefix + template.Name

	configmap.Data = map[string]string{
		SaveTemplateKey: string(template.Template),
	}
	if template.Schema != nil {
		data, err := yaml.Marshal(template.Schema)
		if err != nil {
			return nil, err
		}
		configmap.Data[SaveSchemaKey] = string(data)
	}
	data, err := yaml.Marshal(template.ExpandedWriter)
	if err != nil {
		return nil, err
	}
	configmap.Data[SaveExpandedWriterKey] = string(data)
	configmap.Labels = map[string]string{
		types.LabelConfigCatalog: types.VelaCoreConfig,
		types.LabelConfigScope:   template.Scope,
	}
	configmap.Annotations = map[string]string{
		types.AnnotationConfigDescription: template.Description,
		types.AnnotationConfigAlias:       template.Alias,
		types.AnnotationConfigSensitive:   fmt.Sprintf("%t", template.Sensitive),
	}
	template.ConfigMap = &configmap

	return template, nil
}

// IsFieldNotExist check whether the error type is the field not found
func IsFieldNotExist(err error) bool {
	return strings.Contains(err.Error(), "not exist")
}

// CreateOrUpdateConfigTemplate parse and update the config template
func (k *kubeConfigFactory) CreateOrUpdateConfigTemplate(ctx context.Context, ns string, it *Template) error {
	if ns != "" {
		it.ConfigMap.Namespace = ns
	}
	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(it.ConfigMap)
	if err != nil {
		return fmt.Errorf("fail to convert configmap to unstructured: %w", err)
	}
	us := &unstructured.Unstructured{Object: obj}
	us.SetAPIVersion("v1")
	us.SetKind("ConfigMap")
	return k.apiApply(ctx, []*unstructured.Unstructured{us}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()})
}

func convertConfigMap2Template(cm v1.ConfigMap) (*Template, error) {
	if cm.Labels == nil || cm.Annotations == nil {
		return nil, fmt.Errorf("this configmap is not a valid config-template")
	}
	it := &Template{
		NamespacedName: NamespacedName{
			Name:      strings.Replace(cm.Name, TemplateConfigMapNamePrefix, "", 1),
			Namespace: cm.Namespace,
		},
		Alias:       cm.Annotations[types.AnnotationConfigAlias],
		Description: cm.Annotations[types.AnnotationConfigDescription],
		Sensitive:   cm.Annotations[types.AnnotationConfigSensitive] == "true",
		Scope:       cm.Labels[types.LabelConfigScope],
		CreateTime:  cm.CreationTimestamp.Time,
		Template:    script.CUE(cm.Data[SaveTemplateKey]),
	}
	if cm.Data[SaveSchemaKey] != "" {
		var schema openapi3.Schema
		err := yaml.Unmarshal([]byte(cm.Data[SaveSchemaKey]), &schema)
		if err != nil {
			return nil, fmt.Errorf("fail to parse the schema: %w", err)
		}
		it.Schema = &schema
	}
	if cm.Data[SaveExpandedWriterKey] != "" {
		var config writer.ExpandedWriterConfig
		err := yaml.Unmarshal([]byte(cm.Data[SaveExpandedWriterKey]), &config)
		if err != nil {
			return nil, fmt.Errorf("fail to parse the schema: %w", err)
		}
		it.ExpandedWriter = config
	}
	return it, nil
}

// DeleteTemplate delete the config template
func (k *kubeConfigFactory) DeleteTemplate(ctx context.Context, ns, name string) error {
	var configmap v1.ConfigMap
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: ns, Name: TemplateConfigMapNamePrefix + name}, &configmap); err != nil {
		if apierrors.IsNotFound(err) {
			return fmt.Errorf("the config template %s not found", name)
		}
		return fmt.Errorf("fail to delete the config template %s:%w", name, err)
	}
	return k.cli.Delete(ctx, &configmap)
}

// ListTemplates list the config templates
func (k *kubeConfigFactory) ListTemplates(ctx context.Context, ns, scope string) ([]*Template, error) {
	var list = &v1.ConfigMapList{}
	selector, err := labels.Parse(fmt.Sprintf("%s=%s", types.LabelConfigCatalog, types.VelaCoreConfig))
	if err != nil {
		return nil, err
	}
	if err := k.cli.List(ctx, list,
		client.MatchingLabelsSelector{Selector: selector},
		client.InNamespace(ns)); err != nil {
		return nil, err
	}
	var templates []*Template
	for _, item := range list.Items {
		it, err := convertConfigMap2Template(item)
		if err != nil {
			klog.Warningf("fail to parse the configmap %s:%s", item.Name, err.Error())
		}
		if it != nil {
			if scope == "" || it.Scope == scope {
				templates = append(templates, it)
			}
		}
	}
	return templates, nil
}

// LoadTemplate load the template
func (k *kubeConfigFactory) LoadTemplate(ctx context.Context, name, ns string) (*Template, error) {
	var cm v1.ConfigMap
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: ns, Name: TemplateConfigMapNamePrefix + name}, &cm); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, ErrTemplateNotFound
		}
		return nil, err
	}
	return convertConfigMap2Template(cm)
}

// ParseConfig merge the properties to template and build a config instance
// If the templateName is empty, means creating a secret without the template.
func (k *kubeConfigFactory) ParseConfig(ctx context.Context,
	template NamespacedName, meta Metadata,
) (*Config, error) {
	var secret v1.Secret

	config := &Config{
		Metadata: meta,
		Secret:   &secret,
	}

	if template.Name != "" {
		template, err := k.LoadTemplate(ctx, template.Name, template.Namespace)
		if err != nil {
			return nil, err
		}
		contextValue := icontext.ConfigRenderContext{
			Name:      meta.Name,
			Namespace: meta.Namespace,
		}
		// Compile the config template
		val, err := template.Template.RunAndOutputWithCueX(ctx, contextValue, meta.Properties)
		if err != nil && !velacue.IsFieldNotExist(err) {
			return nil, err
		}
		// Render the validation response and check validation result
		valid := val.LookupPath(cue.ParsePath(TemplateValidationReturns))
		validation := Validation{}
		if valid.Exists() {
			if err := valid.Decode(&validation); err != nil {
				return nil, fmt.Errorf("the validation.$returns format must be validation")
			}
		}
		if len(validation.Message) > 0 {
			return nil, &validation
		}
		// Render the output secret
		output := val.LookupPath(cue.ParsePath(TemplateOutput))
		if output.Exists() {
			if err := output.Decode(&secret); err != nil {
				return nil, fmt.Errorf("the output format must be secret")
			}
		}
		if secret.Type == "" {
			secret.Type = v1.SecretType(fmt.Sprintf("%s/%s", "", template.Name))
		}
		if secret.Labels == nil {
			secret.Labels = map[string]string{}
		}
		secret.Labels[types.LabelConfigCatalog] = types.VelaCoreConfig
		secret.Labels[types.LabelConfigType] = template.Name
		secret.Labels[types.LabelConfigType] = template.Name
		secret.Labels[types.LabelConfigScope] = template.Scope

		if secret.Annotations == nil {
			secret.Annotations = map[string]string{}
		}
		secret.Annotations[types.AnnotationConfigSensitive] = fmt.Sprintf("%t", template.Sensitive)
		secret.Annotations[types.AnnotationConfigTemplateNamespace] = template.Namespace
		config.Template = *template

		// Render the expanded writer configuration
		data, err := writer.RenderForExpandedWriter(template.ExpandedWriter, config.Template.Template, contextValue, meta.Properties)
		if err != nil {
			return nil, fmt.Errorf("fail to render the content for the expanded writer:%w ", err)
		}
		config.ExpandedWriterData = data

		// Render the outputs objects
		outputs := val.LookupPath(cue.ParsePath(TemplateOutputs))
		if outputs.Exists() {
			var objects = map[string]interface{}{}
			if err := outputs.Decode(&objects); err != nil {
				return nil, fmt.Errorf("the outputs is invalid %w", err)
			}
			var objectReferences []v1.ObjectReference
			config.OutputObjects = make(map[string]*unstructured.Unstructured)
			for k := range objects {
				if ob, ok := objects[k].(map[string]interface{}); ok {
					obj := &unstructured.Unstructured{Object: ob}
					config.OutputObjects[k] = obj
					objectReferences = append(objectReferences, v1.ObjectReference{
						Kind:       obj.GetKind(),
						Namespace:  obj.GetNamespace(),
						Name:       obj.GetName(),
						APIVersion: obj.GetAPIVersion(),
					})
				}
			}
			objectReferenceJSON, err := json.Marshal(objectReferences)
			if err != nil {
				return nil, err
			}
			if secret.Data == nil {
				secret.Data = map[string][]byte{}
			}
			secret.Data[SaveObjectReferenceKey] = objectReferenceJSON
		}
	} else {
		secret.Labels = map[string]string{
			types.LabelConfigCatalog: types.VelaCoreConfig,
			types.LabelConfigType:    "",
		}
		secret.Annotations = map[string]string{}
	}
	secret.Namespace = meta.Namespace
	if secret.Name == "" {
		secret.Name = meta.Name
	}
	secret.Annotations[types.AnnotationConfigAlias] = meta.Alias
	secret.Annotations[types.AnnotationConfigDescription] = meta.Description
	pp, err := json.Marshal(meta.Properties)
	if err != nil {
		return nil, err
	}
	if secret.Data == nil {
		secret.Data = map[string][]byte{}
	}
	secret.Data[SaveInputPropertiesKey] = pp

	return config, nil
}

// ReadConfig read the config secret
func (k *kubeConfigFactory) ReadConfig(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
	var secret v1.Secret
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
		return nil, err
	}
	if secret.Annotations[types.AnnotationConfigSensitive] == "true" {
		return nil, ErrSensitiveConfig
	}
	properties := secret.Data[SaveInputPropertiesKey]
	var input = map[string]interface{}{}
	if err := json.Unmarshal(properties, &input); err != nil {
		return nil, err
	}
	return input, nil
}

func (k *kubeConfigFactory) GetConfig(ctx context.Context, namespace, name string, withStatus bool) (*Config, error) {
	var secret v1.Secret
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, ErrConfigNotFound
		}
		return nil, err
	}
	if secret.Annotations[types.AnnotationConfigSensitive] == "true" {
		return nil, ErrSensitiveConfig
	}
	item, err := convertSecret2Config(&secret)
	if err != nil {
		return nil, err
	}
	if withStatus {
		if err := k.MergeDistributionStatus(ctx, item, item.Namespace); err != nil && !errors.Is(err, ErrNotFoundDistribution) {
			klog.Warningf("fail to merge the status %s:%s", item.Name, err.Error())
		}
	}
	return item, nil
}

// CreateOrUpdateConfig create or update the config.
// Write the expand config to the target server.
func (k *kubeConfigFactory) CreateOrUpdateConfig(ctx context.Context, i *Config, _ string) error {
	var secret v1.Secret
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: i.Namespace, Name: i.Name}, &secret); err == nil {
		if secret.Labels[types.LabelConfigType] != i.Template.Name {
			return ErrChangeTemplate
		}
		if i.Secret.Type != "" && secret.Type != i.Secret.Type {
			return ErrChangeSecretType
		}
	}

	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(i.Secret)
	if err != nil {
		return fmt.Errorf("fail to convert secret to unstructured: %w", err)
	}
	us := &unstructured.Unstructured{Object: obj}
	us.SetAPIVersion("v1")
	us.SetKind("Secret")

	if err := k.apiApply(ctx, []*unstructured.Unstructured{us}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()}); err != nil {
		return fmt.Errorf("fail to apply the secret: %w", err)
	}
	for key, obj := range i.OutputObjects {
		if err := k.apiApply(ctx, []*unstructured.Unstructured{obj}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()}); err != nil {
			return fmt.Errorf("fail to apply the object %s: %w", key, err)
		}
	}
	readConfig := func(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
		return k.ReadConfig(ctx, namespace, name)
	}
	if i.ExpandedWriterData != nil {
		if errs := writer.Write(ctx, i.ExpandedWriterData, readConfig); len(errs) > 0 {
			return errs[0]
		}
	}
	return nil
}

func (k *kubeConfigFactory) IsExist(ctx context.Context, namespace, name string) (bool, error) {
	var secret v1.Secret
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
		if apierrors.IsNotFound(err) {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

func (k *kubeConfigFactory) ListConfigs(ctx context.Context, namespace, template, scope string, withStatus bool) ([]*Config, error) {
	var list = &v1.SecretList{}
	requirement := fmt.Sprintf("%s=%s", types.LabelConfigCatalog, types.VelaCoreConfig)
	if template != "" {
		requirement = fmt.Sprintf("%s,%s=%s", requirement, types.LabelConfigType, template)
	}
	if scope != "" {
		requirement = fmt.Sprintf("%s,%s=%s", requirement, types.LabelConfigScope, scope)
	}
	selector, err := labels.Parse(requirement)
	if err != nil {
		return nil, err
	}
	if err := k.cli.List(ctx, list,
		client.MatchingLabelsSelector{Selector: selector},
		client.InNamespace(namespace)); err != nil {
		return nil, err
	}
	var configs []*Config
	for i := range list.Items {
		item := list.Items[i]
		it, err := convertSecret2Config(&item)
		if err != nil {
			klog.Warningf("fail to parse the secret %s:%s", item.Name, err.Error())
		}
		if it != nil {
			if withStatus {
				if err := k.MergeDistributionStatus(ctx, it, it.Namespace); err != nil && !errors.Is(err, ErrNotFoundDistribution) {
					klog.Warningf("fail to merge the status %s:%s", item.Name, err.Error())
				}
			}
			configs = append(configs, it)
		}
	}
	return configs, nil
}

func (k *kubeConfigFactory) DeleteConfig(ctx context.Context, namespace, name string) error {
	var secret v1.Secret
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
		if apierrors.IsNotFound(err) {
			return fmt.Errorf("the config %s not found", name)
		}
		return fmt.Errorf("fail to delete the config %s:%w", name, err)
	}
	if secret.Labels[types.LabelConfigCatalog] != types.VelaCoreConfig {
		return fmt.Errorf("found a secret but is not a config")
	}

	if objects, exist := secret.Data[SaveObjectReferenceKey]; exist {
		var objectReferences []v1.ObjectReference
		if err := json.Unmarshal(objects, &objectReferences); err != nil {
			return err
		}
		for _, obj := range objectReferences {
			if err := k.cli.Delete(ctx, convertObjectReference2Unstructured(obj)); err != nil && !apierrors.IsNotFound(err) {
				return fmt.Errorf("fail to clear the object %s:%w", obj.Name, err)
			}
		}
	}

	return k.cli.Delete(ctx, &secret)
}

func (k *kubeConfigFactory) MergeDistributionStatus(ctx context.Context, config *Config, namespace string) error {
	app := &v1beta1.Application{}
	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: DefaultDistributionName(config.Name)}, app); err != nil {
		if apierrors.IsNotFound(err) {
			return ErrNotFoundDistribution
		}
		return err
	}
	var targets []*ClusterTargetStatus
	for _, policy := range app.Spec.Policies {
		if policy.Type == v1alpha1.TopologyPolicyType {
			status := workflowv1alpha1.WorkflowStepPhasePending
			message := ""
			if app.Status.Workflow != nil {
				for _, step := range app.Status.Workflow.Steps {
					if policy.Name == strings.Replace(step.Name, "deploy-", "", 1) {
						status = step.Phase
						message = step.Message
					}
				}
			}
			var spec v1alpha1.TopologyPolicySpec
			if err := json.Unmarshal(policy.Properties.Raw, &spec); err == nil {
				for _, clu := range spec.Clusters {
					targets = append(targets, &ClusterTargetStatus{
						ClusterTarget: ClusterTarget{
							Namespace:   spec.Namespace,
							ClusterName: clu,
						},
						Application: NamespacedName{Name: app.Name, Namespace: app.Namespace},
						Status:      string(status),
						Message:     message,
					})
				}
			}
		}
	}
	config.Targets = append(config.Targets, targets...)
	return nil
}

func (k *kubeConfigFactory) CreateOrUpdateDistribution(ctx context.Context, ns, name string, ads *CreateDistributionSpec) error {
	policies := convertTarget2TopologyPolicy(ads.Targets)
	if len(policies) == 0 {
		return ErrNoConfigOrTarget
	}
	// create the share policy
	shareSpec := v1alpha1.SharedResourcePolicySpec{
		Rules: []v1alpha1.SharedResourcePolicyRule{{
			Selector: v1alpha1.ResourcePolicyRuleSelector{
				CompNames: []string{name},
			},
		}},
	}
	properties, err := json.Marshal(shareSpec)
	if err == nil {
		policies = append(policies, v1beta1.AppPolicy{
			Type: v1alpha1.SharedResourcePolicyType,
			Name: "share-config",
			Properties: &runtime.RawExtension{
				Raw: properties,
			},
		})
	}

	var objects []map[string]string
	for _, s := range ads.Configs {
		objects = append(objects, map[string]string{
			"name":      s.Name,
			"namespace": s.Namespace,
			"resource":  "secret",
		})
	}
	if len(objects) == 0 {
		return ErrNoConfigOrTarget
	}

	objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
	if err != nil {
		return err
	}

	reqByte, err := json.Marshal(ads)
	if err != nil {
		return err
	}

	distribution := &v1beta1.Application{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
			Labels: map[string]string{
				types.LabelSourceOfTruth: types.FromInner,
				// This label will override the secret label, then change the catalog of the distributed secrets.
				types.LabelConfigCatalog: types.CatalogConfigDistribution,
			},
			Annotations: map[string]string{
				types.AnnotationConfigDistributionSpec: string(reqByte),
				oam.AnnotationPublishVersion:           util.GenerateVersion("config"),
			},
		},
		Spec: v1beta1.ApplicationSpec{
			Components: []common.ApplicationComponent{
				{
					Name:       name,
					Type:       v1alpha1.RefObjectsComponentType,
					Properties: &runtime.RawExtension{Raw: objectsBytes},
				},
			},
			Policies: policies,
		},
	}
	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(distribution)
	if err != nil {
		return fmt.Errorf("fail to convert application to unstructured: %w", err)
	}
	us := &unstructured.Unstructured{Object: obj}
	us.SetAPIVersion(v1beta1.SchemeGroupVersion.String())
	us.SetKind(v1beta1.ApplicationKind)

	return k.apiApply(ctx, []*unstructured.Unstructured{us}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()})
}

func (k *kubeConfigFactory) ListDistributions(ctx context.Context, ns string) ([]*Distribution, error) {
	var apps v1beta1.ApplicationList
	if err := k.cli.List(ctx, &apps, client.MatchingLabels{
		types.LabelSourceOfTruth: types.FromInner,
		types.LabelConfigCatalog: types.CatalogConfigDistribution,
	}, client.InNamespace(ns)); err != nil {
		return nil, err
	}
	var list []*Distribution
	for _, app := range apps.Items {
		dis := &Distribution{
			Name:        app.Name,
			Namespace:   app.Namespace,
			CreatedTime: app.CreationTimestamp.Time,
			Application: pkgtypes.NamespacedName{
				Namespace: app.Namespace,
				Name:      app.Name,
			},
			Status: app.Status,
		}
		if spec, ok := app.Annotations[types.AnnotationConfigDistributionSpec]; ok {
			var req CreateDistributionSpec
			if err := json.Unmarshal([]byte(spec), &req); err == nil {
				dis.Targets = req.Targets
				dis.Configs = req.Configs
			}
		}
		list = append(list, dis)
	}
	return list, nil
}
func (k *kubeConfigFactory) DeleteDistribution(ctx context.Context, ns, name string) error {
	app := &v1beta1.Application{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      name,
		},
	}
	if err := k.cli.Delete(ctx, app); err != nil {
		if apierrors.IsNotFound(err) {
			return ErrNotFoundDistribution
		}
		return err
	}
	return nil
}

func convertTarget2TopologyPolicy(targets []*ClusterTarget) (policies []v1beta1.AppPolicy) {
	for _, target := range targets {
		policySpec := v1alpha1.TopologyPolicySpec{
			Placement: v1alpha1.Placement{
				Clusters: []string{target.ClusterName},
			},
			Namespace: target.Namespace,
		}
		properties, err := json.Marshal(policySpec)
		if err == nil {
			policies = append(policies, v1beta1.AppPolicy{
				Type: v1alpha1.TopologyPolicyType,
				Name: fmt.Sprintf("%s-%s", target.ClusterName, target.Namespace),
				Properties: &runtime.RawExtension{
					Raw: properties,
				},
			})
		}
	}
	return
}

func convertSecret2Config(se *v1.Secret) (*Config, error) {
	if se == nil || se.Labels == nil {
		return nil, fmt.Errorf("this secret is not a valid config secret")
	}
	config := &Config{
		Metadata: Metadata{
			NamespacedName: NamespacedName{
				Name:      se.Name,
				Namespace: se.Namespace,
			},
		},
		CreateTime: se.CreationTimestamp.Time,
		Secret:     se,
		Template: Template{
			NamespacedName: NamespacedName{
				Name: se.Labels[types.LabelConfigType],
			},
		},
	}
	if se.Annotations != nil {
		config.Alias = se.Annotations[types.AnnotationConfigAlias]
		config.Description = se.Annotations[types.AnnotationConfigDescription]
		config.Template.Namespace = se.Annotations[types.AnnotationConfigTemplateNamespace]
		config.Template.Sensitive = se.Annotations[types.AnnotationConfigSensitive] == "true"
	}
	if !config.Template.Sensitive && len(se.Data[SaveInputPropertiesKey]) > 0 {
		var properties = map[string]interface{}{}
		if err := yaml.Unmarshal(se.Data[SaveInputPropertiesKey], &properties); err != nil {
			return nil, err
		}
		config.Properties = properties
	}
	if !config.Template.Sensitive {
		config.Secret = se
	} else {
		seCope := se.DeepCopy()
		seCope.Data = nil
		seCope.StringData = nil
		config.Secret = seCope
	}
	if content, ok := se.Data[SaveObjectReferenceKey]; ok {
		var objectReferences []v1.ObjectReference
		if err := json.Unmarshal(content, &objectReferences); err != nil {
			klog.Warningf("the object references are invalid, config:%s", se.Name)
		}
		config.ObjectReferences = objectReferences
	}
	return config, nil
}

func convertObjectReference2Unstructured(ref v1.ObjectReference) *unstructured.Unstructured {
	var obj unstructured.Unstructured
	obj.SetAPIVersion(ref.APIVersion)
	obj.SetNamespace(ref.Namespace)
	obj.SetKind(ref.Kind)
	obj.SetName(ref.Name)
	return &obj
}

// DefaultDistributionName generate the distribution name by a config name
func DefaultDistributionName(configName string) string {
	return fmt.Sprintf("distribute-%s", configName)
}
