/*
Copyright (C) 2022-2025 ApeCloud Co., Ltd

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 v1alpha1

import (
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
	dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
)

// OpsRequestSpec defines the desired state of OpsRequest
//
// +kubebuilder:validation:XValidation:rule="has(self.cancel) && self.cancel ? (self.type in ['VerticalScaling', 'HorizontalScaling']) : true",message="forbidden to cancel the opsRequest which type not in ['VerticalScaling','HorizontalScaling']"
type OpsRequestSpec struct {
	// Specifies the name of the Cluster resource that this operation is targeting.
	//
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.clusterName"
	ClusterName string `json:"clusterName,omitempty"`

	// Indicates whether the current operation should be canceled and terminated gracefully if it's in the
	// "Pending", "Creating", or "Running" state.
	//
	// This field applies only to "VerticalScaling" and "HorizontalScaling" opsRequests.
	//
	// Note: Setting `cancel` to true is irreversible; further modifications to this field are ineffective.
	//
	// +optional
	Cancel bool `json:"cancel,omitempty"`

	// Instructs the system to bypass pre-checks (including cluster state checks and customized pre-conditions hooks)
	// and immediately execute the opsRequest, except for the opsRequest of 'Start' type, which will still undergo
	// pre-checks even if `force` is true.
	//
	// This is useful for concurrent execution of 'VerticalScaling' and 'HorizontalScaling' opsRequests.
	// By setting `force` to true, you can bypass the default checks and demand these opsRequests to run
	// simultaneously.
	//
	// Note: Once set, the `force` field is immutable and cannot be updated.
	//
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.force"
	// +optional
	Force bool `json:"force,omitempty"`

	// Indicates whether opsRequest should continue to queue when 'force' is set to true.
	// +kubebuilder:default=false
	// +optional
	EnqueueOnForce bool `json:"enqueueOnForce,omitempty"`

	// Specifies the type of this operation. Supported types include "Start", "Stop", "Restart", "Switchover",
	// "VerticalScaling", "HorizontalScaling", "VolumeExpansion", "Reconfiguring", "Upgrade", "Backup", "Restore",
	// "Expose", "RebuildInstance", "Custom".
	//
	// Note: This field is immutable once set.
	//
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.type"
	Type OpsType `json:"type"`

	// Specifies the duration in seconds that an OpsRequest will remain in the system after successfully completing
	// (when `opsRequest.status.phase` is "Succeed") before automatic deletion.
	//
	// +optional
	TTLSecondsAfterSucceed int32 `json:"ttlSecondsAfterSucceed,omitempty"`

	// Specifies the duration in seconds that an OpsRequest will remain in the system after completion
	// for any phase other than "Succeed" (e.g., "Failed", "Cancelled", "Aborted") before automatic deletion.
	//
	// +optional
	TTLSecondsAfterUnsuccessfulCompletion int32 `json:"ttlSecondsAfterUnsuccessfulCompletion,omitempty"`

	// Specifies the maximum time in seconds that the OpsRequest will wait for its pre-conditions to be met
	// before it aborts the operation.
	// If set to 0 (default), pre-conditions must be satisfied immediately for the OpsRequest to proceed.
	//
	// +kubebuilder:default=0
	// +optional
	PreConditionDeadlineSeconds *int32 `json:"preConditionDeadlineSeconds,omitempty"`

	// Specifies the maximum duration (in seconds) that an opsRequest is allowed to run.
	// If the opsRequest runs longer than this duration, its phase will be marked as Aborted.
	// If this value is not set or set to 0, the timeout will be ignored and the opsRequest will run indefinitely.
	// +optional
	// +kubebuilder:Minimum=0
	TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`

	// Exactly one of its members must be set.
	SpecificOpsRequest `json:",inline"`
}

type SpecificOpsRequest struct {
	// Specifies the desired new version of the Cluster.
	//
	// Note: This field is immutable once set.
	//
	// +optional
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.upgrade"
	Upgrade *Upgrade `json:"upgrade,omitempty"`

	// Lists HorizontalScaling objects, each specifying scaling requirements for a Component,
	// including desired replica changes, configurations for new instances, modifications for existing instances,
	// and take offline/online the specified instances.
	//
	// +optional
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.horizontalScaling"
	HorizontalScalingList []HorizontalScaling `json:"horizontalScaling,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists VolumeExpansion objects, each specifying a component and its corresponding volumeClaimTemplates
	// that requires storage expansion.
	//
	// +optional
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	VolumeExpansionList []VolumeExpansion `json:"volumeExpansion,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists Components to be started. If empty, all components will be started.
	//
	// +optional
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.start"
	// +kubebuilder:validation:MaxItems=1024
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	StartList []ComponentOps `json:"start,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists Components to be stopped. If empty, all components will be stopped.
	//
	// +optional
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.stop"
	// +kubebuilder:validation:MaxItems=1024
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	StopList []ComponentOps `json:"stop,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists Components to be restarted.
	//
	// +optional
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.restart"
	// +kubebuilder:validation:MaxItems=1024
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	RestartList []ComponentOps `json:"restart,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists Switchover objects, each specifying a Component to perform the switchover operation.
	//
	// +optional
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.switchover"
	SwitchoverList []Switchover `json:"switchover,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists VerticalScaling objects, each specifying a component and its desired compute resources for vertical scaling.
	//
	// +kubebuilder:validation:MaxItems=1024
	// +optional
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	VerticalScalingList []VerticalScaling `json:"verticalScaling,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists Reconfigure objects, each specifying a Component and its configuration updates.
	//
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.reconfigure"
	// +optional
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	Reconfigures []Reconfigure `json:"reconfigures,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Lists Expose objects, each specifying a Component and its services to be exposed.
	//
	// +optional
	ExposeList []Expose `json:"expose,omitempty"`

	// Specifies the parameters to back up a Cluster.
	//
	// +kubebuilder:validation:XValidation:rule="has(oldSelf.parameters) == has(self.parameters)",message="forbidden to update backup.parameters"
	// +optional
	Backup *Backup `json:"backup,omitempty"`

	// Specifies the parameters to restore a Cluster.
	// Note that this restore operation will roll back cluster services.
	//
	// +kubebuilder:validation:XValidation:rule="has(oldSelf.parameters) == has(self.parameters)",message="forbidden to update restore.parameters"
	// +optional
	Restore *Restore `json:"restore,omitempty"`

	// Specifies the parameters to rebuild some instances.
	// Rebuilding an instance involves restoring its data from a backup or another database replica.
	// The instances being rebuilt usually serve as standby in the cluster.
	// Hence, rebuilding instances is often also referred to as "standby reconstruction".
	//
	// +optional
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update spec.rebuildFrom"
	RebuildFrom []RebuildInstance `json:"rebuildFrom,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`

	// Specifies a custom operation defined by OpsDefinition.
	//
	// +optional
	CustomOps *CustomOps `json:"custom,omitempty"`
}

// ComponentOps specifies the Component to be operated on.
type ComponentOps struct {
	// Specifies the name of the Component as defined in the cluster.spec
	// +kubebuilder:validation:Required
	ComponentName string `json:"componentName"`
}

type RebuildInstance struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Specifies the instances (Pods) that need to be rebuilt, typically operating as standbys.
	//
	// +kubebuilder:validation:MinItems=1
	// +kubebuilder:validation:Required
	Instances []Instance `json:"instances"`

	// When it is set to true, the instance will be rebuilt in-place.
	// If false, a new pod will be created. Once the new pod is ready to serve,
	// the instance that require rebuilding will be taken offline.
	InPlace bool `json:"inPlace"`

	// Indicates the name of the Backup custom resource from which to recover the instance.
	// Defaults to an empty PersistentVolume if unspecified.
	//
	// Note:
	// - Only full physical backups are supported for multi-replica Components (e.g., 'xtrabackup' for MySQL).
	// - Logical backups (e.g., 'mysqldump' for MySQL) are unsupported in the current version.
	//
	// +optional
	BackupName string `json:"backupName,omitempty"`

	// When multiple source targets exist of the backup, you must specify the source target to restore.
	// +optional
	SourceBackupTargetName string `json:"sourceBackupTargetName,omitempty"`

	// Defines container environment variables for the restore process.
	// merged with the ones specified in the Backup and ActionSet resources.
	//
	// Merge priority: Restore env > Backup env > ActionSet env.
	//
	// Purpose: Some databases require different configurations when being restored as a standby
	// compared to being restored as a primary.
	// For example, when restoring MySQL as a replica, you need to set `skip_slave_start="ON"` for 5.7
	// or `skip_replica_start="ON"` for 8.0.
	// Allowing environment variables to be passed in makes it more convenient to control these behavioral differences
	// during the restore process.
	//
	// +kubebuilder:pruning:PreserveUnknownFields
	// +optional
	RestoreEnv []corev1.EnvVar `json:"restoreEnv,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
}

type Instance struct {
	// Pod name of the instance.
	// +kubebuilder:validation:Required
	Name string `json:"name"`

	// The instance will rebuild on the specified node.
	// If not set, it will rebuild on a random node.
	// +optional
	TargetNodeName string `json:"targetNodeName,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="(has(self.componentName) && !has(self.componentObjectName)) || (!has(self.componentName) && has(self.componentObjectName))",message="need to specified only componentName or componentObjectName"

type Switchover struct {
	// Specifies the name of the Component as defined in the cluster.spec.
	// +optional
	ComponentName string `json:"componentName,omitempty"`

	// Specifies the name of the Component object.
	// +optional
	ComponentObjectName string `json:"componentObjectName,omitempty"`

	// Specifies the instance whose role will be transferred. A typical usage is to transfer the leader role
	// in a consensus system.
	//
	// +kubebuilder:validation:Required
	InstanceName string `json:"instanceName"`

	// If CandidateName is specified, the role will be transferred to this instance.
	// The name must match one of the pods in the component.
	// Refer to ComponentDefinition's Swtichover lifecycle action for more details.
	//
	// +optional
	CandidateName string `json:"candidateName,omitempty"`
}

// Upgrade defines the parameters for an upgrade operation.
type Upgrade struct {
	// Lists components to be upgrade based on desired ComponentDefinition and ServiceVersion.
	// From the perspective of cluster API, the reasonable combinations should be:
	// 1. (comp-def, service-ver) - upgrade to the specified service version and component definition, the user takes the responsibility to ensure that they are compatible.
	// 2. ("", service-ver) - upgrade to the specified service version, let the operator choose the latest compatible component definition.
	// 3. (comp-def, "") - upgrade to the specified component definition, let the operator choose the latest compatible service version.
	// 4. ("", "") - upgrade to the latest service version and component definition, the operator will ensure the compatibility between the selected versions.
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	// +kubebuilder:validation:MaxItems=1024
	// +optional
	Components []UpgradeComponent `json:"components,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`
}

// +kubebuilder:validation:XValidation:rule="has(self.componentDefinitionName) || has(self.serviceVersion)",message="at least one componentDefinitionName or serviceVersion"

type UpgradeComponent struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Specifies the name of the ComponentDefinition, only exact matches are supported.
	// +kubebuilder:validation:MaxLength=64
	// +optional
	ComponentDefinitionName *string `json:"componentDefinitionName,omitempty"`

	// Specifies the version of the Service expected to be provisioned by this Component.
	// Referring to the ServiceVersion defined by the ComponentDefinition and ComponentVersion.
	// And ServiceVersion in ClusterComponentSpec is optional, when no version is specified,
	// use the latest available version in ComponentVersion.
	// +kubebuilder:validation:MaxLength=32
	// +optional
	ServiceVersion *string `json:"serviceVersion,omitempty"`
}

// VerticalScaling refers to the process of adjusting compute resources (e.g., CPU, memory) allocated to a Component.
// It defines the parameters required for the operation.
type VerticalScaling struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Defines the desired compute resources of the Component's instances.
	//
	// +kubebuilder:pruning:PreserveUnknownFields
	corev1.ResourceRequirements `json:",inline"`

	// Specifies the desired compute resources of the instance template that need to vertical scale.
	// +patchMergeKey=name
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=name
	// +optional
	Instances []InstanceResourceTemplate `json:"instances,omitempty"  patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
}

type InstanceResourceTemplate struct {
	// Refer to the instance template name of the component or sharding.
	// +kubebuilder:validation:Required
	Name string `json:"name"`

	// Defines the computational resource size for vertical scaling.
	// +kubebuilder:pruning:PreserveUnknownFields
	corev1.ResourceRequirements `json:",inline"`
}

type InstanceVolumeClaimTemplate struct {
	// Refer to the instance template name of the component or sharding.
	// +kubebuilder:validation:Required
	Name string `json:"name"`

	// volumeClaimTemplates specifies the storage size and volumeClaimTemplate name.
	// +kubebuilder:validation:Required
	// +patchMergeKey=name
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=name
	VolumeClaimTemplates []OpsRequestVolumeClaimTemplate `json:"volumeClaimTemplates" patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
}

// VolumeExpansion encapsulates the parameters required for a volume expansion operation.
type VolumeExpansion struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Specifies a list of OpsRequestVolumeClaimTemplate objects, defining the volumeClaimTemplates
	// that are used to expand the storage and the desired storage size for each one.
	//
	// +kubebuilder:validation:Required
	// +patchMergeKey=name
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=name
	VolumeClaimTemplates []OpsRequestVolumeClaimTemplate `json:"volumeClaimTemplates" patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
}

type OpsRequestVolumeClaimTemplate struct {
	// Specifies the desired storage size for the volume.
	//
	// +kubebuilder:validation:Required
	Storage resource.Quantity `json:"storage"`

	// Specify the name of the volumeClaimTemplate in the Component.
	// The specified name must match one of the volumeClaimTemplates defined
	// in the `clusterComponentSpec.volumeClaimTemplates` field.
	//
	// +kubebuilder:validation:Required
	Name string `json:"name"`
}

// +kubebuilder:validation:XValidation:rule="has(self.shards) ? (!has(self.scaleOut) && !has(self.scaleIn)) : true",message="shards field cannot be used together with scaleOut or scaleIn"

// HorizontalScaling defines the parameters of a horizontal scaling operation.
type HorizontalScaling struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Specifies the desired number of shards for the component.
	// This parameter is mutually exclusive with other parameters.
	Shards *int32 `json:"shards,omitempty"`

	// Specifies the replica changes for scaling out components and instance templates,
	// and brings offline instances back online. Can be used in conjunction with the "scaleIn" operation.
	// Note: Any configuration that deletes instances is considered invalid.
	//
	// +optional
	ScaleOut *ScaleOut `json:"scaleOut,omitempty"`

	// Specifies the replica changes for scaling in components and instance templates,
	// and takes specified instances offline. Can be used in conjunction with the "scaleOut" operation.
	// Note: Any configuration that creates instances is considered invalid.
	// +optional
	ScaleIn *ScaleIn `json:"scaleIn,omitempty"`
}

// ScaleOut defines the configuration for a scale-out operation.
type ScaleOut struct {

	// FromBackup specifies the configuration for creating new instances from an existing backup.
	// This is only effective for non-sharding components.
	// When specified, new instances will be created using data from the specified backup.
	// +optional
	FromBackup *FromBackup `json:"fromBackup,omitempty"`

	// Modifies the replicas of the component and instance templates.
	ReplicaChanger `json:",inline"`

	// Defines the configuration for new instances added during scaling, including resource requirements, labels, annotations, etc.
	// New instances are created based on the provided instance templates.
	// +optional
	// +patchMergeKey=name
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=name
	NewInstances []appsv1.InstanceTemplate `json:"newInstances,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"`

	// Specifies the instances in the offline list to bring back online.
	// +optional
	OfflineInstancesToOnline []string `json:"offlineInstancesToOnline,omitempty"`
}

// ScaleIn defines the configuration for a scale-in operation.
type ScaleIn struct {

	// Modifies the replicas of the component and instance templates.
	ReplicaChanger `json:",inline"`

	// Specifies the instance names that need to be taken offline.
	// +optional
	OnlineInstancesToOffline []string `json:"onlineInstancesToOffline,omitempty"`
}

// ReplicaChanger defines the parameters for changing the number of replicas.
type ReplicaChanger struct {
	// Specifies the replica changes for the component.
	// +kubebuilder:validation:Minimum=0
	ReplicaChanges *int32 `json:"replicaChanges,omitempty"`

	// Modifies the desired replicas count for existing InstanceTemplate.
	// if the inst
	// +patchMergeKey=name
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=name
	// +optional
	Instances []InstanceReplicasTemplate `json:"instances,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
}

type FromBackup struct {
	// Specifies the name of the Backup name.
	// +kubebuilder:validation:Required
	Name string `json:"name"`

	// Specifies the namespace of the Backup namespace.
	// If not specified, the namespace of the OpsRequest will be used.
	// +kubebuilder:validation:Required
	Namespace string `json:"namespace,omitempty"`

	// Defines container environment variables for the restore process.
	// merged with the ones specified in the Backup and ActionSet resources.
	//
	// Merge priority: Restore env > Backup env > ActionSet env.
	//
	// Purpose: Some databases require different configurations when being restored as a standby
	// compared to being restored as a primary.
	// For example, when restoring MySQL as a replica, you need to set `skip_slave_start="ON"` for 5.7
	// or `skip_replica_start="ON"` for 8.0.
	// Allowing environment variables to be passed in makes it more convenient to control these behavioral differences
	// during the restore process.
	//
	// +kubebuilder:pruning:PreserveUnknownFields
	// +optional
	RestoreEnv []corev1.EnvVar `json:"restoreEnv,omitempty" patchStrategy:"merge" patchMergeKey:"name"`

	// Specifies the point in time to which the restore should be performed.
	// Supported time formats:
	//
	// - RFC3339 format, e.g. "2023-11-25T18:52:53Z"
	// - A human-readable date-time format, e.g. "Jul 25,2023 18:52:53 UTC+0800"
	//
	RestorePointInTime string `json:"restorePointInTime,omitempty"`
}

// InstanceReplicasTemplate defines the template for instance replicas.
type InstanceReplicasTemplate struct {
	// Specifies the name of the instance template.
	// +kubebuilder:validation:Required
	Name string `json:"name"`

	// Specifies the replica changes for the instance template.
	// +kubebuilder:validation:Minimum=0
	// +kubebuilder:validation:Required
	ReplicaChanges int32 `json:"replicaChanges"`
}

// Reconfigure defines the parameters for updating a Component's configuration.
type Reconfigure struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Specifies a list of key-value pairs representing parameters and their corresponding values
	// within a single configuration file.
	// This field is used to override or set the values of parameters without modifying the entire configuration file.
	//
	// +optional
	Parameters []ParameterPair `json:"parameters,omitempty"`
}

type CustomOps struct {
	// Specifies the name of the OpsDefinition.
	//
	// +kubebuilder:validation:Required
	OpsDefinitionName string `json:"opsDefinitionName"`

	// Specifies the name of the ServiceAccount to be used for executing the custom operation.
	ServiceAccountName *string `json:"serviceAccountName,omitempty"`

	// Specifies the maximum number of components to be operated on concurrently to mitigate performance impact
	// on clusters with multiple components.
	//
	// It accepts an absolute number (e.g., 5) or a percentage of components to execute in parallel (e.g., "10%").
	// Percentages are rounded up to the nearest whole number of components.
	// For example, if "10%" results in less than one, it rounds up to 1.
	//
	// When unspecified, all components are processed simultaneously by default.
	//
	// Note: This feature is not implemented yet.
	//
	// +optional
	MaxConcurrentComponents intstr.IntOrString `json:"maxConcurrentComponents,omitempty"`

	// Specifies the components and their parameters for executing custom actions as defined in OpsDefinition.
	// Requires at least one component.
	//
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:MinItems=1
	// +kubebuilder:validation:MaxItems=1024
	// +patchMergeKey=componentName
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=componentName
	CustomOpsComponents []CustomOpsComponent `json:"components"  patchStrategy:"merge,retainKeys" patchMergeKey:"componentName"`
}

type CustomOpsComponent struct {
	// Specifies the name of the Component.
	ComponentOps `json:",inline"`

	// Specifies the parameters that match the schema specified in the `opsDefinition.spec.parametersSchema`.
	//
	// +patchMergeKey=name
	// +patchStrategy=merge,retainKeys
	// +listType=map
	// +listMapKey=name
	// +optional
	Parameters []Parameter `json:"parameters,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
}

type Parameter struct {
	// Specifies the identifier of the parameter as defined in the OpsDefinition.
	// +kubebuilder:validation:Required
	Name string `json:"name"`

	// Holds the data associated with the parameter.
	// If the parameter type is an array, the format should be "v1,v2,v3".
	// +kubebuilder:validation:Required
	Value string `json:"value"`

	// Source for the parameter's value. Cannot be used if value is not empty.
	//
	ValueFrom *ParameterSource `json:"valueFrom,omitempty"`
}

type ParameterSource struct {
	// Selects a key of a ConfigMap.
	// +optional
	ConfigMapKeyRef *corev1.ConfigMapKeySelector `json:"configMapKeyRef,omitempty"`
	// Selects a key of a Secret.
	// +optional
	SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"`
}

type ParameterPair struct {
	// Represents the name of the parameter that is to be updated.
	// +kubebuilder:validation:Required
	Key string `json:"key"`

	// Represents the parameter values that are to be updated.
	// If set to nil, the parameter defined by the Key field will be removed from the configuration file.
	// +optional
	Value *string `json:"value"`
}

// ExposeSwitch Specifies the switch for the expose operation. This switch can be used to enable or disable the expose operation.
// +enum
// +kubebuilder:validation:Enum={Enable, Disable}
type ExposeSwitch string

const (
	EnableExposeSwitch  ExposeSwitch = "Enable"
	DisableExposeSwitch ExposeSwitch = "Disable"
)

type Expose struct {
	// Specifies the name of the Component.
	ComponentName string `json:"componentName,omitempty"`

	// Indicates whether the services will be exposed.
	// 'Enable' exposes the services. while 'Disable' removes the exposed Service.
	//
	// +kubebuilder:validation:Required
	Switch ExposeSwitch `json:"switch"`

	// Specifies a list of OpsService.
	// When an OpsService is exposed, a corresponding ClusterService will be added to `cluster.spec.services`.
	// On the other hand, when an OpsService is unexposed, the corresponding ClusterService will be removed
	// from `cluster.spec.services`.
	//
	// Note: If `componentName` is not specified, the `ports` and `selector` fields must be provided
	// in each OpsService definition.
	//
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:Minitems=0
	Services []OpsService `json:"services"`
}

// OpsService represents the parameters to dynamically create or remove a ClusterService in the `cluster.spec.services` array.
type OpsService struct {
	// Specifies the name of the Service. This name is used to set `clusterService.name`.
	//
	// Note: This field cannot be updated.
	//
	// +required
	Name string `json:"name"`

	// Contains cloud provider related parameters if ServiceType is LoadBalancer.
	//
	// More info: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer.
	//
	// +optional
	Annotations map[string]string `json:"annotations,omitempty"`

	// Specifies Port definitions that are to be exposed by a ClusterService.
	//
	// If not specified, the Port definitions from non-NodePort and non-LoadBalancer type ComponentService
	// defined in the ComponentDefinition (`componentDefinition.spec.services`) will be used.
	// If no matching ComponentService is found, the expose operation will fail.
	//
	// More info: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
	//
	// +patchMergeKey=port
	// +patchStrategy=merge
	// +listType=map
	// +listMapKey=port
	// +listMapKey=protocol
	// +optional
	Ports []corev1.ServicePort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"port" protobuf:"bytes,1,rep,name=ports"`

	// Specifies a role to target with the service.
	// If specified, the service will only be exposed to pods with the matching role.
	//
	// Note: If the component has roles, at least one of 'roleSelector' or 'podSelector' must be specified.
	// If both are specified, a pod must match both conditions to be selected.
	//
	// +optional
	RoleSelector string `json:"roleSelector,omitempty"`

	// Routes service traffic to pods with matching label keys and values.
	// If specified, the service will only be exposed to pods matching the selector.
	//
	// Note: If the component has roles, at least one of 'roleSelector' or 'podSelector' must be specified.
	// If both are specified, a pod must match both conditions to be selected.
	//
	// +optional
	// +mapType=atomic
	PodSelector map[string]string `json:"podSelector,omitempty"`

	// Determines how the Service is exposed. Defaults to 'ClusterIP'.
	// Valid options are `ClusterIP`, `NodePort`, and `LoadBalancer`.
	//
	// - `ClusterIP`: allocates a cluster-internal IP address for load-balancing to endpoints.
	//    Endpoints are determined by the selector or if that is not specified,
	//    they are determined by manual construction of an Endpoints object or EndpointSlice objects.
	// - `NodePort`: builds on ClusterIP and allocates a port on every node which routes to the same endpoints as the clusterIP.
	// - `LoadBalancer`: builds on NodePort and creates an external load-balancer (if supported in the current cloud)
	//    which routes to the same endpoints as the clusterIP.
	//
	// Note: although K8s Service type allows the 'ExternalName' type, it is not a valid option for the expose operation.
	//
	// For more info, see:
	// https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types.
	//
	// +optional
	ServiceType corev1.ServiceType `json:"serviceType,omitempty"`

	// A list of IP families (e.g., IPv4, IPv6) assigned to this Service.
	//
	// Usually assigned automatically based on the cluster configuration and the `ipFamilyPolicy` field.
	// If specified manually, the requested IP family must be available in the cluster and allowed by the `ipFamilyPolicy`.
	// If the requested IP family is not available or not allowed, the Service creation will fail.
	//
	// Valid values:
	//
	// - "IPv4"
	// - "IPv6"
	//
	// This field may hold a maximum of two entries (dual-stack families, in either order).
	//
	// Common combinations of `ipFamilies` and `ipFamilyPolicy` are:
	//
	// - ipFamilies=[] + ipFamilyPolicy="PreferDualStack" :
	//   The Service prefers dual-stack but can fall back to single-stack if the cluster does not support dual-stack.
	//   The IP family is automatically assigned based on the cluster configuration.
	// - ipFamilies=["IPV4","IPV6"] + ipFamilyPolicy="RequiredDualStack" :
	//   The Service requires dual-stack and will only be created if the cluster supports both IPv4 and IPv6.
	//   The primary IP family is IPV4.
	// - ipFamilies=["IPV6","IPV4"] + ipFamilyPolicy="RequiredDualStack" :
	//   The Service requires dual-stack and will only be created if the cluster supports both IPv4 and IPv6.
	//   The primary IP family is IPV6.
	// - ipFamilies=["IPV4"] + ipFamilyPolicy="SingleStack" :
	//   The Service uses a single-stack with IPv4 only.
	// - ipFamilies=["IPV6"] + ipFamilyPolicy="SingleStack" :
	//   The Service uses a single-stack with IPv6 only.
	//
	// +listType=atomic
	// +optional
	IPFamilies []corev1.IPFamily `json:"ipFamilies,omitempty" protobuf:"bytes,19,opt,name=ipFamilies,casttype=IPFamily"`

	// Specifies whether the Service should use a single IP family (SingleStack) or two IP families (DualStack).
	//
	// Possible values:
	//
	// - 'SingleStack' (default) : The Service uses a single IP family.
	//   If no value is provided, IPFamilyPolicy defaults to SingleStack.
	// - 'PreferDualStack' : The Service prefers to use two IP families on dual-stack configured clusters
	//   or a single IP family on single-stack clusters.
	// - 'RequiredDualStack' : The Service requires two IP families on dual-stack configured clusters.
	//   If the cluster is not configured for dual-stack, the Service creation fails.
	//
	// +optional
	IPFamilyPolicy *corev1.IPFamilyPolicy `json:"ipFamilyPolicy,omitempty" protobuf:"bytes,17,opt,name=ipFamilyPolicy,casttype=IPFamilyPolicy"`
}

type Backup struct {
	// Specifies the name of the Backup custom resource.
	//
	// +optional
	BackupName string `json:"backupName,omitempty"`

	// Indicates the name of the BackupPolicy applied to perform this Backup.
	//
	// +optional
	BackupPolicyName string `json:"backupPolicyName,omitempty"`

	// Specifies the name of BackupMethod.
	// The specified BackupMethod must be defined in the BackupPolicy.
	//
	// +optional
	BackupMethod string `json:"backupMethod,omitempty"`

	// Determines whether the backup contents stored in backup repository
	// should be deleted when the Backup custom resource is deleted.
	// Supported values are `Retain` and `Delete`.
	// - `Retain` means that the backup content and its physical snapshot on backup repository are kept.
	// - `Delete` means that the backup content and its physical snapshot on backup repository are deleted.
	// +kubebuilder:validation:Enum=Delete;Retain
	// +kubebuilder:default=Delete
	// +optional
	DeletionPolicy string `json:"deletionPolicy,omitempty"`

	// Determines the duration for which the Backup custom resources should be retained.
	//
	// The controller will automatically remove all Backup objects that are older than the specified RetentionPeriod.
	// For example, RetentionPeriod of `30d` will keep only the Backup objects of last 30 days.
	// Sample duration format:
	//
	// - years: 2y
	// - months: 6mo
	// - days: 30d
	// - hours: 12h
	// - minutes: 30m
	//
	// You can also combine the above durations. For example: 30d12h30m.
	// If not set, the Backup objects will be kept forever.
	//
	// If the `deletionPolicy` is set to 'Delete', then the associated backup data will also be deleted
	// along with the Backup object.
	// Otherwise, only the Backup custom resource will be deleted.
	//
	// +optional
	RetentionPeriod string `json:"retentionPeriod,omitempty"`

	// If the specified BackupMethod is incremental, `parentBackupName` is required.
	//
	// +optional
	ParentBackupName string `json:"parentBackupName,omitempty"`

	// Specifies a list of name-value pairs representing parameters and their corresponding values.
	// Parameters match the schema specified in the `actionset.spec.parametersSchema`
	//
	// +listType=map
	// +listMapKey=name
	// +kubebuilder:validation:MaxItems=128
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update parameters"
	// +optional
	Parameters []dpv1alpha1.ParameterPair `json:"parameters,omitempty"`
}

type Restore struct {
	// Specifies the name of the Backup custom resource.
	//
	// +kubebuilder:validation:Required
	BackupName string `json:"backupName"`

	// Specifies the namespace of the backup custom resource. If not specified, the namespace of the opsRequest will be used.
	//
	// +optional
	BackupNamespace string `json:"backupNamespace,omitempty"`

	// Specifies the point in time to which the restore should be performed.
	// Supported time formats:
	//
	// - RFC3339 format, e.g. "2023-11-25T18:52:53Z"
	// - A human-readable date-time format, e.g. "Jul 25,2023 18:52:53 UTC+0800"
	//
	RestorePointInTime string `json:"restorePointInTime,omitempty"`

	// Specifies a list of environment variables to be set in the container.
	//
	// +kubebuilder:pruning:PreserveUnknownFields
	// +optional
	Env []corev1.EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name"`

	// Specifies the policy for restoring volume claims of a Component's Pods.
	// It determines whether the volume claims should be restored sequentially (one by one) or in parallel (all at once).
	// Support values:
	//
	// - "Serial"
	// - "Parallel"
	//
	// +kubebuilder:validation:Enum=Serial;Parallel
	// +kubebuilder:default=Parallel
	VolumeRestorePolicy string `json:"volumeRestorePolicy,omitempty"`

	// Controls the timing of PostReady actions during the recovery process.
	//
	// If false (default), PostReady actions execute when the Component reaches the "Running" state.
	// If true, PostReady actions are delayed until the entire Cluster is "Running,"
	// ensuring the cluster's overall stability before proceeding.
	//
	// This setting is useful for coordinating PostReady operations across the Cluster for optimal cluster conditions.
	DeferPostReadyUntilClusterRunning bool `json:"deferPostReadyUntilClusterRunning,omitempty"`

	// Specifies a list of name-value pairs representing parameters and their corresponding values.
	// Parameters match the schema specified in the `actionset.spec.parametersSchema`
	//
	// +listType=map
	// +listMapKey=name
	// +kubebuilder:validation:MaxItems=128
	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="forbidden to update parameters"
	// +optional
	Parameters []dpv1alpha1.ParameterPair `json:"parameters,omitempty"`
}

// OpsRequestStatus represents the observed state of an OpsRequest.
type OpsRequestStatus struct {
	// Records the cluster generation after the OpsRequest action has been handled.
	// +optional
	ClusterGeneration int64 `json:"clusterGeneration,omitempty"`

	// Represents the phase of the OpsRequest.
	// Possible values include "Pending", "Creating", "Running", "Cancelling", "Cancelled", "Failed", "Succeed".
	Phase OpsPhase `json:"phase,omitempty"`

	// Represents the progress of the OpsRequest.
	// +kubebuilder:validation:Pattern:=`^(\d+|\-)/(\d+|\-)$`
	// +kubebuilder:default=-/-
	Progress string `json:"progress"`

	// Records the configuration prior to any changes.
	// +optional
	LastConfiguration LastConfiguration `json:"lastConfiguration,omitempty"`

	// Records the status information of Components changed due to the OpsRequest.
	// +optional
	Components map[string]OpsRequestComponentStatus `json:"components,omitempty"`

	// A collection of additional key-value pairs that provide supplementary information for the OpsRequest.
	Extras []map[string]string `json:"extras,omitempty"`

	// Records the time when the OpsRequest started processing.
	// +optional
	StartTimestamp metav1.Time `json:"startTimestamp,omitempty"`

	// Records the time when the OpsRequest was completed.
	// +optional
	CompletionTimestamp metav1.Time `json:"completionTimestamp,omitempty"`

	// Records the time when the OpsRequest was cancelled.
	// +optional
	CancelTimestamp metav1.Time `json:"cancelTimestamp,omitempty"`

	// Describes the detailed status of the OpsRequest.
	// Possible condition types include "Cancelled", "WaitForProgressing", "Validated", "Succeed", "Failed", "Restarting",
	// "VerticalScaling", "HorizontalScaling", "VolumeExpanding", "Reconfigure", "Switchover", "Stopping", "Starting",
	// "VersionUpgrading", "Exposing", "Backup", "InstancesRebuilding", "CustomOperation".
	// +optional
	// +patchMergeKey=type
	// +patchStrategy=merge
	// +listType=map
	// +listMapKey=type
	Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="has(self.objectKey) || has(self.actionName)", message="at least one objectKey or actionName."

type ProgressStatusDetail struct {
	// Specifies the group to which the current object belongs to.
	// +optional
	Group string `json:"group,omitempty"`

	// `objectKey` uniquely identifies the object, which can be any K8s object, like a Pod, Job, Component, or PVC.
	// Either `objectKey` or `actionName` must be provided.
	// +optional
	ObjectKey string `json:"objectKey,omitempty"`

	// Indicates the name of an OpsAction, as defined in `opsDefinition.spec.actions[*].name`.
	// Either `objectKey` or `actionName` must be provided.
	// +optional
	ActionName string `json:"actionName,omitempty"`

	// Lists the tasks, such as Jobs or Pods, that carry out the action.
	// +optional
	ActionTasks []ActionTask `json:"actionTasks,omitempty"`

	// Represents the current processing state of the object, including "Processing", "Pending", "Failed", "Succeed"
	// +kubebuilder:validation:Required
	Status ProgressStatus `json:"status"`

	// Provides a human-readable explanation of the object's condition.
	// +optional
	Message string `json:"message,omitempty"`

	// Records the start time of object processing.
	// +optional
	StartTime metav1.Time `json:"startTime,omitempty"`

	// Records the completion time of object processing.
	// +optional
	EndTime metav1.Time `json:"endTime,omitempty"`
}

type ActionTask struct {
	// Represents the name of the task.
	// +kubebuilder:validation:Required
	ObjectKey string `json:"objectKey"`

	// Represents the namespace where the task is deployed.
	// +kubebuilder:validation:Required
	Namespace string `json:"namespace"`

	// Indicates the current status of the task, including "Processing", "Failed", "Succeed".
	// +kubebuilder:validation:Required
	Status ActionTaskStatus `json:"status"`

	// The name of the Pod that the task is associated with or operates on.
	// +optional
	TargetPodName string `json:"targetPodName,omitempty"`

	// The count of retry attempts made for this task.
	// +optional
	Retries int32 `json:"retries,omitempty"`
}

// LastComponentConfiguration can be used to track and compare the desired state of the Component over time.
type LastComponentConfiguration struct {
	// Records the `replicas` of the Component prior to any changes.
	// +optional
	Replicas *int32 `json:"replicas,omitempty"`

	// Records the `shards` of the Component prior to any changes.
	// +optional
	Shards *int32 `json:"shards,omitempty"`

	// Records the resources of the Component prior to any changes.
	// +kubebuilder:pruning:PreserveUnknownFields
	// +optional
	corev1.ResourceRequirements `json:",inline,omitempty"`

	// Records volumes' storage size of the Component prior to any changes.
	// +optional
	VolumeClaimTemplates []OpsRequestVolumeClaimTemplate `json:"volumeClaimTemplates,omitempty"`

	// Records the ClusterComponentService list of the Component prior to any changes.
	// +optional
	Services []appsv1.ClusterComponentService `json:"services,omitempty"`

	// Records the InstanceTemplate list of the Component prior to any changes.
	// +optional
	Instances []appsv1.InstanceTemplate `json:"instances,omitempty"`

	// Records the offline instances of the Component prior to any changes.
	// +optional
	OfflineInstances []string `json:"offlineInstances,omitempty"`

	// Records the version of the Service expected to be provisioned by this Component prior to any changes.
	// +optional
	ServiceVersion string `json:"serviceVersion,omitempty"`

	// Records the name of the ComponentDefinition prior to any changes.
	// +optional
	ComponentDefinitionName string `json:"componentDefinitionName,omitempty"`
}

type LastConfiguration struct {

	// Records the configuration of each Component prior to any changes.
	// +optional
	Components map[string]LastComponentConfiguration `json:"components,omitempty"`
}

type OpsRequestComponentStatus struct {
	// Records the current phase of the Component, mirroring `cluster.status.components[componentName].phase`.
	// +optional
	Phase appsv1.ComponentPhase `json:"phase,omitempty"`

	// Records the timestamp when the Component last transitioned to a "Failed" phase.
	// +optional
	LastFailedTime metav1.Time `json:"lastFailedTime,omitempty"`

	// Records the result of the preConditions check of the opsRequest, which determines subsequent steps.
	// +optional
	PreCheckResult *PreCheckResult `json:"preCheck,omitempty"`

	// Describes the progress details of objects or actions associated with the Component.
	// +optional
	ProgressDetails []ProgressStatusDetail `json:"progressDetails,omitempty"`

	// Provides an explanation for the Component being in its current state.
	// +kubebuilder:validation:MaxLength=1024
	// +optional
	Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`

	// Provides a human-readable message indicating details about this operation.
	// +kubebuilder:validation:MaxLength=32768
	// +optional
	Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}

type PreCheckResult struct {
	// Indicates whether the preCheck operation passed or failed.
	// +kubebuilder:validation:Required
	Pass bool `json:"pass"`

	// Provides explanations related to the preCheck result in a human-readable format.
	// +optional
	Message string `json:"message,omitempty"`
}

// UpdatedParameters holds details about the modifications made to configuration parameters.
// Example:
//
// ```yaml
// updatedParameters:
//
//	updatedKeys:
//	  my.cnf: '{"mysqld":{"max_connections":"100"}}'
//
// ```
type UpdatedParameters struct {
	// Maps newly added configuration files to their content.
	// +optional
	AddedKeys map[string]string `json:"addedKeys,omitempty"`

	// Lists the name of configuration files that have been deleted.
	// +optional
	DeletedKeys map[string]string `json:"deletedKeys,omitempty"`

	// Maps the name of configuration files to their updated content, detailing the changes made.
	// +optional
	UpdatedKeys map[string]string `json:"updatedKeys,omitempty"`
}

// +genclient
// +k8s:openapi-gen=true
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:categories={kubeblocks},shortName=ops
// +kubebuilder:printcolumn:name="TYPE",type="string",JSONPath=".spec.type",description="Operation request type."
// +kubebuilder:printcolumn:name="CLUSTER",type="string",JSONPath=".spec.clusterName",description="Operand cluster."
// +kubebuilder:printcolumn:name="STATUS",type="string",JSONPath=".status.phase",description="Operation status phase."
// +kubebuilder:printcolumn:name="PROGRESS",type="string",JSONPath=".status.progress",description="Operation processing progress."
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"

// OpsRequest is the Schema for the opsrequests API
type OpsRequest struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   OpsRequestSpec   `json:"spec,omitempty"`
	Status OpsRequestStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// OpsRequestList contains a list of OpsRequest
type OpsRequestList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []OpsRequest `json:"items"`
}

func init() {
	SchemeBuilder.Register(&OpsRequest{}, &OpsRequestList{})
}

func (c ComponentOps) GetComponentName() string {
	return c.ComponentName
}

// ToExposeListToMap build expose map
func (r OpsRequestSpec) ToExposeListToMap() map[string]Expose {
	exposeMap := make(map[string]Expose)
	for _, v := range r.ExposeList {
		exposeMap[v.ComponentName] = v
	}
	return exposeMap
}

func (r OpsRequestSpec) GetClusterName() string {
	return r.ClusterName
}

func (r OpsRequestSpec) GetBackup() *Backup {
	return r.Backup
}

func (r OpsRequestSpec) GetRestore() *Restore {
	return r.Restore
}

func (p *ProgressStatusDetail) SetStatusAndMessage(status ProgressStatus, message string) {
	p.Message = message
	p.Status = status
}

func (s *Switchover) GetComponentName() string {
	if len(s.ComponentObjectName) > 0 {
		return s.ComponentObjectName
	}
	return s.ComponentName
}
