package common

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/vim25/mo"
	vim25types "github.com/vmware/govmomi/vim25/types"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	cnsvsphere "sigs.k8s.io/vsphere-csi-driver/pkg/common/cns-lib/vsphere"
	"sigs.k8s.io/vsphere-csi-driver/pkg/csi/service/logger"
)

type AuthorizationService interface {

	GetDatastoreMapForBlockVolumes(ctx context.Context) map[string]*cnsvsphere.DatastoreInfo

	GetFsEnabledClusterToDsMap(ctx context.Context) map[string][]*cnsvsphere.DatastoreInfo

	ResetvCenterInstance(ctx context.Context, vCenter *cnsvsphere.VirtualCenter)
}

type AuthManager struct {

	datastoreMapForBlockVolumes map[string]*cnsvsphere.DatastoreInfo

	fsEnabledClusterToDsMap map[string][]*cnsvsphere.DatastoreInfo

	rwMutex sync.RWMutex

	vcenter *cnsvsphere.VirtualCenter
}

var onceForAuthorizationService sync.Once

var authManagerInstance *AuthManager

func GetAuthorizationService(ctx context.Context, vc *cnsvsphere.VirtualCenter) (AuthorizationService, error) {
	log := logger.GetLogger(ctx)
	onceForAuthorizationService.Do(func() {
		log.Info("Initializing authorization service...")

		authManagerInstance = &AuthManager{
			datastoreMapForBlockVolumes: make(map[string]*cnsvsphere.DatastoreInfo),
			fsEnabledClusterToDsMap:     make(map[string][]*cnsvsphere.DatastoreInfo),
			rwMutex:                     sync.RWMutex{},
			vcenter:                     vc,
		}
	})

	log.Info("authorization service initialized")
	return authManagerInstance, nil
}

func (authManager *AuthManager) GetDatastoreMapForBlockVolumes(ctx context.Context) map[string]*cnsvsphere.DatastoreInfo {
	datastoreMapForBlockVolumes := make(map[string]*cnsvsphere.DatastoreInfo)
	authManager.rwMutex.RLock()
	defer authManager.rwMutex.RUnlock()
	for dsURL, dsInfo := range authManager.datastoreMapForBlockVolumes {
		datastoreMapForBlockVolumes[dsURL] = dsInfo
	}
	return datastoreMapForBlockVolumes
}

func (authManager *AuthManager) GetFsEnabledClusterToDsMap(ctx context.Context) map[string][]*cnsvsphere.DatastoreInfo {
	fsEnabledClusterToDsMap := make(map[string][]*cnsvsphere.DatastoreInfo)
	authManager.rwMutex.RLock()
	defer authManager.rwMutex.RUnlock()
	for clusterID, datastores := range authManager.fsEnabledClusterToDsMap {
		fsEnabledClusterToDsMap[clusterID] = datastores
	}
	return fsEnabledClusterToDsMap
}

func (authManager *AuthManager) ResetvCenterInstance(ctx context.Context, vCenter *cnsvsphere.VirtualCenter) {
	log := logger.GetLogger(ctx)
	log.Info("Resetting vCenter Instance in the AuthManager")
	authManager.vcenter = vCenter
}

func (authManager *AuthManager) refreshDatastoreMapForBlockVolumes() {
	ctx, log := logger.GetNewContextWithLogger()
	log.Debug("auth manager: refreshDatastoreMapForBlockVolumes is triggered")
	newDatastoreMapForBlockVolumes, err := GenerateDatastoreMapForBlockVolumes(ctx, authManager.vcenter)
	if err == nil {
		authManager.rwMutex.Lock()
		defer authManager.rwMutex.Unlock()
		authManager.datastoreMapForBlockVolumes = newDatastoreMapForBlockVolumes
		log.Debugf("auth manager: datastoreMapForBlockVolumes is updated to %v", newDatastoreMapForBlockVolumes)
	} else {
		log.Warnf("auth manager: failed to get updated datastoreMapForBlockVolumes, Err: %v", err)
	}
}

func (authManager *AuthManager) refreshFSEnabledClustersToDsMap() {
	ctx, log := logger.GetNewContextWithLogger()
	log.Debug("auth manager: refreshDatastoreMapsForFileVolumes is triggered")
	newFsEnabledClusterToDsMap, err := GenerateFSEnabledClustersToDsMap(ctx, authManager.vcenter)
	if err == nil {
		authManager.rwMutex.Lock()
		defer authManager.rwMutex.Unlock()

		authManager.fsEnabledClusterToDsMap = newFsEnabledClusterToDsMap
		log.Debugf("auth manager: newFsEnabledClusterToDsMap is updated to %v", newFsEnabledClusterToDsMap)
	} else {
		log.Warnf("auth manager: failed to get updated datastoreMapForFileVolumes, Err: %v", err)
	}
}

func ComputeDatastoreMapForBlockVolumes(authManager *AuthManager, authCheckInterval int) {
	log := logger.GetLoggerWithNoContext()
	log.Info("auth manager: ComputeDatastoreMapForBlockVolumes enter")
	ticker := time.NewTicker(time.Duration(authCheckInterval) * time.Minute)
	for ; true; <-ticker.C {
		authManager.refreshDatastoreMapForBlockVolumes()
	}
}

func ComputeFSEnabledClustersToDsMap(authManager *AuthManager, authCheckInterval int) {
	log := logger.GetLoggerWithNoContext()
	log.Info("auth manager: ComputeFSEnabledClustersToDsMap enter")
	ticker := time.NewTicker(time.Duration(authCheckInterval) * time.Minute)
	for ; true; <-ticker.C {
		authManager.refreshFSEnabledClustersToDsMap()
	}
}

func GenerateDatastoreMapForBlockVolumes(ctx context.Context, vc *cnsvsphere.VirtualCenter) (map[string]*cnsvsphere.DatastoreInfo, error) {
	log := logger.GetLogger(ctx)

	dcList, err := vc.GetDatacenters(ctx)
	if err != nil {
		msg := fmt.Sprintf("failed to get datacenter list. err: %+v", err)
		log.Error(msg)
		return nil, status.Errorf(codes.Internal, msg)
	}
	var dsURLTodsInfoMap map[string]*cnsvsphere.DatastoreInfo
	var dsURLs []string
	var dsInfos []*cnsvsphere.DatastoreInfo
	var entities []vim25types.ManagedObjectReference
	for _, dc := range dcList {
		dsURLTodsInfoMap, err = dc.GetAllDatastores(ctx)
		if err != nil {
			msg := fmt.Sprintf("failed to get dsURLTodsInfoMap. err: %+v", err)
			log.Error(msg)
		}
		for dsURL, dsInfo := range dsURLTodsInfoMap {
			dsURLs = append(dsURLs, dsURL)
			dsInfos = append(dsInfos, dsInfo)
			dsMoRef := dsInfo.Reference()
			entities = append(entities, dsMoRef)
		}
	}

	dsURLToInfoMap, err := getDatastoresWithBlockVolumePrivs(ctx, vc, dsURLs, dsInfos, entities)
	if err != nil {
		log.Errorf("failed to get datastores with required priv. Error: %+v", err)
		return nil, err
	}
	return dsURLToInfoMap, nil
}

func GenerateFSEnabledClustersToDsMap(ctx context.Context, vc *cnsvsphere.VirtualCenter) (map[string][]*cnsvsphere.DatastoreInfo, error) {
	log := logger.GetLogger(ctx)
	clusterToDsInfoListMap := make(map[string][]*cnsvsphere.DatastoreInfo)

	datacenters, err := vc.ListDatacenters(ctx)
	if err != nil {
		log.Errorf("failed to find datacenters from VC: %q, Error: %+v", vc.Config.Host, err)
		return nil, err
	}

	vsanDsURLToInfoMap, err := vc.GetVsanDatastores(ctx, datacenters)
	if err != nil {
		log.Errorf("failed to get vSAN datastores with error %+v", err)
		return nil, err
	}

	if len(vsanDsURLToInfoMap) == 0 {
		log.Debug("No vSAN datastores found")
		return clusterToDsInfoListMap, nil
	}

	err = vc.ConnectVsan(ctx)
	if err != nil {
		log.Errorf("error occurred while connecting to VSAN, err: %+v", err)
		return nil, err
	}

	fsEnabledClusterToDsURLsMap, err := getFSEnabledClusterToDsURLsMap(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to get file service enabled clusters map with error %+v", err)
		return nil, err
	}

	for clusterMoID, dsURLs := range fsEnabledClusterToDsURLsMap {
		for _, dsURL := range dsURLs {
			if dsInfo, ok := vsanDsURLToInfoMap[dsURL]; ok {
				log.Debugf("Adding vSAN datastore %q to the list for FS enabled cluster %q", dsURL, clusterMoID)
				clusterToDsInfoListMap[clusterMoID] = append(clusterToDsInfoListMap[clusterMoID], dsInfo)
			}
		}
	}

	log.Debugf("clusterToDsInfoListMap is %+v", clusterToDsInfoListMap)
	return clusterToDsInfoListMap, nil
}

func IsFileServiceEnabled(ctx context.Context, datastoreUrls []string, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) (map[string]bool, error) {

	log := logger.GetLogger(ctx)
	err := vc.Connect(ctx)
	if err != nil {
		log.Errorf("failed to connect to VirtualCenter. err=%v", err)
		return nil, err
	}
	err = vc.ConnectVsan(ctx)
	if err != nil {
		log.Errorf("error occurred while connecting to VSAN, err: %+v", err)
		return nil, err
	}

	dsToFileServiceEnabledMap, err := getDsToFileServiceEnabledMap(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to query if file service is enabled on vsan datastores or not. error: %+v", err)
		return nil, err
	}
	log.Debugf("dsToFileServiceEnabledMap is %+v", dsToFileServiceEnabledMap)

	dsToFSEnabledMapToReturn := make(map[string]bool)
	for _, datastoreURL := range datastoreUrls {
		if val, ok := dsToFileServiceEnabledMap[datastoreURL]; ok {
			if !val {
				msg := fmt.Sprintf("File service is not enabled on the datastore: %s", datastoreURL)
				log.Debugf(msg)
				dsToFSEnabledMapToReturn[datastoreURL] = false
			} else {
				msg := fmt.Sprintf("File service is enabled on the datastore: %s", datastoreURL)
				log.Debugf(msg)
				dsToFSEnabledMapToReturn[datastoreURL] = true
			}
		} else {
			msg := fmt.Sprintf("File service is not enabled on the datastore: %s", datastoreURL)
			log.Debugf(msg)
			dsToFSEnabledMapToReturn[datastoreURL] = false
		}
	}
	return dsToFSEnabledMapToReturn, nil
}

func getDatastoresWithBlockVolumePrivs(ctx context.Context, vc *cnsvsphere.VirtualCenter,
	dsURLs []string, dsInfos []*cnsvsphere.DatastoreInfo, entities []vim25types.ManagedObjectReference) (map[string]*cnsvsphere.DatastoreInfo, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("auth manager: file - dsURLs %v dsInfos %v", dsURLs, dsInfos)

	dsURLToInfoMap := make(map[string]*cnsvsphere.DatastoreInfo)

	authMgr := object.NewAuthorizationManager(vc.Client.Client)
	privIds := []string{DsPriv, SysReadPriv}

	userName := vc.Config.Username

	result, err := authMgr.HasUserPrivilegeOnEntities(ctx, entities, userName, privIds)
	if err != nil {
		log.Errorf("auth manager: failed to check privilege %v on entities %v for user %s", privIds, entities, userName)
		return nil, err
	}
	log.Debugf("auth manager: HasUserPrivilegeOnEntities returns %v when checking privileges %v on entities %v for user %s", result, privIds, entities, userName)
	for index, entityPriv := range result {
		hasPriv := true
		privAvails := entityPriv.PrivAvailability
		for _, privAvail := range privAvails {

			if !privAvail.IsGranted {
				hasPriv = false
				break
			}
		}
		if hasPriv {
			dsURLToInfoMap[dsURLs[index]] = dsInfos[index]
			log.Debugf("auth manager: datastore with URL %s and name %s has privileges and is added to dsURLToInfoMap", dsInfos[index].Info.Name, dsURLs[index])
		}
	}
	return dsURLToInfoMap, nil
}

func getDsToFileServiceEnabledMap(ctx context.Context, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) (map[string]bool, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("Computing the cluster to file service status (enabled/disabled) map.")

	vSANFSClustersWithPriv, err := getFSEnabledClustersWithPriv(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to get the file service enabled clusters with privileges. error: %+v", err)
		return nil, err
	}

	dsToFileServiceEnabledMap := make(map[string]bool)
	for _, cluster := range vSANFSClustersWithPriv {
		dsMoList, err := getDatastoreMOsFromCluster(ctx, vc, cluster)
		if err != nil {
			log.Errorf("failed to get datastores for cluster %q. error: %+v", cluster.Reference().Value, err)
			return nil, err
		}

		for _, dsMo := range dsMoList {
			if dsMo.Summary.Type == VsanDatastoreType {
				dsToFileServiceEnabledMap[dsMo.Info.GetDatastoreInfo().Url] = true
			}
		}
	}
	return dsToFileServiceEnabledMap, nil
}

func getFSEnabledClusterToDsURLsMap(ctx context.Context, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) (map[string][]string, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("Computing the map for vSAN FS enabled clusters to datastore URLS.")

	vSANFSClustersWithPriv, err := getFSEnabledClustersWithPriv(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to get the file service enabled clusters with privileges. error: %+v", err)
		return nil, err
	}

	fsEnabledClusterToDsMap := make(map[string][]string)
	for _, cluster := range vSANFSClustersWithPriv {
		clusterMoID := cluster.Reference().Value
		dsMoList, err := getDatastoreMOsFromCluster(ctx, vc, cluster)
		if err != nil {
			log.Errorf("failed to get datastores for cluster %q. error: %+v", clusterMoID, err)
			return nil, err
		}
		for _, dsMo := range dsMoList {
			if dsMo.Summary.Type == VsanDatastoreType {
				fsEnabledClusterToDsMap[clusterMoID] =
					append(fsEnabledClusterToDsMap[clusterMoID], dsMo.Info.GetDatastoreInfo().Url)
			}
		}
	}

	return fsEnabledClusterToDsMap, nil
}

func getFSEnabledClustersWithPriv(ctx context.Context, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) ([]*object.ClusterComputeResource, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("Computing the clusters with vSAN file services enabled and Host.Config.Storage privileges")

	clusterComputeResources := []*object.ClusterComputeResource{}
	for _, datacenter := range datacenters {
		finder := find.NewFinder(datacenter.Datacenter.Client(), false)
		finder.SetDatacenter(datacenter.Datacenter)
		clusterComputeResource, err := finder.ClusterComputeResourceList(ctx, "*")
		if err != nil {
			if _, ok := err.(*find.NotFoundError); ok {
				log.Debugf("No clusterComputeResource found in dc: %+v. error: %+v", datacenter, err)
				continue
			}
			log.Errorf("Error occurred while getting clusterComputeResource. error: %+v", err)
			return nil, err
		}
		clusterComputeResources = append(clusterComputeResources, clusterComputeResource...)
	}

	authMgr := object.NewAuthorizationManager(vc.Client.Client)
	privIds := []string{HostConfigStoragePriv}
	userName := vc.Config.Username
	var entities []vim25types.ManagedObjectReference
	clusterComputeResourcesMap := make(map[string]*object.ClusterComputeResource)
	for _, cluster := range clusterComputeResources {
		entities = append(entities, cluster.Reference())
		clusterComputeResourcesMap[cluster.Reference().Value] = cluster
	}

	result, err := authMgr.HasUserPrivilegeOnEntities(ctx, entities, userName, privIds)
	if err != nil {
		log.Errorf("auth manager: failed to check privilege %v on entities %v for user %s", privIds, entities, userName)
		return nil, err
	}
	log.Debugf("auth manager: HasUserPrivilegeOnEntities returns %v when checking privileges %v on entities %v for user %s", result, privIds, entities, userName)
	clusterComputeResourceWithPriv := []*object.ClusterComputeResource{}
	for _, entityPriv := range result {
		hasPriv := true
		privAvails := entityPriv.PrivAvailability
		for _, privAvail := range privAvails {

			if !privAvail.IsGranted {
				hasPriv = false
				break
			}
		}
		if hasPriv {
			clusterComputeResourceWithPriv = append(clusterComputeResourceWithPriv, clusterComputeResourcesMap[entityPriv.Entity.Value])
		}
	}
	log.Debugf("Clusters with priv: %s are : %+v", HostConfigStoragePriv, clusterComputeResourceWithPriv)

	clusterComputeResourceWithPrivAndFS := []*object.ClusterComputeResource{}

	for _, cluster := range clusterComputeResourceWithPriv {

		config, err := vc.VsanClient.VsanClusterGetConfig(ctx, cluster.Reference())
		if err != nil {
			log.Errorf("failed to get the vsan cluster config. error: %+v", err)
			return nil, err
		}
		if !(*config.Enabled) {
			log.Debugf("cluster: %+v is a non-vSAN cluster. Skipping this cluster", cluster)
			continue
		} else if config.FileServiceConfig == nil {
			log.Debugf("VsanClusterGetConfig.FileServiceConfig is empty. Skipping this cluster: %+v with config: %+v",
				cluster, config)
			continue
		}

		log.Debugf("cluster: %+v has vSAN file services enabled: %t", cluster, config.FileServiceConfig.Enabled)
		if config.FileServiceConfig.Enabled {
			clusterComputeResourceWithPrivAndFS = append(clusterComputeResourceWithPrivAndFS, cluster)
		}
	}

	return clusterComputeResourceWithPrivAndFS, nil
}

func getDatastoreMOsFromCluster(ctx context.Context, vc *cnsvsphere.VirtualCenter,
	cluster *object.ClusterComputeResource) ([]mo.Datastore, error) {
	log := logger.GetLogger(ctx)
	datastores, err := cluster.Datastores(ctx)
	if err != nil {
		log.Errorf("Error occurred while getting datastores from cluster %q. error: %+v", cluster.Reference().Value, err)
		return nil, err
	}

	pc := property.DefaultCollector(vc.Client.Client)
	properties := []string{"info", "summary"}
	var dsList []vim25types.ManagedObjectReference
	var dsMoList []mo.Datastore
	for _, datastore := range datastores {
		dsList = append(dsList, datastore.Reference())
	}
	err = pc.Retrieve(ctx, dsList, properties, &dsMoList)
	if err != nil {
		log.Errorf("failed to retrieve datastores. dsObjList: %+v, properties: %+v, err: %v", dsList, properties, err)
		return nil, err
	}
	return dsMoList, nil
}
/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package common

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/vim25/mo"
	vim25types "github.com/vmware/govmomi/vim25/types"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	cnsvsphere "sigs.k8s.io/vsphere-csi-driver/pkg/common/cns-lib/vsphere"
	"sigs.k8s.io/vsphere-csi-driver/pkg/csi/service/logger"
)

// AuthorizationService exposes interfaces to support authorization check on datastores and get datastores
// which will be used by create volume
type AuthorizationService interface {
	// GetDatastoreMapForBlockVolumes returns a map of datastore URL to datastore info for only those
	// datastores the CSI VC user has Datastore.FileManagement privilege for.
	GetDatastoreMapForBlockVolumes(ctx context.Context) map[string]*cnsvsphere.DatastoreInfo

	// GetFsEnabledClusterToDsMap returns a map of cluster ID to datastores info objects for vSAN clusters with
	// file services enabled. The datastores are those on which the CSI VC user has Host.Config.Storage privilege.
	GetFsEnabledClusterToDsMap(ctx context.Context) map[string][]*cnsvsphere.DatastoreInfo

	// ResetvCenterInstance sets new vCenter instance for AuthorizationService
	ResetvCenterInstance(ctx context.Context, vCenter *cnsvsphere.VirtualCenter)
}

// AuthManager maintains an internal map to track the datastores that need to be used by create volume
type AuthManager struct {
	// map the datastore url to datastore info which need to be used when invoking CNS CreateVolume API
	datastoreMapForBlockVolumes map[string]*cnsvsphere.DatastoreInfo
	// map the vSAN file services enabled cluster ID to list of datastore info for that cluster.
	// The datastore info objects are to be used when invoking CNS CreateVolume API
	fsEnabledClusterToDsMap map[string][]*cnsvsphere.DatastoreInfo
	// this mutex is to make sure the update for datastoreMap is mutually exclusive
	rwMutex sync.RWMutex
	// vCenter Instance
	vcenter *cnsvsphere.VirtualCenter
}

// onceForAuthorizationService is used for initializing the AuthorizationService singleton.
var onceForAuthorizationService sync.Once

// authManagerIntance is instance of authManager and implements interface for AuthorizationService
var authManagerInstance *AuthManager

// GetAuthorizationService returns the singleton AuthorizationService
func GetAuthorizationService(ctx context.Context, vc *cnsvsphere.VirtualCenter) (AuthorizationService, error) {
	log := logger.GetLogger(ctx)
	onceForAuthorizationService.Do(func() {
		log.Info("Initializing authorization service...")

		authManagerInstance = &AuthManager{
			datastoreMapForBlockVolumes: make(map[string]*cnsvsphere.DatastoreInfo),
			fsEnabledClusterToDsMap:     make(map[string][]*cnsvsphere.DatastoreInfo),
			rwMutex:                     sync.RWMutex{},
			vcenter:                     vc,
		}
	})

	log.Info("authorization service initialized")
	return authManagerInstance, nil
}

// GetDatastoreMapForBlockVolumes returns a DatastoreMapForBlockVolumes. This map maps datastore url to datastore info
// which need to be used when creating block volume.
func (authManager *AuthManager) GetDatastoreMapForBlockVolumes(ctx context.Context) map[string]*cnsvsphere.DatastoreInfo {
	datastoreMapForBlockVolumes := make(map[string]*cnsvsphere.DatastoreInfo)
	authManager.rwMutex.RLock()
	defer authManager.rwMutex.RUnlock()
	for dsURL, dsInfo := range authManager.datastoreMapForBlockVolumes {
		datastoreMapForBlockVolumes[dsURL] = dsInfo
	}
	return datastoreMapForBlockVolumes
}

// GetFsEnabledClusterToDsMap returns a map of cluster ID with vSAN file services enabled to its datastore info objects.
func (authManager *AuthManager) GetFsEnabledClusterToDsMap(ctx context.Context) map[string][]*cnsvsphere.DatastoreInfo {
	fsEnabledClusterToDsMap := make(map[string][]*cnsvsphere.DatastoreInfo)
	authManager.rwMutex.RLock()
	defer authManager.rwMutex.RUnlock()
	for clusterID, datastores := range authManager.fsEnabledClusterToDsMap {
		fsEnabledClusterToDsMap[clusterID] = datastores
	}
	return fsEnabledClusterToDsMap
}

// ResetvCenterInstance sets new vCenter instance for AuthorizationService
func (authManager *AuthManager) ResetvCenterInstance(ctx context.Context, vCenter *cnsvsphere.VirtualCenter) {
	log := logger.GetLogger(ctx)
	log.Info("Resetting vCenter Instance in the AuthManager")
	authManager.vcenter = vCenter
}

// refreshDatastoreMapForBlockVolumes scans all datastores in vCenter to check privileges, and compute the
// datastoreMapForBlockVolumes
func (authManager *AuthManager) refreshDatastoreMapForBlockVolumes() {
	ctx, log := logger.GetNewContextWithLogger()
	log.Debug("auth manager: refreshDatastoreMapForBlockVolumes is triggered")
	newDatastoreMapForBlockVolumes, err := GenerateDatastoreMapForBlockVolumes(ctx, authManager.vcenter)
	if err == nil {
		authManager.rwMutex.Lock()
		defer authManager.rwMutex.Unlock()
		authManager.datastoreMapForBlockVolumes = newDatastoreMapForBlockVolumes
		log.Debugf("auth manager: datastoreMapForBlockVolumes is updated to %v", newDatastoreMapForBlockVolumes)
	} else {
		log.Warnf("auth manager: failed to get updated datastoreMapForBlockVolumes, Err: %v", err)
	}
}

// refreshFSEnabledClustersToDsMap scans all clusters with vSAN FS enabled in vCenter to
// check privileges, and compute the fsEnabledClusterToDsMap
func (authManager *AuthManager) refreshFSEnabledClustersToDsMap() {
	ctx, log := logger.GetNewContextWithLogger()
	log.Debug("auth manager: refreshDatastoreMapsForFileVolumes is triggered")
	newFsEnabledClusterToDsMap, err := GenerateFSEnabledClustersToDsMap(ctx, authManager.vcenter)
	if err == nil {
		authManager.rwMutex.Lock()
		defer authManager.rwMutex.Unlock()

		authManager.fsEnabledClusterToDsMap = newFsEnabledClusterToDsMap
		log.Debugf("auth manager: newFsEnabledClusterToDsMap is updated to %v", newFsEnabledClusterToDsMap)
	} else {
		log.Warnf("auth manager: failed to get updated datastoreMapForFileVolumes, Err: %v", err)
	}
}

// ComputeDatastoreMapForBlockVolumes refreshes DatastoreMapForBlockVolumes periodically
func ComputeDatastoreMapForBlockVolumes(authManager *AuthManager, authCheckInterval int) {
	log := logger.GetLoggerWithNoContext()
	log.Info("auth manager: ComputeDatastoreMapForBlockVolumes enter")
	ticker := time.NewTicker(time.Duration(authCheckInterval) * time.Minute)
	for ; true; <-ticker.C {
		authManager.refreshDatastoreMapForBlockVolumes()
	}
}

// ComputeFSEnabledClustersToDsMap refreshes fsEnabledClusterToDsMap periodically
func ComputeFSEnabledClustersToDsMap(authManager *AuthManager, authCheckInterval int) {
	log := logger.GetLoggerWithNoContext()
	log.Info("auth manager: ComputeFSEnabledClustersToDsMap enter")
	ticker := time.NewTicker(time.Duration(authCheckInterval) * time.Minute)
	for ; true; <-ticker.C {
		authManager.refreshFSEnabledClustersToDsMap()
	}
}

// GenerateDatastoreMapForBlockVolumes scans all datastores in Vcenter and do privilege check on those datastoes
// It will return datastores which has the privileges for creating block volume
func GenerateDatastoreMapForBlockVolumes(ctx context.Context, vc *cnsvsphere.VirtualCenter) (map[string]*cnsvsphere.DatastoreInfo, error) {
	log := logger.GetLogger(ctx)
	// get all datastores from VC
	dcList, err := vc.GetDatacenters(ctx)
	if err != nil {
		msg := fmt.Sprintf("failed to get datacenter list. err: %+v", err)
		log.Error(msg)
		return nil, status.Errorf(codes.Internal, msg)
	}
	var dsURLTodsInfoMap map[string]*cnsvsphere.DatastoreInfo
	var dsURLs []string
	var dsInfos []*cnsvsphere.DatastoreInfo
	var entities []vim25types.ManagedObjectReference
	for _, dc := range dcList {
		dsURLTodsInfoMap, err = dc.GetAllDatastores(ctx)
		if err != nil {
			msg := fmt.Sprintf("failed to get dsURLTodsInfoMap. err: %+v", err)
			log.Error(msg)
		}
		for dsURL, dsInfo := range dsURLTodsInfoMap {
			dsURLs = append(dsURLs, dsURL)
			dsInfos = append(dsInfos, dsInfo)
			dsMoRef := dsInfo.Reference()
			entities = append(entities, dsMoRef)
		}
	}

	dsURLToInfoMap, err := getDatastoresWithBlockVolumePrivs(ctx, vc, dsURLs, dsInfos, entities)
	if err != nil {
		log.Errorf("failed to get datastores with required priv. Error: %+v", err)
		return nil, err
	}
	return dsURLToInfoMap, nil
}

// GenerateFSEnabledClustersToDsMap scans all clusters in VC and do privilege check on them.
// It will return a map of cluster id to the list of datastore objects.
// The key is cluster moid with vSAN FS enabled and Host.Config.Storage privilege.
// The value is a list of vSAN datastoreInfo objects for each cluster.
func GenerateFSEnabledClustersToDsMap(ctx context.Context, vc *cnsvsphere.VirtualCenter) (map[string][]*cnsvsphere.DatastoreInfo, error) {
	log := logger.GetLogger(ctx)
	clusterToDsInfoListMap := make(map[string][]*cnsvsphere.DatastoreInfo)

	datacenters, err := vc.ListDatacenters(ctx)
	if err != nil {
		log.Errorf("failed to find datacenters from VC: %q, Error: %+v", vc.Config.Host, err)
		return nil, err
	}
	// get all vSAN datastores from VC
	vsanDsURLToInfoMap, err := vc.GetVsanDatastores(ctx, datacenters)
	if err != nil {
		log.Errorf("failed to get vSAN datastores with error %+v", err)
		return nil, err
	}
	// Return empty map if no vSAN datastores are found.
	if len(vsanDsURLToInfoMap) == 0 {
		log.Debug("No vSAN datastores found")
		return clusterToDsInfoListMap, nil
	}

	// Initialize vsan client
	err = vc.ConnectVsan(ctx)
	if err != nil {
		log.Errorf("error occurred while connecting to VSAN, err: %+v", err)
		return nil, err
	}

	fsEnabledClusterToDsURLsMap, err := getFSEnabledClusterToDsURLsMap(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to get file service enabled clusters map with error %+v", err)
		return nil, err
	}

	// Create a map of cluster to dsInfo objects. These objects are used while calling CNS
	// API to create file volumes
	for clusterMoID, dsURLs := range fsEnabledClusterToDsURLsMap {
		for _, dsURL := range dsURLs {
			if dsInfo, ok := vsanDsURLToInfoMap[dsURL]; ok {
				log.Debugf("Adding vSAN datastore %q to the list for FS enabled cluster %q", dsURL, clusterMoID)
				clusterToDsInfoListMap[clusterMoID] = append(clusterToDsInfoListMap[clusterMoID], dsInfo)
			}
		}
	}

	log.Debugf("clusterToDsInfoListMap is %+v", clusterToDsInfoListMap)
	return clusterToDsInfoListMap, nil
}

// IsFileServiceEnabled checks if file service is enabled on the specified datastoreUrls.
func IsFileServiceEnabled(ctx context.Context, datastoreUrls []string, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) (map[string]bool, error) {
	// Compute this map during controller init. Re use the map every other time.
	log := logger.GetLogger(ctx)
	err := vc.Connect(ctx)
	if err != nil {
		log.Errorf("failed to connect to VirtualCenter. err=%v", err)
		return nil, err
	}
	err = vc.ConnectVsan(ctx)
	if err != nil {
		log.Errorf("error occurred while connecting to VSAN, err: %+v", err)
		return nil, err
	}
	// This gets the datastore to file service enabled map for all vsan datastores
	// belonging to clusters with vSAN FS enabled and Host.Config.Storage privileges
	dsToFileServiceEnabledMap, err := getDsToFileServiceEnabledMap(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to query if file service is enabled on vsan datastores or not. error: %+v", err)
		return nil, err
	}
	log.Debugf("dsToFileServiceEnabledMap is %+v", dsToFileServiceEnabledMap)
	// Now create a map of datastores which are queried in the method.
	dsToFSEnabledMapToReturn := make(map[string]bool)
	for _, datastoreURL := range datastoreUrls {
		if val, ok := dsToFileServiceEnabledMap[datastoreURL]; ok {
			if !val {
				msg := fmt.Sprintf("File service is not enabled on the datastore: %s", datastoreURL)
				log.Debugf(msg)
				dsToFSEnabledMapToReturn[datastoreURL] = false
			} else {
				msg := fmt.Sprintf("File service is enabled on the datastore: %s", datastoreURL)
				log.Debugf(msg)
				dsToFSEnabledMapToReturn[datastoreURL] = true
			}
		} else {
			msg := fmt.Sprintf("File service is not enabled on the datastore: %s", datastoreURL)
			log.Debugf(msg)
			dsToFSEnabledMapToReturn[datastoreURL] = false
		}
	}
	return dsToFSEnabledMapToReturn, nil
}

// getDatastoresWithBlockVolumePrivs gets datastores with required priv for CSI user
func getDatastoresWithBlockVolumePrivs(ctx context.Context, vc *cnsvsphere.VirtualCenter,
	dsURLs []string, dsInfos []*cnsvsphere.DatastoreInfo, entities []vim25types.ManagedObjectReference) (map[string]*cnsvsphere.DatastoreInfo, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("auth manager: file - dsURLs %v dsInfos %v", dsURLs, dsInfos)
	// dsURLToInfoMap will store a list of vSAN datastores with vSAN FS enabled for which
	// CSI user has privilege to
	dsURLToInfoMap := make(map[string]*cnsvsphere.DatastoreInfo)
	// get authMgr
	authMgr := object.NewAuthorizationManager(vc.Client.Client)
	privIds := []string{DsPriv, SysReadPriv}

	userName := vc.Config.Username
	// invoke authMgr function HasUserPrivilegeOnEntities
	result, err := authMgr.HasUserPrivilegeOnEntities(ctx, entities, userName, privIds)
	if err != nil {
		log.Errorf("auth manager: failed to check privilege %v on entities %v for user %s", privIds, entities, userName)
		return nil, err
	}
	log.Debugf("auth manager: HasUserPrivilegeOnEntities returns %v when checking privileges %v on entities %v for user %s", result, privIds, entities, userName)
	for index, entityPriv := range result {
		hasPriv := true
		privAvails := entityPriv.PrivAvailability
		for _, privAvail := range privAvails {
			// required privilege is not grant for this entity
			if !privAvail.IsGranted {
				hasPriv = false
				break
			}
		}
		if hasPriv {
			dsURLToInfoMap[dsURLs[index]] = dsInfos[index]
			log.Debugf("auth manager: datastore with URL %s and name %s has privileges and is added to dsURLToInfoMap", dsInfos[index].Info.Name, dsURLs[index])
		}
	}
	return dsURLToInfoMap, nil
}

// Creates a map of vsan datastores to file service enabled status.
// Since only datastores belonging to clusters with vSAN FS enabled and Host.Config.Storage privileges are returned,
// file service enabled status will be true for them.
func getDsToFileServiceEnabledMap(ctx context.Context, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) (map[string]bool, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("Computing the cluster to file service status (enabled/disabled) map.")

	//Get clusters with vSAN FS enabled and privileges
	vSANFSClustersWithPriv, err := getFSEnabledClustersWithPriv(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to get the file service enabled clusters with privileges. error: %+v", err)
		return nil, err
	}

	dsToFileServiceEnabledMap := make(map[string]bool)
	for _, cluster := range vSANFSClustersWithPriv {
		dsMoList, err := getDatastoreMOsFromCluster(ctx, vc, cluster)
		if err != nil {
			log.Errorf("failed to get datastores for cluster %q. error: %+v", cluster.Reference().Value, err)
			return nil, err
		}
		// TODO: Also identify which vSAN datastore is management
		// and which one is a workload datastore to support file volumes on VMC
		for _, dsMo := range dsMoList {
			if dsMo.Summary.Type == VsanDatastoreType {
				dsToFileServiceEnabledMap[dsMo.Info.GetDatastoreInfo().Url] = true
			}
		}
	}
	return dsToFileServiceEnabledMap, nil
}

// Creates a map of cluster id to datastore urls.
// The key is cluster moid with vSAN FS enabled and Host.Config.Storage privilege.
// The value is a list of vSAN datastore URLs for each cluster.
func getFSEnabledClusterToDsURLsMap(ctx context.Context, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) (map[string][]string, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("Computing the map for vSAN FS enabled clusters to datastore URLS.")

	//Get clusters with vSAN FS enabled and privileges
	vSANFSClustersWithPriv, err := getFSEnabledClustersWithPriv(ctx, vc, datacenters)
	if err != nil {
		log.Errorf("failed to get the file service enabled clusters with privileges. error: %+v", err)
		return nil, err
	}

	fsEnabledClusterToDsMap := make(map[string][]string)
	for _, cluster := range vSANFSClustersWithPriv {
		clusterMoID := cluster.Reference().Value
		dsMoList, err := getDatastoreMOsFromCluster(ctx, vc, cluster)
		if err != nil {
			log.Errorf("failed to get datastores for cluster %q. error: %+v", clusterMoID, err)
			return nil, err
		}
		for _, dsMo := range dsMoList {
			if dsMo.Summary.Type == VsanDatastoreType {
				fsEnabledClusterToDsMap[clusterMoID] =
					append(fsEnabledClusterToDsMap[clusterMoID], dsMo.Info.GetDatastoreInfo().Url)
			}
		}
	}

	return fsEnabledClusterToDsMap, nil
}

// Returns a list of clusters with Host.Config.Storage privilege and vSAN file services enabled.
func getFSEnabledClustersWithPriv(ctx context.Context, vc *cnsvsphere.VirtualCenter, datacenters []*cnsvsphere.Datacenter) ([]*object.ClusterComputeResource, error) {
	log := logger.GetLogger(ctx)
	log.Debugf("Computing the clusters with vSAN file services enabled and Host.Config.Storage privileges")
	// Get clusters from datacenters
	clusterComputeResources := []*object.ClusterComputeResource{}
	for _, datacenter := range datacenters {
		finder := find.NewFinder(datacenter.Datacenter.Client(), false)
		finder.SetDatacenter(datacenter.Datacenter)
		clusterComputeResource, err := finder.ClusterComputeResourceList(ctx, "*")
		if err != nil {
			if _, ok := err.(*find.NotFoundError); ok {
				log.Debugf("No clusterComputeResource found in dc: %+v. error: %+v", datacenter, err)
				continue
			}
			log.Errorf("Error occurred while getting clusterComputeResource. error: %+v", err)
			return nil, err
		}
		clusterComputeResources = append(clusterComputeResources, clusterComputeResource...)
	}

	// Get Clusters with HostConfigStoragePriv
	authMgr := object.NewAuthorizationManager(vc.Client.Client)
	privIds := []string{HostConfigStoragePriv}
	userName := vc.Config.Username
	var entities []vim25types.ManagedObjectReference
	clusterComputeResourcesMap := make(map[string]*object.ClusterComputeResource)
	for _, cluster := range clusterComputeResources {
		entities = append(entities, cluster.Reference())
		clusterComputeResourcesMap[cluster.Reference().Value] = cluster
	}

	// invoke authMgr function HasUserPrivilegeOnEntities
	result, err := authMgr.HasUserPrivilegeOnEntities(ctx, entities, userName, privIds)
	if err != nil {
		log.Errorf("auth manager: failed to check privilege %v on entities %v for user %s", privIds, entities, userName)
		return nil, err
	}
	log.Debugf("auth manager: HasUserPrivilegeOnEntities returns %v when checking privileges %v on entities %v for user %s", result, privIds, entities, userName)
	clusterComputeResourceWithPriv := []*object.ClusterComputeResource{}
	for _, entityPriv := range result {
		hasPriv := true
		privAvails := entityPriv.PrivAvailability
		for _, privAvail := range privAvails {
			// required privilege is not grant for this entity
			if !privAvail.IsGranted {
				hasPriv = false
				break
			}
		}
		if hasPriv {
			clusterComputeResourceWithPriv = append(clusterComputeResourceWithPriv, clusterComputeResourcesMap[entityPriv.Entity.Value])
		}
	}
	log.Debugf("Clusters with priv: %s are : %+v", HostConfigStoragePriv, clusterComputeResourceWithPriv)

	// Get clusters which are vSAN and have vSAN FS enabled
	clusterComputeResourceWithPrivAndFS := []*object.ClusterComputeResource{}
	// Get all the vsan datastores with vsan FS from these clusters and add it to map.
	for _, cluster := range clusterComputeResourceWithPriv {
		// Get the cluster config to know if file service is enabled on it or not.
		config, err := vc.VsanClient.VsanClusterGetConfig(ctx, cluster.Reference())
		if err != nil {
			log.Errorf("failed to get the vsan cluster config. error: %+v", err)
			return nil, err
		}
		if !(*config.Enabled) {
			log.Debugf("cluster: %+v is a non-vSAN cluster. Skipping this cluster", cluster)
			continue
		} else if config.FileServiceConfig == nil {
			log.Debugf("VsanClusterGetConfig.FileServiceConfig is empty. Skipping this cluster: %+v with config: %+v",
				cluster, config)
			continue
		}

		log.Debugf("cluster: %+v has vSAN file services enabled: %t", cluster, config.FileServiceConfig.Enabled)
		if config.FileServiceConfig.Enabled {
			clusterComputeResourceWithPrivAndFS = append(clusterComputeResourceWithPrivAndFS, cluster)
		}
	}

	return clusterComputeResourceWithPrivAndFS, nil
}

// Returns datastore managed objects with info & summary properties for a given cluster
func getDatastoreMOsFromCluster(ctx context.Context, vc *cnsvsphere.VirtualCenter,
	cluster *object.ClusterComputeResource) ([]mo.Datastore, error) {
	log := logger.GetLogger(ctx)
	datastores, err := cluster.Datastores(ctx)
	if err != nil {
		log.Errorf("Error occurred while getting datastores from cluster %q. error: %+v", cluster.Reference().Value, err)
		return nil, err
	}

	// Get datastore properties
	pc := property.DefaultCollector(vc.Client.Client)
	properties := []string{"info", "summary"}
	var dsList []vim25types.ManagedObjectReference
	var dsMoList []mo.Datastore
	for _, datastore := range datastores {
		dsList = append(dsList, datastore.Reference())
	}
	err = pc.Retrieve(ctx, dsList, properties, &dsMoList)
	if err != nil {
		log.Errorf("failed to retrieve datastores. dsObjList: %+v, properties: %+v, err: %v", dsList, properties, err)
		return nil, err
	}
	return dsMoList, nil
}
