package main

import (
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/labstack/echo/v4"
	log "github.com/sirupsen/logrus"

	"crypto/sha1"
	"encoding/base64"

	"github.com/cloudfoundry/stratos/src/jetstream/api"
	"github.com/cloudfoundry/stratos/src/jetstream/api/config"
	"github.com/cloudfoundry/stratos/src/jetstream/plugins/userfavorites/userfavoritesendpoints"
)

const dbReferenceError = "unable to establish a database reference: '%v'"

func isSSLRelatedError(err error) (bool, string) {
	var urlError *url.Error
	if errors.As(err, &urlError) {
		var (
			certInvalidError      *x509.CertificateInvalidError
			unknownAuthorityError *x509.UnknownAuthorityError
			hostnameError         *x509.HostnameError
		)
		if errors.As(urlError.Err, unknownAuthorityError) {
			return true, unknownAuthorityError.Error()
		}
		if errors.As(urlError.Err, hostnameError) {
			return true, hostnameError.Error()
		}
		if errors.As(urlError.Err, certInvalidError) {
			return true, certInvalidError.Error()
		}
	}
	return false, ""
}

func (p *portalProxy) RegisterEndpoint(c echo.Context, fetchInfo api.InfoFunc) error {
	log.Debug("registerEndpoint")

	params := new(api.RegisterEndpointParams)
	err := api.BindOnce(params, c)
	if err != nil {
		return err
	}

	skipSSLValidation, err := strconv.ParseBool(params.SkipSSLValidation)
	if err != nil {
		log.Errorf("Failed to parse skip_ssl_validation value: %s", err)
		// default to false
		skipSSLValidation = false
	}

	ssoAllowed, err := strconv.ParseBool(params.SSOAllowed)
	if err != nil {
		// default to false
		ssoAllowed = false
	}

	cnsiClientId := params.CNSIClientID
	cnsiClientSecret := params.CNSIClientSecret
	subType := params.SubType

	createSystemEndpoint, err := strconv.ParseBool(params.CreateSystemEndpoint)
	if err != nil {
		// default to true
		createSystemEndpoint = true
	}

	if cnsiClientId == "" {
		cnsiClientId = p.GetConfig().CFClient
		cnsiClientSecret = p.GetConfig().CFClientSecret
	}

	userID, err := p.GetSessionStringValue(c, "user_id")
	if err != nil {
		return api.NewHTTPShadowError(
			http.StatusInternalServerError,
			"Failed to get session user",
			"Failed to get session user: %v", err)
	}

	caCert := params.CACert

	newCNSI, err := p.DoRegisterEndpoint(params.CNSIName, params.APIEndpoint, skipSSLValidation, cnsiClientId, cnsiClientSecret, userID, ssoAllowed, subType, createSystemEndpoint, caCert, fetchInfo)
	if err != nil {
		return err
	}

	c.JSON(http.StatusCreated, newCNSI)
	return nil
}

func (p *portalProxy) DoRegisterEndpoint(cnsiName string, apiEndpoint string, skipSSLValidation bool, clientId string, clientSecret string, userId string, ssoAllowed bool, subType string, createSystemEndpoint bool, caCert string, fetchInfo api.InfoFunc) (api.CNSIRecord, error) {
	log.Debug("DoRegisterEndpoint")

	if len(cnsiName) == 0 || len(apiEndpoint) == 0 {
		return api.CNSIRecord{}, api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Needs CNSI Name and API Endpoint",
			"CNSI Name or Endpoint were not provided when trying to register an CF Cluster")
	}

	apiEndpoint = strings.TrimRight(apiEndpoint, "/")

	// Remove trailing slash, if there is one
	apiEndpointURL, err := url.Parse(apiEndpoint)
	if err != nil {
		return api.CNSIRecord{}, api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Failed to get API Endpoint",
			"Failed to get API Endpoint: %v", err)
	}

	isAdmin := true

	// anonymous admin?
	if p.GetConfig().UserEndpointsEnabled != config.UserEndpointsConfigEnum.Disabled && len(userId) != 0 {
		currentCreator, err := p.StratosAuthService.GetUser(userId)
		if err != nil {
			return api.CNSIRecord{}, api.NewHTTPShadowError(
				http.StatusInternalServerError,
				"Failed to get user information",
				"Failed to get user information: %v", err)
		}
		isAdmin = currentCreator.Admin
	}

	if p.GetConfig().UserEndpointsEnabled == config.UserEndpointsConfigEnum.Disabled {
		// check if we've already got this endpoint in the DB
		ok := p.adminCNSIRecordExists(apiEndpoint)
		if ok {
			// a record with the same api endpoint was found
			return api.CNSIRecord{}, api.NewHTTPShadowError(
				http.StatusBadRequest,
				"Can not register same endpoint multiple times",
				"Can not register same endpoint multiple times",
			)
		}
	} else {
		// get all endpoints determined by the APIEndpoint
		duplicateEndpoints, err := p.listCNSIByAPIEndpoint(apiEndpoint)
		if err != nil {
			return api.CNSIRecord{}, api.NewHTTPShadowError(
				http.StatusBadRequest,
				"Failed to check other endpoints",
				"Failed to check other endpoints: %v",
				err)
		}
		// check if we've already got this APIEndpoint in this DB
		for _, duplicate := range duplicateEndpoints {
			// cant create same system endpoint
			if len(duplicate.Creator) == 0 && isAdmin && createSystemEndpoint {
				return api.CNSIRecord{}, api.NewHTTPShadowError(
					http.StatusBadRequest,
					"Can not register same system endpoint multiple times",
					"Can not register same system endpoint multiple times",
				)
			}

			// cant create same user endpoint
			if duplicate.Creator == userId {
				return api.CNSIRecord{}, api.NewHTTPShadowError(
					http.StatusBadRequest,
					"Can not register same endpoint multiple times",
					"Can not register same endpoint multiple times",
				)
			}
		}
	}

	h := sha1.New()
	// see why its generated this way in Issue #4753 / #3031
	if p.GetConfig().UserEndpointsEnabled != config.UserEndpointsConfigEnum.Disabled && (!isAdmin || (isAdmin && !createSystemEndpoint)) {
		// Make the new guid unique per api url AND user id
		h.Write([]byte(apiEndpointURL.String() + userId))
	} else {
		h.Write([]byte(apiEndpointURL.String()))
	}
	guid := base64.RawURLEncoding.EncodeToString(h.Sum(nil))

	newCNSI, _, err := fetchInfo(apiEndpoint, skipSSLValidation, caCert)
	if err != nil {
		if ok, detail := isSSLRelatedError(err); ok {
			return api.CNSIRecord{}, api.NewHTTPShadowError(
				http.StatusForbidden,
				"SSL error - "+detail,
				"There is a problem with the server Certificate - %s",
				detail)
		}
		return api.CNSIRecord{}, api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Failed to validate endpoint",
			"Failed to validate endpoint: %v",
			err)
	}

	newCNSI.Name = cnsiName
	newCNSI.APIEndpoint = apiEndpointURL
	newCNSI.SkipSSLValidation = skipSSLValidation
	newCNSI.ClientId = clientId
	newCNSI.ClientSecret = clientSecret
	newCNSI.SSOAllowed = ssoAllowed
	newCNSI.SubType = subType
	newCNSI.CACert = caCert

	if p.GetConfig().UserEndpointsEnabled != config.UserEndpointsConfigEnum.Disabled && (!isAdmin || !createSystemEndpoint) {
		newCNSI.Creator = userId
	}

	err = p.setCNSIRecord(guid, newCNSI)

	// set the guid on the object so it's returned in the response
	newCNSI.GUID = guid

	// Notify plugins if they support the notification interface
	for _, plugin := range p.Plugins {
		if notifier, ok := plugin.(api.EndpointNotificationPlugin); ok {
			notifier.OnEndpointNotification(api.EndpointRegisterAction, &newCNSI)
		}
	}

	return newCNSI, err
}

// unregisterCluster godoc
// @Summary Unregister endpoint
// @Description
// @Tags admin
// @Accept	x-www-form-urlencoded
// @Produce	json
// @Param id path string true "Endpoint GUID"
// @Success 200
// @Failure 400 {object} api.ErrorResponseBody "Error response"
// @Failure 401 {object} api.ErrorResponseBody "Error response"
// @Security ApiKeyAuth
// @Router /endpoints/{id} [delete]
// TODO (wchrisjohnson) We need do this as a TRANSACTION, vs a set of single calls
func (p *portalProxy) unregisterCluster(c echo.Context) error {
	cnsiGUID := c.Param("id")
	log.WithField("cnsiGUID", cnsiGUID).Debug("unregisterCluster")

	if len(cnsiGUID) == 0 {
		return api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Missing target endpoint",
			"Need CNSI GUID passed as form param")
	}

	return p.doUnregisterCluster(cnsiGUID)
}

func (p *portalProxy) doUnregisterCluster(cnsiGUID string) error {
	log.Debug("doUnregisterCluster")

	// Should check for errors?
	p.unsetCNSIRecord(cnsiGUID)

	p.unsetCNSITokenRecords(cnsiGUID)

	ufe := userfavoritesendpoints.Constructor(p, cnsiGUID)
	ufe.RemoveFavorites()

	return nil
}

func (p *portalProxy) buildCNSIList(c echo.Context) ([]*api.CNSIRecord, error) {
	log.Debug("buildCNSIList")

	if p.GetConfig().UserEndpointsEnabled != config.UserEndpointsConfigEnum.Disabled {
		userID, err := p.GetSessionValue(c, "user_id")
		if err != nil {
			return nil, err
		}

		u, err := p.StratosAuthService.GetUser(userID.(string))
		if err != nil {
			return nil, err
		}

		if u.Admin {
			return p.ListEndpoints()
		}

		if p.GetConfig().UserEndpointsEnabled != config.UserEndpointsConfigEnum.AdminOnly {
			// if endpoint with same url exists as system and user endpoint, hide the system endpoint
			unfilteredList, err := p.ListAdminEndpoints(userID.(string))
			if err != nil {
				return unfilteredList, err
			}

			filteredList := []*api.CNSIRecord{}

			for _, endpoint := range unfilteredList {
				duplicateSystemEndpoint := false
				duplicateEndpointIndex := -1

				for i := 0; i < len(filteredList); i++ {
					if filteredList[i].APIEndpoint.String() == endpoint.APIEndpoint.String() {
						duplicateSystemEndpoint = len(filteredList[i].Creator) == 0
						duplicateEndpointIndex = i
					}
				}

				if duplicateEndpointIndex != -1 && !u.Admin {
					if duplicateSystemEndpoint {
						filteredList[duplicateEndpointIndex] = endpoint
					}
				} else {
					filteredList = append(filteredList, endpoint)
				}
			}

			return filteredList, err
		}
	}
	return p.ListAdminEndpoints("")
}

func (p *portalProxy) ListEndpoints() ([]*api.CNSIRecord, error) {
	log.Debug("ListEndpoints")
	var cnsiList []*api.CNSIRecord
	var err error

	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		return cnsiList, fmt.Errorf("listRegisteredCNSIs: %s", err)
	}

	cnsiList, err = cnsiRepo.List(p.Config.EncryptionKeyInBytes)
	if err != nil {
		return cnsiList, err
	}

	return cnsiList, nil
}

// ListAdminEndpoints - return a CNSI list with endpoints created by the current user and all admins
func (p *portalProxy) ListAdminEndpoints(userID string) ([]*api.CNSIRecord, error) {
	log.Debug("ListAdminEndpoints")
	// Initialise cnsiList to ensure empty struct (marshals to null) is not returned
	cnsiList := []*api.CNSIRecord{}
	var userList []string
	var err error

	userList = append(userList, userID)
	if len(userID) != 0 {
		userList = append(userList, "")
	}

	//get a cnsi list from every admin found and given userID
	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		return cnsiList, fmt.Errorf("listRegisteredCNSIs: %s", err)
	}

	for _, id := range userList {
		creatorList, err := cnsiRepo.ListByCreator(id, p.Config.EncryptionKeyInBytes)
		if err != nil {
			return creatorList, err
		}
		cnsiList = append(cnsiList, creatorList...)
	}
	return cnsiList, nil
}

// listCNSIByAPIEndpoint - receives a URL as string
func (p *portalProxy) listCNSIByAPIEndpoint(apiEndpoint string) ([]*api.CNSIRecord, error) {
	log.Debug("listCNSIByAPIEndpoint")

	var err error
	cnsiList := []*api.CNSIRecord{}

	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		return cnsiList, fmt.Errorf("listCNSIByAPIEndpoint: %s", err)
	}

	cnsiList, err = cnsiRepo.ListByAPIEndpoint(apiEndpoint, p.Config.EncryptionKeyInBytes)
	if err != nil {
		return cnsiList, err
	}

	for _, cnsi := range cnsiList {
		// Ensure that trailing slash is removed from the API Endpoint
		cnsi.APIEndpoint.Path = strings.TrimRight(cnsi.APIEndpoint.Path, "/")
	}

	return cnsiList, nil
}

// listCNSIs godoc
// @Summary List endpoints
// @Description
// @Accept	x-www-form-urlencoded
// @Produce	json
// @Success 200 {array}  api.CNSIRecord "List of endpoints"
// @Failure 400 {object} api.ErrorResponseBody "Error response"
// @Failure 401 {object} api.ErrorResponseBody "Error response"
// @Security ApiKeyAuth
// @Router /endpoints [get]
func (p *portalProxy) listCNSIs(c echo.Context) error {
	log.Debug("listCNSIs")
	cnsiList, err := p.buildCNSIList(c)
	if err != nil {
		return api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Failed to retrieve list of CNSIs",
			"Failed to retrieve list of CNSIs: %v", err,
		)
	}

	jsonString, err := marshalCNSIlist(cnsiList)
	if err != nil {
		return err
	}

	c.Response().Header().Set("Content-Type", "application/json")
	c.Response().Write(jsonString)
	return nil
}

func (p *portalProxy) listRegisteredCNSIs(c echo.Context) error {
	log.Debug("listRegisteredCNSIs")
	userGUIDIntf, err := p.GetSessionValue(c, "user_id")
	if err != nil {
		return api.NewHTTPShadowError(
			http.StatusBadRequest,
			"User session could not be found",
			"User session could not be found: %v", err,
		)
	}
	userGUID := userGUIDIntf.(string)

	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		return fmt.Errorf("listRegisteredCNSIs: %s", err)
	}

	var jsonString []byte
	var clusterList []*api.ConnectedEndpoint

	clusterList, err = cnsiRepo.ListByUser(userGUID)
	if err != nil {
		return api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Failed to retrieve list of clusters",
			"Failed to retrieve list of clusters: %v", err,
		)
	}

	jsonString, err = marshalClusterList(clusterList)
	if err != nil {
		return err
	}

	c.Response().Header().Set("Content-Type", "application/json")
	c.Response().Write(jsonString)
	return nil
}

func marshalCNSIlist(cnsiList []*api.CNSIRecord) ([]byte, error) {
	log.Debug("marshalCNSIlist")
	jsonString, err := json.Marshal(cnsiList)
	if err != nil {
		return nil, api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Failed to retrieve list of CNSIs",
			"Failed to retrieve list of CNSIs: %v", err,
		)
	}
	return jsonString, nil
}

func marshalClusterList(clusterList []*api.ConnectedEndpoint) ([]byte, error) {
	log.Debug("marshalClusterList")
	jsonString, err := json.Marshal(clusterList)
	if err != nil {
		return nil, api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Failed to retrieve list of clusters",
			"Failed to retrieve list of clusters: %v", err,
		)
	}
	return jsonString, nil
}

func (p *portalProxy) UpdateEndpointMetadata(guid string, metadata string) error {
	log.Debug("UpdateEndpointMetadata")

	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return fmt.Errorf(dbReferenceError, err)
	}

	err = cnsiRepo.UpdateMetadata(guid, metadata)
	if err != nil {
		msg := "unable to update endpoint metadata: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	return nil
}

func (p *portalProxy) GetCNSIRecord(guid string) (api.CNSIRecord, error) {
	log.Debug("GetCNSIRecord")
	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		return api.CNSIRecord{}, err
	}

	rec, err := cnsiRepo.Find(guid, p.Config.EncryptionKeyInBytes)
	if err != nil {
		return api.CNSIRecord{}, err
	}

	// Ensure that trailing slash is removed from the API Endpoint
	rec.APIEndpoint.Path = strings.TrimRight(rec.APIEndpoint.Path, "/")

	return rec, nil
}

func (p *portalProxy) GetAdminCNSIRecordByEndpoint(endpoint string) (api.CNSIRecord, error) {
	log.Debug("GetAdminCNSIRecordByEndpoint")
	var rec *api.CNSIRecord

	endpointList, err := p.listCNSIByAPIEndpoint(endpoint)
	if err != nil {
		return api.CNSIRecord{}, err
	}

	// search for endpoint created by an admin
	for _, endpoint := range endpointList {
		if len(endpoint.Creator) == 0 {
			rec = endpoint
		}
	}

	if rec == nil {
		return api.CNSIRecord{}, fmt.Errorf("can not find admin CNSIRecord by given endpoint")
	}

	// Ensure that trailing slash is removed from the API Endpoint
	rec.APIEndpoint.Path = strings.TrimRight(rec.APIEndpoint.Path, "/")

	return *rec, nil
}

func (p *portalProxy) adminCNSIRecordExists(apiEndpoint string) bool {
	log.Debug("adminCNSIRecordExists")

	_, err := p.GetAdminCNSIRecordByEndpoint(apiEndpoint)
	return err == nil
}

func (p *portalProxy) setCNSIRecord(guid string, c api.CNSIRecord) error {
	log.Debug("setCNSIRecord")
	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return fmt.Errorf(dbReferenceError, err)
	}

	err = cnsiRepo.Save(guid, c, p.Config.EncryptionKeyInBytes)
	if err != nil {
		msg := "unable to save a CNSI Token: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	return nil
}

func (p *portalProxy) unsetCNSIRecord(guid string) error {
	log.Debug("unsetCNSIRecord")
	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return fmt.Errorf(dbReferenceError, err)
	}

	// Lookup the endpoint, so can pass the information to the plugins
	endpoint, lookupErr := cnsiRepo.Find(guid, p.Config.EncryptionKeyInBytes)

	// Delete the endpoint
	err = cnsiRepo.Delete(guid)
	if err != nil {
		msg := "unable to delete a CNSI record: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	if lookupErr == nil {
		// Notify plugins if they support the notification interface
		for _, plugin := range p.Plugins {
			if notifier, ok := plugin.(api.EndpointNotificationPlugin); ok {
				notifier.OnEndpointNotification(api.EndpointUnregisterAction, &endpoint)
			}
		}
	}

	return nil
}

func (p *portalProxy) SaveEndpointToken(cnsiGUID string, userGUID string, tokenRecord api.TokenRecord) error {
	log.Debug("SaveEndpointToken")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		return err
	}

	return tokenRepo.SaveCNSIToken(cnsiGUID, userGUID, tokenRecord, p.Config.EncryptionKeyInBytes)
}

func (p *portalProxy) DeleteEndpointToken(cnsiGUID string, userGUID string) error {
	log.Debug("DeleteEndpointToken")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		return err
	}

	return tokenRepo.DeleteCNSIToken(cnsiGUID, userGUID)
}

func (p *portalProxy) GetCNSITokenRecord(cnsiGUID string, userGUID string) (api.TokenRecord, bool) {
	log.Debug("GetCNSITokenRecord")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		return api.TokenRecord{}, false
	}

	tr, err := tokenRepo.FindCNSIToken(cnsiGUID, userGUID, p.Config.EncryptionKeyInBytes)
	if err != nil {
		return api.TokenRecord{}, false
	}

	return tr, true
}

func (p *portalProxy) GetCNSITokenRecordWithDisconnected(cnsiGUID string, userGUID string) (api.TokenRecord, bool) {
	log.Debug("GetCNSITokenRecordWithDisconnected")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		return api.TokenRecord{}, false
	}

	tr, err := tokenRepo.FindCNSITokenIncludeDisconnected(cnsiGUID, userGUID, p.Config.EncryptionKeyInBytes)
	if err != nil {
		return api.TokenRecord{}, false
	}

	return tr, true
}

func (p *portalProxy) ListEndpointsByUser(userGUID string) ([]*api.ConnectedEndpoint, error) {
	log.Debug("ListCEndpointsByUser")
	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return nil, fmt.Errorf(dbReferenceError, err)
	}

	cnsiList, err := cnsiRepo.ListByUser(userGUID)
	if err != nil {
		log.Debugf("Error was: %+v", err)
		return nil, err
	}

	return cnsiList, nil
}

// Uopdate the Access Token, Refresh Token and Token Expiry for a token
func (p *portalProxy) updateTokenAuth(userGUID string, t api.TokenRecord) error {
	log.Debug("updateTokenAuth")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return fmt.Errorf(dbReferenceError, err)
	}

	err = tokenRepo.UpdateTokenAuth(userGUID, t, p.Config.EncryptionKeyInBytes)
	if err != nil {
		msg := "unable to update Token: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	return nil
}

func (p *portalProxy) setCNSITokenRecord(cnsiGUID string, userGUID string, t api.TokenRecord) error {
	log.Debug("setCNSITokenRecord")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return fmt.Errorf(dbReferenceError, err)
	}

	err = tokenRepo.SaveCNSIToken(cnsiGUID, userGUID, t, p.Config.EncryptionKeyInBytes)
	if err != nil {
		msg := "unable to save a CNSI Token: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	return nil
}

func (p *portalProxy) unsetCNSITokenRecord(cnsiGUID string, userGUID string) error {
	log.Debug("unsetCNSITokenRecord")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		msg := "unable to establish a database reference: '%v'"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	err = tokenRepo.DeleteCNSIToken(cnsiGUID, userGUID)
	if err != nil {
		msg := "unable to delete a CNSI Token: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	return nil
}

func (p *portalProxy) unsetCNSITokenRecords(cnsiGUID string) error {
	log.Debug("unsetCNSITokenRecord")
	tokenRepo, err := p.GetStoreFactory().TokenStore()
	if err != nil {
		msg := "unable to establish a database reference: '%v'"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	err = tokenRepo.DeleteCNSITokens(cnsiGUID)
	if err != nil {
		msg := "unable to delete a CNSI Token: %v"
		log.Errorf(msg, err)
		return fmt.Errorf(msg, err)
	}

	return nil
}

// updateEndpoint godoc
// @Summary Edit endpoint
// @Description
// @Tags admin
// @Accept	x-www-form-urlencoded
// @Produce	json
// @Param id path string true "Endpoint GUID"
// @Param name formData string true "Endpoint name"
// @Param skipSSL formData string false "Skip SSL" Enums(true, false)
// @Param setClientInfo formData string false "Set client info" Enums(true, false)
// @Param clientID formData string false "Client ID"
// @Param clientSecret formData string false "Client secret"
// @Param allowSSO formData string false "Allow SSO" Enums(true, false)
// @Success 200
// @Failure 400 {object} api.ErrorResponseBody "Error response"
// @Failure 401 {object} api.ErrorResponseBody "Error response"
// @Security ApiKeyAuth
// @Router /endpoints/{id} [post]
func (p *portalProxy) updateEndpoint(ec echo.Context) error {
	log.Debug("updateEndpoint")

	params := new(api.UpdateEndpointParams)
	if err := ec.Bind(params); err != nil {
		return err
	}

	// Check we have an ID
	if len(params.ID) == 0 {
		return api.NewHTTPShadowError(
			http.StatusBadRequest,
			"Missing target endpoint",
			"Need Endpoint ID")
	}

	cnsiRepo, err := p.GetStoreFactory().EndpointStore()
	if err != nil {
		log.Errorf(dbReferenceError, err)
		return fmt.Errorf(dbReferenceError, err)
	}

	endpoint, err := cnsiRepo.Find(params.ID, p.Config.EncryptionKeyInBytes)
	if err != nil {
		return fmt.Errorf("could not find the endpoint %s: '%v'", params.ID, err)
	}

	updates := false

	// Update name
	name := params.Name
	if len(name) > 0 {
		endpoint.Name = name
		updates = true
	}

	// CA Cert
	caCert := params.CACert
	if strings.Compare(endpoint.CACert, caCert) != 0 {
		endpoint.CACert = caCert
		updates = true
	}

	// Skip SSL validation
	skipSSL := params.SkipSSL
	if len(skipSSL) > 0 {
		v, err := strconv.ParseBool(skipSSL)
		if err == nil {
			if v != endpoint.SkipSSLValidation {
				// SSL Validation value changed
				endpoint.SkipSSLValidation = v
				updates = true
				if !v {
					// Skip SSL validation is OFF - so check we can communicate with the endpoint
					plugin, err := p.GetEndpointTypeSpec(endpoint.CNSIType)
					if err != nil {
						return fmt.Errorf("can not get endpoint type for %s: '%v'", endpoint.CNSIType, err)
					}
					_, _, err = plugin.Info(endpoint.APIEndpoint.String(), endpoint.SkipSSLValidation, endpoint.CACert)
					if err != nil {
						if ok, detail := isSSLRelatedError(err); ok {
							return api.NewHTTPShadowError(
								http.StatusForbidden,
								"SSL error - "+detail,
								"There is a problem with the server Certificate - %s",
								detail)
						}
						return api.NewHTTPShadowError(
							http.StatusBadRequest,
							fmt.Sprintf("Could not validate endpoint: %v", err),
							"Could not validate endpoint: %v",
							err)
					}
				}
			}
		}
	}

	// Client ID and Client Secret
	setClientInfo := params.SetClientInfo
	isSet, err := strconv.ParseBool(setClientInfo)
	if err == nil && isSet {
		clientID := params.ClientID
		clientSecret := params.ClientSecret
		endpoint.ClientId = clientID
		endpoint.ClientSecret = clientSecret
		updates = true
	}

	// Allow SSO
	allowSSO := params.AllowSSO
	if len(allowSSO) > 0 {
		v, err := strconv.ParseBool(allowSSO)
		if err == nil {
			if v != endpoint.SSOAllowed {
				// Allow SSO value changed
				endpoint.SSOAllowed = v
				updates = true
			}
		}
	}

	// Apply updates
	if updates {
		err := cnsiRepo.Update(endpoint, p.Config.EncryptionKeyInBytes)
		if err != nil {
			return fmt.Errorf("could not update the endpoint %s: '%v'", params.ID, err)
		}
	}

	// Notify plugins if they support the notification interface
	for _, plugin := range p.Plugins {
		if notifier, ok := plugin.(api.EndpointNotificationPlugin); ok {
			notifier.OnEndpointNotification(api.EndpointUpdateAction, &endpoint)
		}
	}

	return nil
}
