//
// Copyright (c) 2019-2023 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
//   Red Hat, Inc. - initial API and implementation
//

package v1

import (
	"context"
	"strconv"
	"strings"

	"k8s.io/apimachinery/pkg/api/resource"

	"github.com/eclipse-che/che-operator/pkg/common/utils"

	"github.com/devfile/devworkspace-operator/pkg/infrastructure"
	chev2 "github.com/eclipse-che/che-operator/api/v2"
	"github.com/eclipse-che/che-operator/pkg/common/constants"
	k8shelper "github.com/eclipse-che/che-operator/pkg/common/k8s-helper"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/conversion"
)

func (dst *CheCluster) ConvertFrom(srcRaw conversion.Hub) error {
	src := srcRaw.(*chev2.CheCluster)
	dst.ObjectMeta = src.ObjectMeta

	if err := dst.convertFrom_Server(src); err != nil {
		return err
	}

	if err := dst.convertFrom_K8s(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Auth(src); err != nil {
		return err
	}

	if err := dst.convertFrom_DevWorkspace(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Dashboard(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Metrics(src); err != nil {
		return err
	}

	if err := dst.convertFrom_ImagePuller(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Storage(src); err != nil {
		return err
	}

	if err := dst.convertFrom_GitServices(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Status(src); err != nil {
		return err
	}

	return nil
}

func (dst *CheCluster) convertFrom_GitServices(src *chev2.CheCluster) error {
	for _, github := range src.Spec.GitServices.GitHub {
		dst.Spec.GitServices.GitHub = append(
			dst.Spec.GitServices.GitHub,
			GitHubService{
				SecretName: github.SecretName,
			})
	}

	for _, gitlab := range src.Spec.GitServices.GitLab {
		dst.Spec.GitServices.GitLab = append(
			dst.Spec.GitServices.GitLab,
			GitLabService{
				SecretName: gitlab.SecretName,
			})
	}

	for _, bitbucket := range src.Spec.GitServices.BitBucket {
		dst.Spec.GitServices.BitBucket = append(
			dst.Spec.GitServices.BitBucket,
			BitBucketService{
				SecretName: bitbucket.SecretName,
			})
	}

	return nil
}

func (dst *CheCluster) convertFrom_Server(src *chev2.CheCluster) error {
	dst.Spec.Server.AirGapContainerRegistryHostname = src.Spec.ContainerRegistry.Hostname
	dst.Spec.Server.AirGapContainerRegistryOrganization = src.Spec.ContainerRegistry.Organization
	dst.Spec.Server.CheClusterRoles = strings.Join(src.Spec.Components.CheServer.ClusterRoles, ",")
	if src.Spec.DevEnvironments.User != nil {
		dst.Spec.Server.CheWorkspaceClusterRole = strings.Join(src.Spec.DevEnvironments.User.ClusterRoles, ",")
	}
	dst.Spec.Server.CustomCheProperties = utils.CloneMap(src.Spec.Components.CheServer.ExtraProperties)
	if src.Spec.Components.CheServer.Debug != nil {
		dst.Spec.Server.CheDebug = strconv.FormatBool(*src.Spec.Components.CheServer.Debug)
	}
	dst.Spec.Server.CheLogLevel = src.Spec.Components.CheServer.LogLevel
	if src.Spec.Components.CheServer.Proxy != nil {
		dst.Spec.Server.ProxyURL = src.Spec.Components.CheServer.Proxy.Url
		dst.Spec.Server.ProxyPort = src.Spec.Components.CheServer.Proxy.Port
		dst.Spec.Server.NonProxyHosts = strings.Join(src.Spec.Components.CheServer.Proxy.NonProxyHosts, "|")
		dst.Spec.Server.ProxySecret = src.Spec.Components.CheServer.Proxy.CredentialsSecretName
	}
	dst.Spec.Server.AllowAutoProvisionUserNamespace = src.Spec.DevEnvironments.DefaultNamespace.AutoProvision
	dst.Spec.Server.WorkspaceNamespaceDefault = src.Spec.DevEnvironments.DefaultNamespace.Template
	dst.Spec.Server.WorkspacePodNodeSelector = utils.CloneMap(src.Spec.DevEnvironments.NodeSelector)

	for _, v := range src.Spec.DevEnvironments.Tolerations {
		dst.Spec.Server.WorkspacePodTolerations = append(dst.Spec.Server.WorkspacePodTolerations, v)
	}

	for _, p := range src.Spec.DevEnvironments.DefaultPlugins {
		dst.Spec.Server.WorkspacesDefaultPlugins = append(dst.Spec.Server.WorkspacesDefaultPlugins,
			WorkspacesDefaultPlugins{
				Editor:  p.Editor,
				Plugins: p.Plugins,
			})
	}

	dst.Spec.Server.WorkspaceDefaultEditor = src.Spec.DevEnvironments.DefaultEditor
	dst.Spec.Server.WorkspaceDefaultComponents = src.Spec.DevEnvironments.DefaultComponents

	if src.Spec.Components.CheServer.Deployment != nil {
		if len(src.Spec.Components.CheServer.Deployment.Containers) != 0 {
			dst.Spec.Server.CheServerEnv = src.Spec.Components.CheServer.Deployment.Containers[0].Env
			cheServerImageAndTag := strings.Split(src.Spec.Components.CheServer.Deployment.Containers[0].Image, ":")
			dst.Spec.Server.CheImage = strings.Join(cheServerImageAndTag[0:len(cheServerImageAndTag)-1], ":")
			dst.Spec.Server.CheImageTag = cheServerImageAndTag[len(cheServerImageAndTag)-1]
			dst.Spec.Server.CheImagePullPolicy = src.Spec.Components.CheServer.Deployment.Containers[0].ImagePullPolicy

			if src.Spec.Components.CheServer.Deployment.Containers[0].Resources != nil {
				if src.Spec.Components.CheServer.Deployment.Containers[0].Resources.Requests != nil {
					dst.Spec.Server.ServerMemoryRequest = resource2String(src.Spec.Components.CheServer.Deployment.Containers[0].Resources.Requests.Memory)
					dst.Spec.Server.ServerCpuRequest = resource2String(src.Spec.Components.CheServer.Deployment.Containers[0].Resources.Requests.Cpu)
				}
				if src.Spec.Components.CheServer.Deployment.Containers[0].Resources.Limits != nil {
					dst.Spec.Server.ServerMemoryLimit = resource2String(src.Spec.Components.CheServer.Deployment.Containers[0].Resources.Limits.Memory)
					dst.Spec.Server.ServerCpuLimit = resource2String(src.Spec.Components.CheServer.Deployment.Containers[0].Resources.Limits.Cpu)
				}
			}
		}
	}

	if infrastructure.IsOpenShift() {
		dst.Spec.Server.CheHost = src.Spec.Networking.Hostname
		dst.Spec.Server.CheServerRoute.Labels = utils.FormatLabels(src.Spec.Networking.Labels)
		dst.Spec.Server.CheServerRoute.Annotations = utils.CloneMap(src.Spec.Networking.Annotations)
		dst.Spec.Server.CheServerRoute.Domain = src.Spec.Networking.Domain
		dst.Spec.Server.CheHostTLSSecret = src.Spec.Networking.TlsSecretName
	} else {
		dst.Spec.Server.CheHost = src.Spec.Networking.Hostname
		dst.Spec.Server.CheServerIngress.Labels = utils.FormatLabels(src.Spec.Networking.Labels)
		dst.Spec.Server.CheServerIngress.Annotations = utils.CloneMap(src.Spec.Networking.Annotations)
		delete(dst.Spec.Server.CheServerIngress.Annotations, "kubernetes.io/ingress.class")
	}

	if src.Spec.Networking.Auth.Gateway.Deployment != nil {
		for _, c := range src.Spec.Networking.Auth.Gateway.Deployment.Containers {
			switch c.Name {
			case constants.GatewayContainerName:
				dst.Spec.Server.SingleHostGatewayImage = c.Image
			case constants.GatewayConfigSideCarContainerName:
				dst.Spec.Server.SingleHostGatewayConfigSidecarImage = c.Image
			}
		}
	}

	dst.Spec.Server.SingleHostGatewayConfigMapLabels = utils.CloneMap(src.Spec.Networking.Auth.Gateway.ConfigLabels)

	trustStoreConfigMap, err := findTrustStoreConfigMap(src.Namespace)
	if err != nil {
		return err
	} else {
		dst.Spec.Server.ServerTrustStoreConfigMapName = trustStoreConfigMap
	}

	if src.Spec.DevEnvironments.TrustedCerts != nil && src.Spec.DevEnvironments.TrustedCerts.GitTrustedCertsConfigMapName != "" {
		dst.Spec.Server.GitSelfSignedCert = true
	}

	if err := dst.convertFrom_Server_PluginRegistry(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Server_DevfileRegistry(src); err != nil {
		return err
	}

	if err := dst.convertFrom_Server_Dashboard(src); err != nil {
		return err
	}

	return nil
}

func (dst *CheCluster) convertFrom_Server_PluginRegistry(src *chev2.CheCluster) error {
	dst.Spec.Server.OpenVSXRegistryURL = src.Spec.Components.PluginRegistry.OpenVSXURL
	dst.Spec.Server.ExternalPluginRegistry = src.Spec.Components.PluginRegistry.DisableInternalRegistry

	if src.Spec.Components.PluginRegistry.DisableInternalRegistry {
		if len(src.Spec.Components.PluginRegistry.ExternalPluginRegistries) != 0 {
			dst.Spec.Server.PluginRegistryUrl = src.Spec.Components.PluginRegistry.ExternalPluginRegistries[0].Url
		}
	}

	if src.Spec.Components.PluginRegistry.Deployment != nil {
		if len(src.Spec.Components.PluginRegistry.Deployment.Containers) != 0 {
			dst.Spec.Server.PluginRegistryEnv = src.Spec.Components.PluginRegistry.Deployment.Containers[0].Env
			dst.Spec.Server.PluginRegistryImage = src.Spec.Components.PluginRegistry.Deployment.Containers[0].Image
			dst.Spec.Server.PluginRegistryPullPolicy = src.Spec.Components.PluginRegistry.Deployment.Containers[0].ImagePullPolicy

			if src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources != nil {
				if src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources.Requests != nil {
					dst.Spec.Server.PluginRegistryMemoryRequest = resource2String(src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources.Requests.Memory)
					dst.Spec.Server.PluginRegistryCpuRequest = resource2String(src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources.Requests.Cpu)
				}
				if src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources.Limits != nil {
					dst.Spec.Server.PluginRegistryMemoryLimit = resource2String(src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources.Limits.Memory)
					dst.Spec.Server.PluginRegistryCpuLimit = resource2String(src.Spec.Components.PluginRegistry.Deployment.Containers[0].Resources.Limits.Cpu)
				}
			}
		}
	}

	return nil
}

func (dst *CheCluster) convertFrom_Server_DevfileRegistry(src *chev2.CheCluster) error {
	dst.Spec.Server.ExternalDevfileRegistry = src.Spec.Components.DevfileRegistry.DisableInternalRegistry

	for _, r := range src.Spec.Components.DevfileRegistry.ExternalDevfileRegistries {
		dst.Spec.Server.ExternalDevfileRegistries = append(dst.Spec.Server.ExternalDevfileRegistries,
			ExternalDevfileRegistries{
				Url: r.Url,
			})
	}

	if src.Spec.Components.DevfileRegistry.Deployment != nil {
		if len(src.Spec.Components.DevfileRegistry.Deployment.Containers) != 0 {
			dst.Spec.Server.DevfileRegistryEnv = src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Env
			dst.Spec.Server.DevfileRegistryImage = src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Image
			dst.Spec.Server.DevfileRegistryPullPolicy = src.Spec.Components.DevfileRegistry.Deployment.Containers[0].ImagePullPolicy

			if src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources != nil {
				if src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources.Requests != nil {
					dst.Spec.Server.DevfileRegistryMemoryRequest = resource2String(src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources.Requests.Memory)
					dst.Spec.Server.DevfileRegistryCpuRequest = resource2String(src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources.Requests.Cpu)
				}
				if src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources.Limits != nil {
					dst.Spec.Server.DevfileRegistryMemoryLimit = resource2String(src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources.Limits.Memory)
					dst.Spec.Server.DevfileRegistryCpuLimit = resource2String(src.Spec.Components.DevfileRegistry.Deployment.Containers[0].Resources.Limits.Cpu)
				}
			}
		}
	}

	return nil
}

func (dst *CheCluster) convertFrom_Server_Dashboard(src *chev2.CheCluster) error {
	if src.Spec.Components.Dashboard.Deployment != nil {
		if len(src.Spec.Components.Dashboard.Deployment.Containers) != 0 {
			dst.Spec.Server.DashboardEnv = src.Spec.Components.Dashboard.Deployment.Containers[0].Env
			dst.Spec.Server.DashboardImage = src.Spec.Components.Dashboard.Deployment.Containers[0].Image
			dst.Spec.Server.DashboardImagePullPolicy = string(src.Spec.Components.Dashboard.Deployment.Containers[0].ImagePullPolicy)
			if src.Spec.Components.Dashboard.Deployment.Containers[0].Resources != nil {
				if src.Spec.Components.Dashboard.Deployment.Containers[0].Resources.Requests != nil {
					dst.Spec.Server.DashboardMemoryRequest = resource2String(src.Spec.Components.Dashboard.Deployment.Containers[0].Resources.Requests.Memory)
					dst.Spec.Server.DashboardCpuRequest = resource2String(src.Spec.Components.Dashboard.Deployment.Containers[0].Resources.Requests.Cpu)
				}
				if src.Spec.Components.Dashboard.Deployment.Containers[0].Resources.Limits != nil {
					dst.Spec.Server.DashboardMemoryLimit = resource2String(src.Spec.Components.Dashboard.Deployment.Containers[0].Resources.Limits.Memory)
					dst.Spec.Server.DashboardCpuLimit = resource2String(src.Spec.Components.Dashboard.Deployment.Containers[0].Resources.Limits.Cpu)
				}
			}
		}
	}

	return nil
}

func (dst *CheCluster) convertFrom_K8s(src *chev2.CheCluster) error {
	if src.Spec.Components.CheServer.Deployment != nil {
		if src.Spec.Components.CheServer.Deployment.SecurityContext != nil {
			if src.Spec.Components.CheServer.Deployment.SecurityContext.RunAsUser != nil {
				dst.Spec.K8s.SecurityContextRunAsUser = strconv.FormatInt(*src.Spec.Components.CheServer.Deployment.SecurityContext.RunAsUser, 10)
			}
			if src.Spec.Components.CheServer.Deployment.SecurityContext.FsGroup != nil {
				dst.Spec.K8s.SecurityContextFsGroup = strconv.FormatInt(*src.Spec.Components.CheServer.Deployment.SecurityContext.FsGroup, 10)
			}
		}
	}

	if !infrastructure.IsOpenShift() {
		dst.Spec.K8s.IngressDomain = src.Spec.Networking.Domain
		dst.Spec.K8s.TlsSecretName = src.Spec.Networking.TlsSecretName
		dst.Spec.K8s.IngressClass = src.Spec.Networking.Annotations["kubernetes.io/ingress.class"]
	}

	return nil
}

func (dst *CheCluster) convertFrom_Auth(src *chev2.CheCluster) error {
	dst.Spec.Auth.IdentityProviderURL = src.Spec.Networking.Auth.IdentityProviderURL
	dst.Spec.Auth.OAuthClientName = src.Spec.Networking.Auth.OAuthClientName
	dst.Spec.Auth.OAuthSecret = src.Spec.Networking.Auth.OAuthSecret
	dst.Spec.Auth.OAuthScope = src.Spec.Networking.Auth.OAuthScope
	dst.Spec.Auth.IdentityToken = src.Spec.Networking.Auth.IdentityToken

	if src.Spec.Networking.Auth.Gateway.Deployment != nil {
		for _, c := range src.Spec.Networking.Auth.Gateway.Deployment.Containers {
			switch c.Name {
			case constants.GatewayAuthenticationContainerName:
				dst.Spec.Auth.GatewayAuthenticationSidecarImage = c.Image
				dst.Spec.Auth.GatewayOAuthProxyEnv = c.Env
			case constants.GatewayAuthorizationContainerName:
				dst.Spec.Auth.GatewayAuthorizationSidecarImage = c.Image
				dst.Spec.Auth.GatewayKubeRbacProxyEnv = c.Env
			case constants.GatewayConfigSideCarContainerName:
				dst.Spec.Auth.GatewayConfigBumpEnv = c.Env
			case constants.GatewayContainerName:
				dst.Spec.Auth.GatewayEnv = c.Env
			}
		}
	}

	return nil
}

func (dst *CheCluster) convertFrom_DevWorkspace(src *chev2.CheCluster) error {
	if src.Spec.DevEnvironments.MaxNumberOfRunningWorkspacesPerUser != nil {
		dst.Spec.DevWorkspace.RunningLimit = strconv.FormatInt(*src.Spec.DevEnvironments.MaxNumberOfRunningWorkspacesPerUser, 10)
	}
	dst.Spec.DevWorkspace.SecondsOfInactivityBeforeIdling = src.Spec.DevEnvironments.SecondsOfInactivityBeforeIdling
	dst.Spec.DevWorkspace.SecondsOfRunBeforeIdling = src.Spec.DevEnvironments.SecondsOfRunBeforeIdling
	dst.Spec.DevWorkspace.Enable = true
	return nil
}

func (dst *CheCluster) convertFrom_ImagePuller(src *chev2.CheCluster) error {
	dst.Spec.ImagePuller.Enable = src.Spec.Components.ImagePuller.Enable
	dst.Spec.ImagePuller.Spec = src.Spec.Components.ImagePuller.Spec

	return nil
}

func (dst *CheCluster) convertFrom_Metrics(src *chev2.CheCluster) error {
	dst.Spec.Metrics.Enable = src.Spec.Components.Metrics.Enable

	return nil
}

func (dst *CheCluster) convertFrom_Dashboard(src *chev2.CheCluster) error {
	if src.Spec.Components.Dashboard.HeaderMessage != nil {
		dst.Spec.Dashboard.Warning = src.Spec.Components.Dashboard.HeaderMessage.Text
	}

	return nil
}

func (dst *CheCluster) convertFrom_Status(src *chev2.CheCluster) error {
	dst.Status.CheURL = src.Status.CheURL
	dst.Status.CheVersion = src.Status.CheVersion
	dst.Status.DevfileRegistryURL = src.Status.DevfileRegistryURL
	dst.Status.PluginRegistryURL = src.Status.PluginRegistryURL
	dst.Status.Message = src.Status.Message
	dst.Status.Reason = src.Status.Reason
	dst.Status.DevworkspaceStatus.GatewayPhase = GatewayPhase(src.Status.GatewayPhase)
	dst.Status.DevworkspaceStatus.GatewayHost = src.GetCheHost()
	dst.Status.DevworkspaceStatus.WorkspaceBaseDomain = src.Status.WorkspaceBaseDomain
	dst.Status.DevworkspaceStatus.Message = src.Status.Message
	dst.Status.DevworkspaceStatus.Phase = ClusterPhase(src.Status.ChePhase)
	dst.Status.DevworkspaceStatus.Reason = src.Status.Reason

	switch src.Status.ChePhase {
	case chev2.ClusterPhaseActive:
		dst.Status.CheClusterRunning = "Available"
	case chev2.ClusterPhaseInactive:
		dst.Status.CheClusterRunning = "Unavailable"
	case chev2.RollingUpdate:
		dst.Status.CheClusterRunning = "Available, Rolling Update in Progress"
	}

	if src.Spec.DevEnvironments.TrustedCerts != nil && src.Spec.DevEnvironments.TrustedCerts.GitTrustedCertsConfigMapName != "" {
		dst.Status.GitServerTLSCertificateConfigMapName = src.Spec.DevEnvironments.TrustedCerts.GitTrustedCertsConfigMapName
	}

	return nil
}

func (dst *CheCluster) convertFrom_Storage(src *chev2.CheCluster) error {
	dst.Spec.Storage.PvcStrategy = src.Spec.DevEnvironments.Storage.PvcStrategy
	if src.Spec.DevEnvironments.Storage.PerUserStrategyPvcConfig != nil {
		dst.Spec.Storage.PvcClaimSize = src.Spec.DevEnvironments.Storage.PerUserStrategyPvcConfig.ClaimSize
		dst.Spec.Storage.WorkspacePVCStorageClassName = src.Spec.DevEnvironments.Storage.PerUserStrategyPvcConfig.StorageClass
	}

	if src.Spec.DevEnvironments.Storage.PerWorkspaceStrategyPvcConfig != nil {
		dst.Spec.Storage.PerWorkspaceStrategyPvcClaimSize = src.Spec.DevEnvironments.Storage.PerWorkspaceStrategyPvcConfig.ClaimSize
		dst.Spec.Storage.PerWorkspaceStrategyPVCStorageClassName = src.Spec.DevEnvironments.Storage.PerWorkspaceStrategyPvcConfig.StorageClass
	}

	return nil
}

// Finds TrustStore ConfigMap.
func findTrustStoreConfigMap(namespace string) (string, error) {
	k8sHelper := k8shelper.New()

	_, err := k8sHelper.GetClientset().CoreV1().ConfigMaps(namespace).Get(context.TODO(), constants.DefaultCaBundleCertsCMName, metav1.GetOptions{})
	if err == nil {
		// TrustStore ConfigMap with a default name exists
		return constants.DefaultCaBundleCertsCMName, nil
	}

	return "", nil
}

func resource2String(resource *resource.Quantity) string {
	if resource == nil || resource.IsZero() {
		return ""
	}
	return resource.String()
}
