package components

import (
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"github.com/blang/semver/v4"
	csvVersion "github.com/operator-framework/api/pkg/lib/version"
	csvv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
	"golang.org/x/tools/go/packages"
	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/intstr"
	crdgen "sigs.k8s.io/controller-tools/pkg/crd"
	crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"
	"sigs.k8s.io/controller-tools/pkg/loader"
	"sigs.k8s.io/controller-tools/pkg/markers"

	hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis/hco/v1beta1"
	"github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
	hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
	vmimportv1beta1 "github.com/kubevirt/vm-import-operator/pkg/apis/v2v/v1beta1"
)

const (
	crName              = util.HyperConvergedName
	packageName         = util.HyperConvergedName
	hcoName             = "hyperconverged-cluster-operator"
	hcoNameWebhook      = "hyperconverged-cluster-webhook"
	hcoDeploymentName   = "hco-operator"
	hcoWhDeploymentName = "hco-webhook"
	certVolume          = "apiservice-cert"

	cliDownloadsName = "hyperconverged-cluster-cli-download"

	kubevirtProjectName = "KubeVirt project"
)

var deploymentType = metav1.TypeMeta{
	APIVersion: "apps/v1",
	Kind:       "Deployment",
}

type DeploymentOperatorParams struct {
	Namespace           string
	Image               string
	WebhookImage        string
	CliDownloadsImage   string
	ImagePullPolicy     string
	ConversionContainer string
	VmwareContainer     string
	VirtIOWinContainer  string
	Smbios              string
	Machinetype         string
	HcoKvIoVersion      string
	KubevirtVersion     string
	CdiVersion          string
	CnaoVersion         string
	SspVersion          string
	NmoVersion          string
	HppoVersion         string
	VMImportVersion     string
	Env                 []corev1.EnvVar
}

func GetDeploymentOperator(params *DeploymentOperatorParams) appsv1.Deployment {
	return appsv1.Deployment{
		TypeMeta: deploymentType,
		ObjectMeta: metav1.ObjectMeta{
			Name: hcoName,
			Labels: map[string]string{
				"name": hcoName,
			},
		},
		Spec: GetDeploymentSpecOperator(params),
	}
}

func GetDeploymentWebhook(namespace, image, imagePullPolicy, hcoKvIoVersion string, env []corev1.EnvVar) appsv1.Deployment {
	deploy := appsv1.Deployment{
		TypeMeta: deploymentType,
		ObjectMeta: metav1.ObjectMeta{
			Name: hcoNameWebhook,
			Labels: map[string]string{
				"name": hcoNameWebhook,
			},
		},
		Spec: GetDeploymentSpecWebhook(namespace, image, imagePullPolicy, hcoKvIoVersion, env),
	}

	InjectVolumesForWebHookCerts(&deploy)
	return deploy
}

func GetDeploymentCliDownloads(params *DeploymentOperatorParams) appsv1.Deployment {
	return appsv1.Deployment{
		TypeMeta: deploymentType,
		ObjectMeta: metav1.ObjectMeta{
			Name: cliDownloadsName,
			Labels: map[string]string{
				"name": cliDownloadsName,
			},
		},
		Spec: GetDeploymentSpecCliDownloads(params),
	}
}

func GetServiceWebhook() v1.Service {
	return v1.Service{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Service",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: hcoNameWebhook + "-service",
		},
		Spec: v1.ServiceSpec{
			Selector: map[string]string{
				"name": hcoNameWebhook,
			},
			Ports: []v1.ServicePort{
				{
					Name:       strconv.Itoa(util.WebhookPort),
					Port:       util.WebhookPort,
					Protocol:   corev1.ProtocolTCP,
					TargetPort: intstr.FromInt(util.WebhookPort),
				},
			},
			Type: corev1.ServiceTypeClusterIP,
		},
	}
}

func GetDeploymentSpecOperator(params *DeploymentOperatorParams) appsv1.DeploymentSpec {
	return appsv1.DeploymentSpec{
		Replicas: int32Ptr(1),
		Selector: &metav1.LabelSelector{
			MatchLabels: map[string]string{
				"name": hcoName,
			},
		},
		Strategy: appsv1.DeploymentStrategy{
			Type: appsv1.RollingUpdateDeploymentStrategyType,
		},
		Template: corev1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{
				Labels: getLabels(hcoName, params.HcoKvIoVersion),
			},
			Spec: corev1.PodSpec{
				ServiceAccountName: hcoName,
				Containers: []corev1.Container{
					{
						Name:            hcoName,
						Image:           params.Image,
						ImagePullPolicy: corev1.PullPolicy(params.ImagePullPolicy),
						// command being name is artifact of operator-sdk usage
						Command:        []string{hcoName},
						ReadinessProbe: getReadinessProbe(),
						LivenessProbe:  getLivenessProbe(),
						Env: append([]corev1.EnvVar{
							{
								// deprecated: left here for CI test.
								Name:  util.OperatorWebhookModeEnv,
								Value: "false",
							},
							{
								Name:  util.ContainerAppName,
								Value: util.ContainerOperatorApp,
							},
							{
								Name:  "KVM_EMULATION",
								Value: "",
							},
							{
								Name:  "OPERATOR_IMAGE",
								Value: params.Image,
							},
							{
								Name:  "OPERATOR_NAME",
								Value: hcoName,
							},
							{
								Name:  "OPERATOR_NAMESPACE",
								Value: params.Namespace,
							},
							{
								Name: "POD_NAME",
								ValueFrom: &corev1.EnvVarSource{
									FieldRef: &corev1.ObjectFieldSelector{
										FieldPath: "metadata.name",
									},
								},
							},
							{
								Name:  "WATCH_NAMESPACE",
								Value: "",
							},
							{
								Name:  "CONVERSION_CONTAINER",
								Value: params.ConversionContainer,
							},
							{
								Name:  "VMWARE_CONTAINER",
								Value: params.VmwareContainer,
							},
							{
								Name:  "VIRTIOWIN_CONTAINER",
								Value: params.VirtIOWinContainer,
							},
							{
								Name:  "SMBIOS",
								Value: params.Smbios,
							},
							{
								Name:  "MACHINETYPE",
								Value: params.Machinetype,
							},
							{
								Name:  util.HcoKvIoVersionName,
								Value: params.HcoKvIoVersion,
							},
							{
								Name:  util.KubevirtVersionEnvV,
								Value: params.KubevirtVersion,
							},
							{
								Name:  util.CdiVersionEnvV,
								Value: params.CdiVersion,
							},
							{
								Name:  util.CnaoVersionEnvV,
								Value: params.CnaoVersion,
							},
							{
								Name:  util.SspVersionEnvV,
								Value: params.SspVersion,
							},
							{
								Name:  util.NmoVersionEnvV,
								Value: params.NmoVersion,
							},
							{
								Name:  util.HppoVersionEnvV,
								Value: params.HppoVersion,
							},
							{
								Name:  util.VMImportEnvV,
								Value: params.VMImportVersion,
							},
						}, params.Env...),
						Resources: v1.ResourceRequirements{
							Requests: map[v1.ResourceName]resource.Quantity{
								v1.ResourceCPU:    resource.MustParse("10m"),
								v1.ResourceMemory: resource.MustParse("96Mi"),
							},
						},
					},
				},
				PriorityClassName: "system-cluster-critical",
			},
		},
	}
}

func GetDeploymentSpecCliDownloads(params *DeploymentOperatorParams) appsv1.DeploymentSpec {
	return appsv1.DeploymentSpec{
		Replicas: int32Ptr(1),
		Selector: &metav1.LabelSelector{
			MatchLabels: map[string]string{
				"name": cliDownloadsName,
			},
		},
		Strategy: appsv1.DeploymentStrategy{
			Type: appsv1.RollingUpdateDeploymentStrategyType,
		},
		Template: corev1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{
				Labels: getLabels(cliDownloadsName, params.HcoKvIoVersion),
			},
			Spec: corev1.PodSpec{
				Containers: []corev1.Container{
					{
						Name:            "server",
						Image:           params.CliDownloadsImage,
						ImagePullPolicy: corev1.PullPolicy(params.ImagePullPolicy),
						Resources: v1.ResourceRequirements{
							Requests: map[v1.ResourceName]resource.Quantity{
								v1.ResourceCPU:    resource.MustParse("10m"),
								v1.ResourceMemory: resource.MustParse("96Mi"),
							},
						},
						Ports: []v1.ContainerPort{
							{
								Protocol:      v1.ProtocolTCP,
								ContainerPort: int32(8080),
							},
						},
					},
				},
			},
		},
	}
}

func getLabels(name, hcoKvIoVersion string) map[string]string {
	return map[string]string{
		"name":                    name,
		hcoutil.AppLabelVersion:   hcoKvIoVersion,
		hcoutil.AppLabelPartOf:    hcoutil.HyperConvergedCluster,
		hcoutil.AppLabelComponent: string(hcoutil.AppComponentDeployment),
	}
}

// Currently we are abusing the pod readiness to signal to OLM that HCO is not ready
// for an upgrade. This has a lot of side effects, one of this is the validating webhook
// being not able to receive traffic when exposed by a pod that is not reporting ready=true.
// This can cause a lot of side effects if not deadlocks when the system reach a status where,
// for any possible reason, HCO pod cannot be ready and so HCO pod cannot validate any further update or
// delete request on HCO CR.
// A proper solution is properly use the readiness probe only to report the pod readiness and communicate
// status to OLM via conditions once OLM will be ready for:
// https://github.com/operator-framework/enhancements/blob/master/enhancements/operator-conditions.md
// in the meanwhile a quick (but dirty!) solution is to expose the same hco binary on two distinct pods:
// the first one will run only the controller and the second one (almost always ready) just the validating
// webhook one.
func GetDeploymentSpecWebhook(namespace, image, imagePullPolicy, hcoKvIoVersion string, env []corev1.EnvVar) appsv1.DeploymentSpec {
	return appsv1.DeploymentSpec{
		Replicas: int32Ptr(1),
		Selector: &metav1.LabelSelector{
			MatchLabels: map[string]string{
				"name": hcoNameWebhook,
			},
		},
		Strategy: appsv1.DeploymentStrategy{
			Type: appsv1.RollingUpdateDeploymentStrategyType,
		},
		Template: corev1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{
				Labels: getLabels(hcoNameWebhook, hcoKvIoVersion),
			},
			Spec: corev1.PodSpec{
				ServiceAccountName: hcoName,
				Containers: []corev1.Container{
					{
						Name:            hcoNameWebhook,
						Image:           image,
						ImagePullPolicy: corev1.PullPolicy(imagePullPolicy),
						Command:         []string{hcoNameWebhook},
						ReadinessProbe:  getReadinessProbe(),
						LivenessProbe:   getLivenessProbe(),
						Env: append([]corev1.EnvVar{
							{
								// deprecated: left here for CI test.
								Name:  util.OperatorWebhookModeEnv,
								Value: "true",
							},
							{
								Name:  util.ContainerAppName,
								Value: util.ContainerWebhookApp,
							},
							{
								Name:  "OPERATOR_IMAGE",
								Value: image,
							},
							{
								Name:  "OPERATOR_NAME",
								Value: hcoNameWebhook,
							},
							{
								Name:  "OPERATOR_NAMESPACE",
								Value: namespace,
							},
							{
								Name: "POD_NAME",
								ValueFrom: &corev1.EnvVarSource{
									FieldRef: &corev1.ObjectFieldSelector{
										FieldPath: "metadata.name",
									},
								},
							},
							{
								Name:  "WATCH_NAMESPACE",
								Value: "",
							},
						}, env...),
						Resources: v1.ResourceRequirements{
							Requests: map[v1.ResourceName]resource.Quantity{
								v1.ResourceCPU:    resource.MustParse("5m"),
								v1.ResourceMemory: resource.MustParse("48Mi"),
							},
						},
					},
				},
				PriorityClassName: "system-node-critical",
			},
		},
	}
}

func GetClusterRole() rbacv1.ClusterRole {
	return rbacv1.ClusterRole{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "rbac.authorization.k8s.io/v1",
			Kind:       "ClusterRole",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: hcoName,
			Labels: map[string]string{
				"name": hcoName,
			},
		},
		Rules: GetClusterPermissions(),
	}
}

var (
	emptyAPIGroup = []string{""}
)

func GetClusterPermissions() []rbacv1.PolicyRule {

	return []rbacv1.PolicyRule{
		{
			APIGroups: stringListToSilce(util.APIVersionGroup),
			Resources: stringListToSilce("hyperconvergeds"),
			Verbs:     stringListToSilce("get", "list", "update", "watch"),
		},
		{
			APIGroups: stringListToSilce(util.APIVersionGroup),
			Resources: stringListToSilce("hyperconvergeds/finalizers", "hyperconvergeds/status"),
			Verbs:     stringListToSilce("get", "list", "create", "update", "watch"),
		},
		roleWithAllPermissions("kubevirt.io", stringListToSilce("kubevirts", "kubevirts/finalizers")),
		roleWithAllPermissions("cdi.kubevirt.io", stringListToSilce("cdis", "cdis/finalizers")),
		roleWithAllPermissions("ssp.kubevirt.io", stringListToSilce("ssps", "ssps/finalizers")),
		roleWithAllPermissions("networkaddonsoperator.network.kubevirt.io", stringListToSilce("networkaddonsconfigs", "networkaddonsconfigs/finalizers")),
		roleWithAllPermissions("v2v.kubevirt.io", stringListToSilce("vmimportconfigs", "vmimportconfigs/finalizers")),
		roleWithAllPermissions("", stringListToSilce("configmaps")),
		{
			APIGroups: emptyAPIGroup,
			Resources: stringListToSilce("events"),
			Verbs:     stringListToSilce("get", "list", "watch", "create", "patch"),
		},
		roleWithAllPermissions("", stringListToSilce("services")),
		{
			APIGroups: emptyAPIGroup,
			Resources: stringListToSilce("pods"),
			Verbs:     stringListToSilce("get", "list", "watch"),
		},
		{
			APIGroups: []string{
				"apps",
			},
			Resources: stringListToSilce("deployments", "replicasets"),
			Verbs:     stringListToSilce("get", "list"),
		},
		roleWithAllPermissions("rbac.authorization.k8s.io", stringListToSilce("roles", "rolebindings")),
		{
			APIGroups: []string{
				"apiextensions.k8s.io",
			},
			Resources: stringListToSilce("customresourcedefinitions"),
			Verbs:     stringListToSilce("get", "list", "watch"),
		},
		{
			APIGroups: []string{
				"apiextensions.k8s.io",
			},
			Resources: stringListToSilce("customresourcedefinitions/status"),
			Verbs:     stringListToSilce("get", "list", "watch", "patch", "update"),
		},
		roleWithAllPermissions("monitoring.coreos.com", stringListToSilce("servicemonitors", "prometheusrules")),
		{
			APIGroups: []string{
				"operators.coreos.com",
			},
			Resources: stringListToSilce("clusterserviceversions"),
			Verbs:     stringListToSilce("get", "list", "watch"),
		},
		{
			APIGroups: []string{"scheduling.k8s.io"},
			Resources: stringListToSilce("priorityclasses"),
			Verbs:     stringListToSilce("get", "list", "watch", "create", "delete"),
		},
		{
			APIGroups: []string{
				"admissionregistration.k8s.io",
			},
			Resources: stringListToSilce("validatingwebhookconfigurations"),
			Verbs:     stringListToSilce("list", "watch", "update", "patch"),
		},
		roleWithAllPermissions("console.openshift.io", stringListToSilce("consoleclidownloads", "consolequickstarts")),
		{
			APIGroups: []string{
				"config.openshift.io",
			},
			Resources: stringListToSilce("clusterversions", "ingresses"),
			Verbs:     stringListToSilce("get", "list"),
		},
		roleWithAllPermissions("coordination.k8s.io", stringListToSilce("leases")),
		roleWithAllPermissions("route.openshift.io", stringListToSilce("routes")),
	}
}

func roleWithAllPermissions(apiGroup string, resources []string) rbacv1.PolicyRule {
	return rbacv1.PolicyRule{
		APIGroups: stringListToSilce(apiGroup),
		Resources: resources,
		Verbs:     stringListToSilce("get", "list", "watch", "create", "update", "delete"),
	}
}

func GetServiceAccount(namespace string) v1.ServiceAccount {
	return v1.ServiceAccount{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "ServiceAccount",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      hcoName,
			Namespace: namespace,
			Labels: map[string]string{
				"name": hcoName,
			},
		},
	}
}

func GetClusterRoleBinding(namespace string) rbacv1.ClusterRoleBinding {
	return rbacv1.ClusterRoleBinding{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "rbac.authorization.k8s.io/v1",
			Kind:       "ClusterRoleBinding",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: hcoName,
			Labels: map[string]string{
				"name": hcoName,
			},
		},
		RoleRef: rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "ClusterRole",
			Name:     hcoName,
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      hcoName,
				Namespace: namespace,
			},
		},
	}
}

func packageErrors(pkg *loader.Package, filterKinds ...packages.ErrorKind) error {
	toSkip := make(map[packages.ErrorKind]struct{})
	for _, errKind := range filterKinds {
		toSkip[errKind] = struct{}{}
	}
	var outErr error
	packages.Visit([]*packages.Package{pkg.Package}, nil, func(pkgRaw *packages.Package) {
		for _, err := range pkgRaw.Errors {
			if _, skip := toSkip[err.Kind]; skip {
				continue
			}
			outErr = err
		}
	})
	return outErr
}

const objectType = "object"

func GetOperatorCRD(relPath string) *extv1.CustomResourceDefinition {
	pkgs, err := loader.LoadRoots(relPath)
	if err != nil {
		panic(err)
	}
	reg := &markers.Registry{}
	panicOnError(crdmarkers.Register(reg))

	parser := &crdgen.Parser{
		Collector: &markers.Collector{Registry: reg},
		Checker:   &loader.TypeChecker{},
	}
	crdgen.AddKnownTypes(parser)
	if len(pkgs) == 0 {
		panic("Failed identifying packages")
	}
	for _, p := range pkgs {
		parser.NeedPackage(p)
	}
	groupKind := schema.GroupKind{Kind: util.HyperConvergedKind, Group: util.APIVersionGroup}
	parser.NeedCRDFor(groupKind, nil)
	for _, p := range pkgs {
		err = packageErrors(p, packages.TypeError)
		if err != nil {
			panic(err)
		}
	}
	c := parser.CustomResourceDefinitions[groupKind]
	// enforce validation of CR name to prevent multiple CRs
	for _, v := range c.Spec.Versions {
		v.Schema.OpenAPIV3Schema.Properties["metadata"] = extv1.JSONSchemaProps{
			Type: objectType,
			Properties: map[string]extv1.JSONSchemaProps{
				"name": {
					Type:    "string",
					Pattern: hcov1beta1.HyperConvergedName,
				},
			},
		}
	}
	return &c
}

var crdMeta = metav1.TypeMeta{
	APIVersion: "apiextensions.k8s.io/v1",
	Kind:       "CustomResourceDefinition",
}

func getSchemaInitialProps() map[string]extv1.JSONSchemaProps {
	return map[string]extv1.JSONSchemaProps{
		"apiVersion": {
			Type: "string",
			Description: `APIVersion defines the versioned schema of this representation
                        of an object. Servers should convert recognized schemas to the latest
                        internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources`,
		},
		"kind": {
			Type: "string",
			Description: `Kind is a string value representing the REST resource this
                        object represents. Servers may infer this from the endpoint the client
                        submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds`,
		},
		"metadata": {Type: objectType},
	}
}

// TODO: remove once VMware provider is removed from HCO
// GetV2VCRD creates CRD for v2v VMWare provider
func GetV2VCRD() *extv1.CustomResourceDefinition {
	versionSchema := &extv1.CustomResourceValidation{
		OpenAPIV3Schema: &extv1.JSONSchemaProps{
			Description: "V2VVmware is the Schema for the v2vvmwares API",
			Type:        objectType,
			Properties:  getSchemaInitialProps(),
		},
	}

	versionSchema.OpenAPIV3Schema.Properties["spec"] = extv1.JSONSchemaProps{
		Description: "V2VVmwareSpec defines the desired state of V2VVmware",
		Type:        objectType,
		Properties: map[string]extv1.JSONSchemaProps{
			"connection": {Type: "string"},
			"thumbprint": {Type: "string"},
			"timeToLive": {Type: "string"},
			"vms": {
				Items: &extv1.JSONSchemaPropsOrArray{
					Schema: &extv1.JSONSchemaProps{
						Type: objectType,
						Properties: map[string]extv1.JSONSchemaProps{
							"detail": {
								Type: objectType,
								Properties: map[string]extv1.JSONSchemaProps{
									"hostPath": {
										Type: "string",
									},
									"raw": {
										Type:        "string",
										Description: "TODO: list required details",
									},
								},
								Required: []string{"hostPath"},
							},
							"detailRequest": {Type: "boolean"},
							"name":          {Type: "string"},
						},
						Required: []string{"name"},
					},
				},
				Type: "array",
			},
		},
	}
	versionSchema.OpenAPIV3Schema.Properties["status"] = extv1.JSONSchemaProps{
		Description: "V2VVmwareStatus defines the observed state of V2VVmware",
		Type:        objectType,
		Properties: map[string]extv1.JSONSchemaProps{
			"phase": {
				Type: "string",
			},
		},
	}

	names := extv1.CustomResourceDefinitionNames{
		Plural:   "v2vvmwares",
		Singular: "v2vvmware",
		Kind:     "V2VVmware",
		ListKind: "V2VVmwareList",
	}

	return getCrd("v2vvmwares."+vmimportv1beta1.SchemeGroupVersion.Group, versionSchema, names)
}

// TODO: remove once oVirt provider  is removed from HCO
// GetV2VOvirtProviderCRD creates CRD for v2v oVirt provider
func GetV2VOvirtProviderCRD() *extv1.CustomResourceDefinition {
	versionSchema := &extv1.CustomResourceValidation{
		OpenAPIV3Schema: &extv1.JSONSchemaProps{
			Description: "OVirtProvider is the Schema for the ovirtproviders API",
			Type:        objectType,
			Properties: map[string]extv1.JSONSchemaProps{
				"apiVersion": {
					Type: "string",
					Description: `APIVersion defines the versioned schema of this representation
                        of an object. Servers should convert recognized schemas to the latest
                        internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources`,
				},
				"kind": {
					Type: "string",
					Description: `Kind is a string value representing the REST resource this
                        object represents. Servers may infer this from the endpoint the client
                        submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds`,
				},
				"metadata": {Type: objectType},
			},
		},
	}

	versionSchema.OpenAPIV3Schema.Properties["spec"] = extv1.JSONSchemaProps{
		Description: "OVirtProviderSpec defines the desired state of OVirtProvider",
		Type:        objectType,
		Properties: map[string]extv1.JSONSchemaProps{
			"connection": {Type: "string"},
			"timeToLive": {Type: "string"},
			"vms": {
				Items: &extv1.JSONSchemaPropsOrArray{
					Schema: &extv1.JSONSchemaProps{
						Description: "OVirtVM aligns with maintained UI interface",
						Type:        objectType,
						Properties: map[string]extv1.JSONSchemaProps{
							"cluster": {Type: "string"},
							"detail": {
								Description: "OVirtVMDetail contains ovirt vm details as json string",
								Type:        objectType,
								Properties: map[string]extv1.JSONSchemaProps{
									"raw": {Type: "string"},
								},
							},
							"detailRequest": {Type: "boolean"},
							"id":            {Type: "string"},
							"name":          {Type: "string"},
						},
						Required: []string{"cluster", "id", "name"},
					},
				},
				Type: "array",
			},
		},
	}

	versionSchema.OpenAPIV3Schema.Properties["status"] = extv1.JSONSchemaProps{
		Description: "OVirtProviderStatus defines the observed state of OVirtProvider",
		Type:        objectType,
		Properties: map[string]extv1.JSONSchemaProps{
			"phase": {
				Description: "VirtualMachineProviderPhase defines provider phase",
				Type:        "string",
			},
		},
	}

	names := extv1.CustomResourceDefinitionNames{
		Plural:   "ovirtproviders",
		Singular: "ovirtprovider",
		Kind:     "OVirtProvider",
		ListKind: "OVirtProviderList",
	}

	return getCrd("ovirtproviders."+vmimportv1beta1.SchemeGroupVersion.Group, versionSchema, names)
}

func GetOperatorCR() *hcov1beta1.HyperConverged {
	completionTimeoutPerGiB := int64(800)
	parallelMigrationsPerCluster := uint32(5)
	parallelOutboundMigrationsPerNode := uint32(2)
	progressTimeout := int64(150)

	return &hcov1beta1.HyperConverged{
		TypeMeta: metav1.TypeMeta{
			APIVersion: util.APIVersion,
			Kind:       util.HyperConvergedKind,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: crName,
		},
		Spec: hcov1beta1.HyperConvergedSpec{
			CertConfig: hcov1beta1.HyperConvergedCertConfig{
				CA: hcov1beta1.CertRotateConfigCA{
					Duration:    metav1.Duration{Duration: 48 * time.Hour},
					RenewBefore: metav1.Duration{Duration: 24 * time.Hour},
				},
				Server: hcov1beta1.CertRotateConfigServer{
					Duration:    metav1.Duration{Duration: 24 * time.Hour},
					RenewBefore: metav1.Duration{Duration: 12 * time.Hour},
				},
			},
			FeatureGates: hcov1beta1.HyperConvergedFeatureGates{
				WithHostPassthroughCPU: false,
				SRIOVLiveMigration:     true,
			},
			LiveMigrationConfig: hcov1beta1.LiveMigrationConfigurations{
				CompletionTimeoutPerGiB:           &completionTimeoutPerGiB,
				ParallelMigrationsPerCluster:      &parallelMigrationsPerCluster,
				ParallelOutboundMigrationsPerNode: &parallelOutboundMigrationsPerNode,
				ProgressTimeout:                   &progressTimeout,
			},
			LocalStorageClassName: "",
		},
	}
}

// GetInstallStrategyBase returns the basics of an HCO InstallStrategy
func GetInstallStrategyBase(params *DeploymentOperatorParams) *csvv1alpha1.StrategyDetailsDeployment {
	return &csvv1alpha1.StrategyDetailsDeployment{

		DeploymentSpecs: []csvv1alpha1.StrategyDeploymentSpec{
			{
				Name:  hcoDeploymentName,
				Spec:  GetDeploymentSpecOperator(params),
				Label: getLabels(hcoName, params.HcoKvIoVersion),
			},
			{
				Name:  hcoWhDeploymentName,
				Spec:  GetDeploymentSpecWebhook(params.Namespace, params.WebhookImage, params.ImagePullPolicy, params.HcoKvIoVersion, params.Env),
				Label: getLabels(hcoNameWebhook, params.HcoKvIoVersion),
			},
			{
				Name:  cliDownloadsName,
				Spec:  GetDeploymentSpecCliDownloads(params),
				Label: getLabels(cliDownloadsName, params.HcoKvIoVersion),
			},
		},
		Permissions: []csvv1alpha1.StrategyDeploymentPermissions{},
		ClusterPermissions: []csvv1alpha1.StrategyDeploymentPermissions{
			{
				ServiceAccountName: hcoName,
				Rules:              GetClusterPermissions(),
			},
		},
	}
}

type CSVBaseParams struct {
	Name            string
	Namespace       string
	DisplayName     string
	MetaDescription string
	Description     string
	Image           string
	Replaces        string
	Version         semver.Version
	CrdDisplay      string
}

// GetCSVBase returns a base HCO CSV without an InstallStrategy
func GetCSVBase(params *CSVBaseParams) *csvv1alpha1.ClusterServiceVersion {
	almExamples, _ := json.Marshal(
		map[string]interface{}{
			"apiVersion": util.APIVersion,
			"kind":       util.HyperConvergedKind,
			"metadata": map[string]interface{}{
				"name":      packageName,
				"namespace": params.Namespace,
				"annotations": map[string]string{
					"deployOVS": "false",
				},
			},
			"spec": map[string]interface{}{},
		})

	sideEffect := admissionregistrationv1.SideEffectClassNone
	// Explicitly fail on unvalidated (for any reason) requests:
	// this can make removing HCO CR harder if HCO webhook is not able
	// to really validate the requests.
	// In that case the user can only directly remove the
	// ValidatingWebhookConfiguration object first (eventually bypassing the OLM if needed).
	failurePolicy := admissionregistrationv1.Fail
	webhookPath := util.HCOWebhookPath
	var webhookTimeout int32 = 10

	validatingWebhook := csvv1alpha1.WebhookDescription{
		GenerateName:            util.HcoValidatingWebhook,
		Type:                    csvv1alpha1.ValidatingAdmissionWebhook,
		DeploymentName:          hcoWhDeploymentName,
		ContainerPort:           util.WebhookPort,
		AdmissionReviewVersions: []string{"v1beta1", "v1"},
		SideEffects:             &sideEffect,
		FailurePolicy:           &failurePolicy,
		TimeoutSeconds:          &webhookTimeout,
		Rules: []admissionregistrationv1.RuleWithOperations{
			{
				Operations: []admissionregistrationv1.OperationType{
					admissionregistrationv1.Create,
					admissionregistrationv1.Delete,
					admissionregistrationv1.Update,
				},
				Rule: admissionregistrationv1.Rule{
					APIGroups:   []string{util.APIVersionGroup},
					APIVersions: []string{util.APIVersionAlpha, util.APIVersionBeta},
					Resources:   []string{"hyperconvergeds"},
				},
			},
		},
		WebhookPath: &webhookPath,
	}

	mutatingWebhookSideEffects := admissionregistrationv1.SideEffectClassNoneOnDryRun
	mutatingWebhookPath := util.HCONSWebhookPath

	mutatingWebhook := csvv1alpha1.WebhookDescription{
		GenerateName:            util.HcoMutatingWebhookNS,
		Type:                    csvv1alpha1.MutatingAdmissionWebhook,
		DeploymentName:          hcoWhDeploymentName,
		ContainerPort:           util.WebhookPort,
		AdmissionReviewVersions: []string{"v1beta1", "v1"},
		SideEffects:             &mutatingWebhookSideEffects,
		FailurePolicy:           &failurePolicy,
		TimeoutSeconds:          &webhookTimeout,
		ObjectSelector: &metav1.LabelSelector{
			MatchLabels: map[string]string{"name": params.Namespace},
		},
		Rules: []admissionregistrationv1.RuleWithOperations{
			{
				Operations: []admissionregistrationv1.OperationType{
					admissionregistrationv1.Delete,
				},
				Rule: admissionregistrationv1.Rule{
					APIGroups:   []string{""},
					APIVersions: []string{"v1"},
					Resources:   []string{"namespaces"},
				},
			},
		},
		WebhookPath: &mutatingWebhookPath,
	}

	return &csvv1alpha1.ClusterServiceVersion{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "operators.coreos.com/v1alpha1",
			Kind:       "ClusterServiceVersion",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%v.v%v", params.Name, params.Version.String()),
			Namespace: "placeholder",
			Annotations: map[string]string{
				"alm-examples":   string(almExamples),
				"capabilities":   "Full Lifecycle",
				"certified":      "false",
				"categories":     "OpenShift Optional",
				"containerImage": params.Image,
				"createdAt":      time.Now().Format("2006-01-02 15:04:05"),
				"description":    params.MetaDescription,
				"repository":     "https://github.com/kubevirt/hyperconverged-cluster-operator",
				"support":        "false",
				"operatorframework.io/suggested-namespace":       params.Namespace,
				"operators.openshift.io/infrastructure-features": `["Disconnected"]`,
				"operatorframework.io/initialization-resource":   string(almExamples),
			},
		},
		Spec: csvv1alpha1.ClusterServiceVersionSpec{
			DisplayName: params.DisplayName,
			Description: params.Description,
			Keywords:    []string{"KubeVirt", "Virtualization"},
			Version:     csvVersion.OperatorVersion{Version: params.Version},
			Replaces:    params.Replaces,
			Maintainers: []csvv1alpha1.Maintainer{
				{
					Name:  kubevirtProjectName,
					Email: "kubevirt-dev@googlegroups.com",
				},
			},
			Maturity: "alpha",
			Provider: csvv1alpha1.AppLink{
				Name: kubevirtProjectName,
				// https://github.com/operator-framework/operator-courier/issues/173
				// URL:  "https://kubevirt.io",
			},
			Links: []csvv1alpha1.AppLink{
				{
					Name: kubevirtProjectName,
					URL:  "https://kubevirt.io",
				},
				{
					Name: "Source Code",
					URL:  "https://github.com/kubevirt/hyperconverged-cluster-operator",
				},
			},
			Icon: []csvv1alpha1.Icon{
				{
					MediaType: "image/png",
					Data:      " ",
				},
			},
			Labels: map[string]string{
				"alm-owner-kubevirt": packageName,
				"operated-by":        packageName,
			},
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"alm-owner-kubevirt": packageName,
					"operated-by":        packageName,
				},
			},
			InstallModes: []csvv1alpha1.InstallMode{
				{
					Type:      csvv1alpha1.InstallModeTypeOwnNamespace,
					Supported: false,
				},
				{
					Type:      csvv1alpha1.InstallModeTypeSingleNamespace,
					Supported: false,
				},
				{
					Type:      csvv1alpha1.InstallModeTypeMultiNamespace,
					Supported: false,
				},
				{
					Type:      csvv1alpha1.InstallModeTypeAllNamespaces,
					Supported: true,
				},
			},
			// Skip this in favor of having a separate function to get
			// the actual StrategyDetailsDeployment when merging CSVs
			InstallStrategy:    csvv1alpha1.NamedInstallStrategy{},
			WebhookDefinitions: []csvv1alpha1.WebhookDescription{validatingWebhook, mutatingWebhook},
			CustomResourceDefinitions: csvv1alpha1.CustomResourceDefinitions{
				Owned: []csvv1alpha1.CRDDescription{
					{
						Name:        "hyperconvergeds.hco.kubevirt.io",
						Version:     util.CurrentAPIVersion,
						Kind:        util.HyperConvergedKind,
						DisplayName: params.CrdDisplay + " Deployment",
						Description: "Represents the deployment of " + params.CrdDisplay,
						// TODO: move this to annotations on hyperconverged_types.go once kubebuilder
						// properly supports SpecDescriptors as the operator-sdk already does
						SpecDescriptors: []csvv1alpha1.SpecDescriptor{
							{
								DisplayName: "Infra components node affinity",
								Description: "nodeAffinity describes node affinity scheduling rules for the infra pods.",
								Path:        "infra.nodePlacement.affinity.nodeAffinity",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:nodeAffinity",
								},
							},
							{
								DisplayName: "Infra components pod affinity",
								Description: "podAffinity describes pod affinity scheduling rules for the infra pods.",
								Path:        "infra.nodePlacement.affinity.podAffinity",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:podAffinity",
								},
							},
							{
								DisplayName: "Infra components pod anti-affinity",
								Description: "podAntiAffinity describes pod anti affinity scheduling rules for the infra pods.",
								Path:        "infra.nodePlacement.affinity.podAntiAffinity",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:podAntiAffinity",
								},
							},
							{
								DisplayName: "Workloads components node affinity",
								Description: "nodeAffinity describes node affinity scheduling rules for the workloads pods.",
								Path:        "workloads.nodePlacement.affinity.nodeAffinity",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:nodeAffinity",
								},
							},
							{
								DisplayName: "Workloads components pod affinity",
								Description: "podAffinity describes pod affinity scheduling rules for the workloads pods.",
								Path:        "workloads.nodePlacement.affinity.podAffinity",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:podAffinity",
								},
							},
							{
								DisplayName: "Workloads components pod anti-affinity",
								Description: "podAntiAffinity describes pod anti affinity scheduling rules for the workloads pods.",
								Path:        "workloads.nodePlacement.affinity.podAntiAffinity",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:podAntiAffinity",
								},
							},
							{
								DisplayName: "HIDDEN FIELDS - operator version",
								Description: "HIDDEN FIELDS - operator version.",
								Path:        "version",
								XDescriptors: []string{
									"urn:alm:descriptor:com.tectonic.ui:hidden",
								},
							},
						},
						StatusDescriptors: []csvv1alpha1.StatusDescriptor{},
					},
					// TODO: remove once oVirt and VMware providers are removed from HCO
					{
						Name:        "v2vvmwares.v2v.kubevirt.io",
						Version:     "v1alpha1",
						Kind:        "V2VVmware",
						DisplayName: "V2V Vmware",
						Description: "V2V Vmware",
					},
					{
						Name:        "ovirtproviders.v2v.kubevirt.io",
						Version:     "v1alpha1",
						Kind:        "OVirtProvider",
						DisplayName: "V2V oVirt",
						Description: "V2V oVirt",
					},
				},
				Required: []csvv1alpha1.CRDDescription{},
			},
		},
	}
}

func InjectVolumesForWebHookCerts(deploy *appsv1.Deployment) {
	defaultMode := int32(420)
	volume := v1.Volume{
		Name: certVolume,
		VolumeSource: corev1.VolumeSource{
			Secret: &corev1.SecretVolumeSource{
				SecretName:  deploy.Name + "-service-cert",
				DefaultMode: &defaultMode,
				Items: []corev1.KeyToPath{
					{
						Key:  "tls.crt",
						Path: hcov1beta1.WebhookCertName,
					},
					{
						Key:  "tls.key",
						Path: hcov1beta1.WebhookKeyName,
					},
				},
			},
		},
	}
	deploy.Spec.Template.Spec.Volumes = append(deploy.Spec.Template.Spec.Volumes, volume)

	for index, container := range deploy.Spec.Template.Spec.Containers {
		deploy.Spec.Template.Spec.Containers[index].VolumeMounts = append(container.VolumeMounts,
			corev1.VolumeMount{
				Name:      "apiservice-cert",
				MountPath: hcov1beta1.DefaultWebhookCertDir,
			})
	}
}

func getReadinessProbe() *corev1.Probe {
	return &corev1.Probe{
		Handler: corev1.Handler{
			HTTPGet: &corev1.HTTPGetAction{
				Path: hcoutil.ReadinessEndpointName,
				Port: intstr.IntOrString{
					Type:   intstr.Int,
					IntVal: hcoutil.HealthProbePort,
				},
				Scheme: corev1.URISchemeHTTP,
			},
		},
		InitialDelaySeconds: 5,
		PeriodSeconds:       5,
		FailureThreshold:    1,
	}
}

func getLivenessProbe() *corev1.Probe {
	return &corev1.Probe{
		Handler: corev1.Handler{
			HTTPGet: &corev1.HTTPGetAction{
				Path: hcoutil.LivenessEndpointName,
				Port: intstr.IntOrString{
					Type:   intstr.Int,
					IntVal: hcoutil.HealthProbePort,
				},
				Scheme: corev1.URISchemeHTTP,
			},
		},
		InitialDelaySeconds: 30,
		PeriodSeconds:       5,
		FailureThreshold:    1,
	}
}

func stringListToSilce(words ...string) []string {
	return words
}

func getCrd(crdName string, versionSchema *extv1.CustomResourceValidation, names extv1.CustomResourceDefinitionNames) *extv1.CustomResourceDefinition {
	return &extv1.CustomResourceDefinition{
		TypeMeta: crdMeta,
		ObjectMeta: metav1.ObjectMeta{
			Name: crdName,
		},
		Spec: getCrdSpec(versionSchema, names),
	}
}

func getCrdSpec(schema *extv1.CustomResourceValidation, names extv1.CustomResourceDefinitionNames) extv1.CustomResourceDefinitionSpec {
	return extv1.CustomResourceDefinitionSpec{
		Group: vmimportv1beta1.SchemeGroupVersion.Group,
		Scope: "Namespaced",
		Versions: []extv1.CustomResourceDefinitionVersion{
			{
				Name:    "v1alpha1",
				Served:  true,
				Storage: true,
				Subresources: &extv1.CustomResourceSubresources{
					Status: &extv1.CustomResourceSubresourceStatus{},
				},
				Schema: schema,
			},
		},
		Names: names,
	}
}

func int32Ptr(i int32) *int32 {
	return &i
}

func panicOnError(err error) {
	if err != nil {
		panic(err)
	}
}
