package server

import (
	"context"
	"fmt"
	"sort"
	"time"

	"github.com/RafayLabs/rcloud-base/internal/constants"
	"github.com/RafayLabs/rcloud-base/pkg/query"
	"github.com/RafayLabs/rcloud-base/pkg/sentry/cryptoutil"
	"github.com/RafayLabs/rcloud-base/pkg/sentry/kubeconfig"
	"github.com/RafayLabs/rcloud-base/pkg/sentry/util"
	"github.com/RafayLabs/rcloud-base/pkg/service"
	sentryrpc "github.com/RafayLabs/rcloud-base/proto/rpc/sentry"
	commonv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3"
	sentry "github.com/RafayLabs/rcloud-base/proto/types/sentry"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type kubeConfigServer struct {
	bs  service.BootstrapService
	aps service.AccountPermissionService
	gps service.GroupPermissionService
	kss service.KubeconfigSettingService
	krs service.KubeconfigRevocationService
	pf  cryptoutil.PasswordFunc
	ks  service.ApiKeyService
	os  service.OrganizationService
	ps  service.PartnerService
}

var _ sentryrpc.KubeConfigServer = (*kubeConfigServer)(nil)

func (s *kubeConfigServer) GetForClusterSystemSession(ctx context.Context, in *sentryrpc.GetForClusterRequest) (*commonv3.HttpBody, error) {
	config, err := kubeconfig.GetConfigForCluster(ctx, s.bs, in, s.pf, s.kss, kubeconfig.RafaySystem)
	if err != nil {
		return nil, err
	}
	return &commonv3.HttpBody{
		ContentType: "application/yaml",
		Data:        config,
	}, nil
}

func (s *kubeConfigServer) GetForClusterWebSession(ctx context.Context, in *sentryrpc.GetForClusterRequest) (*commonv3.HttpBody, error) {
	config, err := kubeconfig.GetConfigForCluster(ctx, s.bs, in, s.pf, s.kss, kubeconfig.WebShell)
	if err != nil {
		return nil, err
	}
	return &commonv3.HttpBody{
		ContentType: "application/yaml",
		Data:        config,
	}, nil
}

func (s *kubeConfigServer) GetForUser(ctx context.Context, in *sentryrpc.GetForUserRequest) (*commonv3.HttpBody, error) {
	config, err := kubeconfig.GetConfigForUser(ctx, s.bs, s.aps, s.gps, in, s.pf, s.kss, s.ks, s.os, s.ps)
	if err != nil {
		_log.Errorw("error generating kubeconfig", "error", err.Error())
		return nil, err
	}
	return &commonv3.HttpBody{
		ContentType: "application/yaml",
		Data:        config,
	}, nil
}

func (s *kubeConfigServer) RevokeKubeconfig(ctx context.Context, req *sentryrpc.RevokeKubeconfigRequest) (*sentryrpc.RevokeKubeconfigResponse, error) {
	opts := req.Opts
	accountID, err := query.GetAccountID(opts)
	if err != nil {
		return nil, err
	}
	isSSOUser := false

	// if no user scope in url revoke for current user
	if opts.UrlScope == "" {
		isSSOUser = opts.IsSSOUser
	}
	err = s.krs.Patch(ctx, &sentry.KubeconfigRevocation{
		OrganizationID: opts.Organization,
		PartnerID:      opts.Partner,
		AccountID:      accountID,
		IsSSOUser:      isSSOUser,
		RevokedAt:      timestamppb.New(time.Now()),
	})
	if err != nil {
		return nil, err
	}

	/*TODO: pending with events
	revokeUser, err := kubeconfig.GetUserNameFromAccountID(ctx, accountID, opts.Organization, s.aps, opts.IsSSOUser)
	acID := accountID
	partnerID := opts.Partner
	orgID := opts.Organization
	kubeconfigRevokeEvent(ctx, "user.kubeconfig.revoke", orgID, partnerID, revokeUser, acID, opts.Username, opts.Account, opts.Groups)
	*/

	return &sentryrpc.RevokeKubeconfigResponse{}, nil
}

func (s *kubeConfigServer) GetOrganizationSetting(ctx context.Context, req *sentryrpc.GetKubeconfigSettingRequest) (*sentryrpc.GetKubeconfigSettingResponse, error) {
	opts := req.Opts
	orgID, err := util.GetOrganizationScope(opts.UrlScope)

	if err != nil {
		return nil, err
	}
	if orgID != opts.Organization {
		opts.Organization = orgID
	}
	ks, err := s.kss.Get(ctx, opts.Organization, "", false)
	if err == constants.ErrNotFound {
		return &sentryrpc.GetKubeconfigSettingResponse{ValiditySeconds: 31536000}, nil
	} else if err != nil {
		return nil, err
	}

	resp := &sentryrpc.GetKubeconfigSettingResponse{
		ValiditySeconds:             ks.ValiditySeconds,
		EnableSessionCheck:          ks.EnableSessionCheck,
		EnablePrivateRelay:          ks.EnablePrivateRelay,
		EnforceOrgAdminSecretAccess: ks.EnforceOrgAdminSecretAccess,
		DisableWebKubectl:           ks.DisableWebKubectl,
		DisableCLIKubectl:           ks.DisableCLIKubectl,
	}
	return resp, nil
}

func (s *kubeConfigServer) GetUserSetting(ctx context.Context, req *sentryrpc.GetKubeconfigSettingRequest) (*sentryrpc.GetKubeconfigSettingResponse, error) {
	opts := req.Opts
	accountID, err := util.GetUserScope(opts.UrlScope)
	if err != nil {
		return nil, err
	}
	ks, err := s.kss.Get(ctx, opts.Organization, accountID, false)
	if err == constants.ErrNotFound {
		req.Opts.UrlScope = "organization/" + opts.Organization
		return s.GetOrganizationSetting(ctx, req)
	} else if err != nil {
		return nil, err
	}
	resp := &sentryrpc.GetKubeconfigSettingResponse{
		ValiditySeconds:             ks.ValiditySeconds,
		EnableSessionCheck:          ks.EnableSessionCheck,
		EnablePrivateRelay:          ks.EnablePrivateRelay,
		EnforceOrgAdminSecretAccess: ks.EnforceOrgAdminSecretAccess,
		DisableWebKubectl:           ks.DisableWebKubectl,
		DisableCLIKubectl:           ks.DisableCLIKubectl,
	}
	return resp, nil

}

func (s *kubeConfigServer) UpdateOrganizationSetting(ctx context.Context, req *sentryrpc.UpdateKubeconfigSettingRequest) (*sentryrpc.UpdateKubeconfigSettingResponse, error) {
	opts := req.Opts
	orgID, err := util.GetOrganizationScope(opts.UrlScope)
	if err != nil {
		return nil, err
	}
	if orgID != opts.Organization {
		return nil, fmt.Errorf("invalid request")
	}

	err = s.kss.Patch(ctx, &sentry.KubeconfigSetting{
		OrganizationID:              opts.Organization,
		PartnerID:                   opts.Partner,
		AccountID:                   "",
		ValiditySeconds:             req.ValiditySeconds,
		EnableSessionCheck:          req.EnableSessionCheck,
		EnablePrivateRelay:          req.EnablePrivateRelay,
		EnforceOrgAdminSecretAccess: req.EnforceOrgAdminSecretAccess,
		DisableWebKubectl:           req.DisableWebKubectl,
		DisableCLIKubectl:           req.DisableCLIKubectl,
	})
	if err != nil {
		return nil, err
	}

	/*TODO:pending with events
	partnerID := opts.Partner
	orgIDString := opts.Organization
	kubeconfigSettingEvent(ctx, "user.kubeconfig.setting", orgIDString, partnerID, "", "", opts.Username, opts.Account, opts.Groups, req.ValiditySeconds, req.EnableSessionCheck)
	*/

	return &sentryrpc.UpdateKubeconfigSettingResponse{}, nil
}

func (s *kubeConfigServer) UpdateUserSetting(ctx context.Context, req *sentryrpc.UpdateKubeconfigSettingRequest) (*sentryrpc.UpdateKubeconfigSettingResponse, error) {
	opts := req.Opts
	accountID, err := util.GetUserScope(opts.UrlScope)
	if err != nil {
		return nil, err
	}
	_log.Infow("UpdateUserSetting", "req.EnforceOrgAdminSecretAccess", req.EnforceOrgAdminSecretAccess)
	_log.Infow("UpdateUserSetting", "req.DisableWebKubectl", req.DisableWebKubectl)

	err = s.kss.Patch(ctx, &sentry.KubeconfigSetting{
		OrganizationID:              opts.Organization,
		PartnerID:                   opts.Partner,
		AccountID:                   accountID,
		ValiditySeconds:             req.ValiditySeconds,
		EnableSessionCheck:          req.EnableSessionCheck,
		IsSSOUser:                   false,
		EnforceOrgAdminSecretAccess: req.EnforceOrgAdminSecretAccess,
		DisableWebKubectl:           req.DisableWebKubectl,
		DisableCLIKubectl:           req.DisableCLIKubectl,
	})
	if err != nil {
		return nil, err
	}

	/*TODO: pending with events
	forUser, err := kubeconfig.GetUserNameFromAccountID(ctx, accountID, opts.Organization, s.aps, opts.IsSSOUser)
	acID := accountID
	partnerID := opts.Partner
	orgIDString := opts.Organization
	kubeconfigSettingEvent(ctx, "user.kubeconfig.setting", orgIDString, partnerID, forUser, acID, opts.Username, opts.Account, opts.Groups, req.ValiditySeconds, req.EnableSessionCheck)
	*/
	return &sentryrpc.UpdateKubeconfigSettingResponse{}, nil
}

// NewKubeConfigServer returns new kube config server
func NewKubeConfigServer(bs service.BootstrapService, aps service.AccountPermissionService, gps service.GroupPermissionService, kss service.KubeconfigSettingService,
	krs service.KubeconfigRevocationService, pf cryptoutil.PasswordFunc, ksvc service.ApiKeyService, os service.OrganizationService, ps service.PartnerService) sentryrpc.KubeConfigServer {
	return &kubeConfigServer{bs, aps, gps, kss, krs, pf, ksvc, os, ps}
}

func checkOrgAdmin(groups []string) bool {
	orgGrp := "Organization Admins"
	sort.Strings(groups)
	indx := sort.SearchStrings(groups, orgGrp)
	if indx < len(groups) {
		if groups[indx] == orgGrp {
			return true
		}
	}
	return false
}

func (s *kubeConfigServer) RevokeKubeconfigSSO(ctx context.Context, req *sentryrpc.RevokeKubeconfigRequest) (*sentryrpc.RevokeKubeconfigResponse, error) {
	opts := req.Opts
	accountID, err := query.GetAccountID(opts)
	if err != nil {
		return nil, err
	}
	err = s.krs.Patch(ctx, &sentry.KubeconfigRevocation{
		OrganizationID: opts.Organization,
		PartnerID:      opts.Partner,
		AccountID:      accountID,
		IsSSOUser:      true,
		RevokedAt:      timestamppb.New(time.Now()),
	})
	if err != nil {
		return nil, err
	}

	/*TODO: pending with events
	revokeUser, err := kubeconfig.GetUserNameFromAccountID(ctx, accountID, opts.Organization, s.aps, opts.IsSSOUser)
	acID := accountID
	partnerID := opts.Partner
	orgID := opts.Organization
	kubeconfigRevokeEvent(ctx, "user.kubeconfig.revoke", orgID, partnerID, revokeUser, acID, opts.Username, opts.Account.String(), opts.Groups)
	*/
	return &sentryrpc.RevokeKubeconfigResponse{}, nil
}
func (s *kubeConfigServer) GetSSOUserSetting(ctx context.Context, req *sentryrpc.GetKubeconfigSettingRequest) (*sentryrpc.GetKubeconfigSettingResponse, error) {
	opts := req.Opts
	accountID, err := util.GetUserScope(opts.UrlScope)
	if err != nil {
		return nil, err
	}
	ks, err := s.kss.Get(ctx, opts.Organization, accountID, true)
	if err == constants.ErrNotFound {
		req.Opts.UrlScope = "organization/" + opts.Organization
		return s.GetOrganizationSetting(ctx, req)
	} else if err != nil {
		return nil, err
	}
	resp := &sentryrpc.GetKubeconfigSettingResponse{
		ValiditySeconds:             ks.ValiditySeconds,
		EnableSessionCheck:          ks.EnableSessionCheck,
		EnablePrivateRelay:          ks.EnablePrivateRelay,
		EnforceOrgAdminSecretAccess: ks.EnforceOrgAdminSecretAccess,
		DisableWebKubectl:           ks.DisableWebKubectl,
		DisableCLIKubectl:           ks.DisableCLIKubectl,
	}
	return resp, nil
}

func (s *kubeConfigServer) UpdateSSOUserSetting(ctx context.Context, req *sentryrpc.UpdateKubeconfigSettingRequest) (*sentryrpc.UpdateKubeconfigSettingResponse, error) {
	opts := req.Opts
	accountID, err := util.GetUserScope(opts.UrlScope)
	if err != nil {
		return nil, err
	}
	_log.Infow("UpdateSSOUserSetting", "req.EnforceOrgAdminSecretAccess", req.EnforceOrgAdminSecretAccess)
	_log.Infow("UpdateSSOUserSetting", "req.DisableWebKubectl", req.DisableWebKubectl)

	err = s.kss.Patch(ctx, &sentry.KubeconfigSetting{
		OrganizationID:              opts.Organization,
		PartnerID:                   opts.Partner,
		AccountID:                   accountID,
		ValiditySeconds:             req.ValiditySeconds,
		EnableSessionCheck:          req.EnableSessionCheck,
		IsSSOUser:                   true,
		EnforceOrgAdminSecretAccess: req.EnforceOrgAdminSecretAccess,
		DisableWebKubectl:           req.DisableWebKubectl,
		DisableCLIKubectl:           req.DisableCLIKubectl,
	})
	if err != nil {
		return nil, err
	}

	/* TODO: pending with events
	forUser, err := kubeconfig.GetUserNameFromAccountID(ctx, accountID, opts.Organization, s.aps, opts.IsSSOUser)
	acID := accountID
	partnerID := opts.Partner
	orgIDString := opts.Organization
	kubeconfigSettingEvent(ctx, "user.kubeconfig.setting", orgIDString, partnerID, forUser, acID, opts.Username, opts.Account.String(), opts.Groups, req.ValiditySeconds, req.EnableSessionCheck)
	*/

	return &sentryrpc.UpdateKubeconfigSettingResponse{}, nil
}
