//
// 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 server

import (
	"fmt"
	"strings"

	"github.com/devfile/devworkspace-operator/pkg/infrastructure"
	util "github.com/eclipse-che/che-operator/pkg/common/utils"
	"github.com/sirupsen/logrus"

	"github.com/eclipse-che/che-operator/pkg/common/chetypes"
	"github.com/eclipse-che/che-operator/pkg/common/constants"
	"github.com/eclipse-che/che-operator/pkg/deploy"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/types"
)

const (
	userCommonPermissionsTemplateName       = "%s-cheworkspaces-clusterrole"
	userDevWorkspacePermissionsTemplateName = "%s-cheworkspaces-devworkspace-clusterrole"
	cheSASpecificPermissionsTemplateName    = "%s-cheworkspaces-namespaces-clusterrole"
)

// Create ClusterRole and ClusterRoleBinding for "che" service account.
// che-server uses "che" service account for creation RBAC for a user in his namespace.
func (s *CheServerReconciler) syncPermissions(ctx *chetypes.DeployContext) (bool, error) {
	policies := map[string][]rbacv1.PolicyRule{
		fmt.Sprintf(userCommonPermissionsTemplateName, ctx.CheCluster.Namespace):       s.getUserCommonPolicies(),
		fmt.Sprintf(cheSASpecificPermissionsTemplateName, ctx.CheCluster.Namespace):    s.getCheSASpecificPolicies(),
		fmt.Sprintf(userDevWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace): s.getUserDevWorkspacePolicies(),
	}

	for name, policy := range policies {
		if done, err := deploy.SyncClusterRoleToCluster(ctx, name, policy); !done {
			return false, err
		}

		if done, err := deploy.SyncClusterRoleBindingToCluster(ctx, name, constants.DefaultCheServiceAccountName, name); !done {
			return false, err
		}
	}

	for _, cheClusterRole := range ctx.CheCluster.Spec.Components.CheServer.ClusterRoles {
		cheClusterRole := strings.TrimSpace(cheClusterRole)
		if cheClusterRole != "" {
			if done, err := deploy.SyncClusterRoleBindingToCluster(ctx, cheClusterRole, constants.DefaultCheServiceAccountName, cheClusterRole); !done {
				return false, err
			}

			finalizer := s.getCRBFinalizerName(cheClusterRole)
			if err := deploy.AppendFinalizer(ctx, finalizer); err != nil {
				return false, err
			}
		}
	}

	// Delete abandoned CRBs
	for _, finalizer := range ctx.CheCluster.Finalizers {
		if strings.HasSuffix(finalizer, cheCRBFinalizerSuffix) {
			cheClusterRole := strings.TrimSuffix(finalizer, cheCRBFinalizerSuffix)
			if !util.Contains(ctx.CheCluster.Spec.Components.CheServer.ClusterRoles, cheClusterRole) {
				if done, err := deploy.Delete(ctx, types.NamespacedName{Name: cheClusterRole}, &rbacv1.ClusterRoleBinding{}); !done {
					return false, err
				}

				if err := deploy.DeleteFinalizer(ctx, finalizer); err != nil {
					return false, err
				}
			}
		}
	}

	return true, nil
}

func (s *CheServerReconciler) deletePermissions(ctx *chetypes.DeployContext) bool {
	names := []string{
		fmt.Sprintf(userCommonPermissionsTemplateName, ctx.CheCluster.Namespace),
		fmt.Sprintf(cheSASpecificPermissionsTemplateName, ctx.CheCluster.Namespace),
		fmt.Sprintf(userDevWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace),
	}

	done := true

	for _, name := range names {
		if _, err := deploy.Delete(ctx, types.NamespacedName{Name: name}, &rbacv1.ClusterRole{}); err != nil {
			done = false
			logrus.Errorf("Failed to delete ClusterRole '%s', cause: %v", name, err)
		}

		if _, err := deploy.Delete(ctx, types.NamespacedName{Name: name}, &rbacv1.ClusterRoleBinding{}); err != nil {
			done = false
			logrus.Errorf("Failed to delete ClusterRoleBinding '%s', cause: %v", name, err)
		}
	}

	for _, name := range ctx.CheCluster.Spec.Components.CheServer.ClusterRoles {
		name := strings.TrimSpace(name)
		if name != "" {
			if _, err := deploy.Delete(ctx, types.NamespacedName{Name: name}, &rbacv1.ClusterRoleBinding{}); err != nil {
				done = false
				logrus.Errorf("Failed to delete ClusterRoleBinding '%s', cause: %v", name, err)
			}

			// Removes any legacy CRB https://github.com/eclipse/che/issues/19506
			legacyName := ctx.CheCluster.Namespace + "-" + constants.DefaultCheServiceAccountName + "-" + name
			if _, err := deploy.Delete(ctx, types.NamespacedName{Name: legacyName}, &rbacv1.ClusterRoleBinding{}); err != nil {
				done = false
				logrus.Errorf("Failed to delete ClusterRoleBinding '%s', cause: %v", legacyName, err)
			}
		}
	}

	return done
}

func (s *CheServerReconciler) getUserDevWorkspacePolicies() []rbacv1.PolicyRule {
	k8sPolicies := []rbacv1.PolicyRule{
		{
			APIGroups: []string{"workspace.devfile.io"},
			Resources: []string{"devworkspaces", "devworkspacetemplates"},
			Verbs:     []string{"get", "create", "delete", "list", "update", "patch", "watch"},
		},
	}

	return k8sPolicies
}

func (s *CheServerReconciler) getCheSASpecificPolicies() []rbacv1.PolicyRule {
	k8sPolicies := []rbacv1.PolicyRule{
		{
			APIGroups: []string{""},
			Resources: []string{"namespaces"},
			Verbs:     []string{"get", "create", "update", "list"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"serviceaccounts"},
			Verbs:     []string{"get", "watch", "create"},
		},
		{
			APIGroups: []string{"rbac.authorization.k8s.io"},
			Resources: []string{"roles"},
			Verbs:     []string{"get", "create", "update"},
		},
		{
			APIGroups: []string{"rbac.authorization.k8s.io"},
			Resources: []string{"rolebindings"},
			Verbs:     []string{"get", "create", "update", "delete"},
		},
	}

	openshiftPolicies := []rbacv1.PolicyRule{
		{
			APIGroups: []string{"project.openshift.io"},
			Resources: []string{"projectrequests"},
			Verbs:     []string{"create", "update"},
		},
		{
			APIGroups: []string{"project.openshift.io"},
			Resources: []string{"projects"},
			Verbs:     []string{"get", "list"},
		},
		{
			APIGroups: []string{"user.openshift.io"},
			Resources: []string{"groups"},
			Verbs:     []string{"get"},
		},
		{
			APIGroups: []string{"authorization.openshift.io"},
			Resources: []string{"roles"},
			Verbs:     []string{"get", "create", "update"},
		},
		{
			APIGroups: []string{"authorization.openshift.io"},
			Resources: []string{"rolebindings"},
			Verbs:     []string{"get", "create", "update", "delete"},
		},
	}

	if infrastructure.IsOpenShift() {
		return append(k8sPolicies, openshiftPolicies...)
	}
	return k8sPolicies
}

func (s *CheServerReconciler) getUserCommonPolicies() []rbacv1.PolicyRule {
	k8sPolicies := []rbacv1.PolicyRule{
		{
			APIGroups: []string{""},
			Resources: []string{"pods/exec"},
			Verbs:     []string{"get", "create"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"pods/log"},
			Verbs:     []string{"get", "list", "watch"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"pods/portforward"},
			Verbs:     []string{"get", "list", "create"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"secrets"},
			Verbs:     []string{"get", "list", "create", "update", "patch", "delete"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"persistentvolumeclaims"},
			Verbs:     []string{"get", "list", "watch", "create", "delete", "update", "patch"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"pods"},
			Verbs:     []string{"get", "list", "watch", "create", "delete", "update", "patch"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"services"},
			Verbs:     []string{"get", "list", "create", "delete", "update", "patch"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"configmaps"},
			Verbs:     []string{"get", "list", "create", "update", "patch", "delete"},
		},
		{
			APIGroups: []string{"apps"},
			Resources: []string{"deployments"},
			Verbs:     []string{"get", "list", "watch", "create", "patch", "delete"},
		},
		{
			APIGroups: []string{"apps"},
			Resources: []string{"replicasets"},
			Verbs:     []string{"get", "list", "patch", "delete"},
		},
		{
			APIGroups: []string{"networking.k8s.io"},
			Resources: []string{"ingresses"},
			Verbs:     []string{"get", "list", "watch", "create", "delete"},
		},
		{
			APIGroups: []string{"metrics.k8s.io"},
			Resources: []string{"pods", "nodes"},
			Verbs:     []string{"get", "list", "watch"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"namespaces"},
			Verbs:     []string{"get", "list"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"events"},
			Verbs:     []string{"watch", "list"},
		},
	}
	openshiftPolicies := []rbacv1.PolicyRule{
		{
			APIGroups: []string{"route.openshift.io"},
			Resources: []string{"routes"},
			Verbs:     []string{"get", "list", "create", "delete"},
		},
		{
			APIGroups: []string{"project.openshift.io"},
			Resources: []string{"projects"},
			Verbs:     []string{"get"},
		},
	}

	if infrastructure.IsOpenShift() {
		return append(k8sPolicies, openshiftPolicies...)
	}
	return k8sPolicies
}

func (s *CheServerReconciler) getDefaultUserClusterRoles(ctx *chetypes.DeployContext) []string {
	return []string{
		fmt.Sprintf(userCommonPermissionsTemplateName, ctx.CheCluster.Namespace),
		fmt.Sprintf(userDevWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace),
	}
}
