package dbjwt

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

	"github.com/duke-git/lancet/v2/slice"
	"github.com/google/uuid"
	"github.com/spf13/viper"
	"github.com/tidbcloud/account/thirdparty/infra"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	authcenter2 "github.com/tidbcloud/account/internel/svc/authcenter"
	"github.com/tidbcloud/account/internel/svc/dbjwt"

	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/tiinf/errs"

	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"

	"github.com/tidbcloud/account/idl/pbgen/proto/central"
)

type dbJwtService struct {
	central.UnimplementedDbJwtServiceServer
}

func NewService() central.DbJwtServiceServer {
	return &dbJwtService{}
}

const (
	getBuiltinUserJwtOrgWhitelistKey      = "dbUser.getBuiltinUserJwtOrgWhitelist"
	getBuiltinUserJwtUsernameWhitelistKey = "dbUser.getBuiltinUserJwtUsernameWhitelist"
	builtinUserJwtMaxExpiredSeconds       = 60 * 15
	serverlessTokenPrefixSeparator        = "."
)

func (s *dbJwtService) GetJwtByToken(ctx context.Context, req *central.GetJwtByTokenReq) (*central.GetJwtByTokenResp, error) {
	var token *dbjwt.DbJwtInfo
	var err error
	if req.Auth0Token != "" {
		token, err = dbjwt.GlobalDBJwtSvc.GetJwtByToken(ctx, &dbjwt.GetJwtByTokenParam{
			ClusterID:  req.ClusterId,
			Auth0Token: req.Auth0Token,
		})
	} else if req.UserId != 0 || req.Email != "" {
		token, err = dbjwt.GlobalDBJwtSvc.GetJwtByUser(ctx, &dbjwt.GetJwtByUserParam{
			ClusterID: req.ClusterId,
			UserID:    req.UserId,
			Email:     req.Email,
		})
	} else {
		return nil, errs.NewBadRequest("param invalid", nil)
	}

	if err != nil {
		return nil, err
	}

	response := &central.GetJwtByTokenResp{
		Jwt:      token.Jwt,
		DbUser:   token.DbUser,
		BaseResp: &base.BaseResp{},
	}
	return response, nil
}

func (s *dbJwtService) GetJwtByApiKey(ctx context.Context, req *central.GetJwtByApiKeyReq) (*central.GetJwtByApiKeyResp, error) {
	param := dbjwt.GetJwtByApiKeyParam{
		ClusterID: req.ClusterId,
		AccessKey: req.AccessKey,
		ApiKeyID:  req.ApiKeyId,
	}
	token, err := dbjwt.GlobalDBJwtSvc.GetJwtByApiKey(ctx, &param)
	if err != nil {
		return nil, err
	}

	response := &central.GetJwtByApiKeyResp{
		Jwt:      token.Jwt,
		DbUser:   token.DbUser,
		BaseResp: &base.BaseResp{},
	}
	return response, nil

}

func (s *dbJwtService) GetJwtByBuiltinUser(ctx context.Context, req *central.GetJwtByBuiltinUserReq) (*central.GetJwtByBuiltinUserResp, error) {
	bm := utils.Begin(ctx, "GetJwtByBuiltinUser")
	defer bm.OnExit(ctx)

	if len(req.Users) == 0 {
		return nil, errs.NewBadRequest("request builtin user is empty", nil)
	}
	if len(req.Users) > 50 {
		return nil, errs.NewBadRequest("the maximum number of builtin user is 50", nil)
	}

	dataCh := make(chan *central.BuiltinUserGetJwtRet)
	jwts := make([]*central.BuiltinUserGetJwtRet, 0, len(req.Users))
	stopCh := make(chan struct{}, 1)

	// collect jwt
	go func() {
		for data := range dataCh {
			jwts = append(jwts, data)
		}
		stopCh <- struct{}{}
	}()

	// gen jwt
	wg := sync.WaitGroup{}
	for i := range req.Users {
		wg.Add(1)
		u := req.Users[i]
		go func(ch chan<- *central.BuiltinUserGetJwtRet) {
			defer wg.Done()
			dataCh <- genJwt4BuiltinUser(ctx, u)
		}(dataCh)
	}
	wg.Wait()
	close(dataCh)

	// wait for read goroutine over
	<-stopCh

	rsp := &central.GetJwtByBuiltinUserResp{
		Jwts:     jwts,
		BaseResp: &base.BaseResp{},
	}
	return rsp, nil
}

func genJwt4BuiltinUser(ctx context.Context, param *central.BuiltinUserGetJwtParam) *central.BuiltinUserGetJwtRet {
	trace := uuid.New().String()
	tilog.Info(ctx, "genJwt4BuiltinUser start", zap.Any("local_trace", trace), zap.Any("param", param))
	ret := &central.BuiltinUserGetJwtRet{
		Username:            param.Username,
		ClusterId:           param.ClusterId,
		OrgId:               param.OrgId,
		TokenExpiredSeconds: param.TokenExpiredSeconds,
	}
	orgWhitelist := viper.GetStringSlice(getBuiltinUserJwtOrgWhitelistKey)
	tilog.Info(ctx, "org whitelist", zap.Any("whitelist", orgWhitelist))
	if !slice.Contain(orgWhitelist, fmt.Sprintf("%d", param.OrgId)) {
		ret.Message = "org_id is not in the whitelist"
		tilog.Info(ctx, "genJwt4BuiltinUser fail", zap.Any("local_trace", trace), zap.Any("message", ret.Message))
		return ret
	}
	username := param.Username
	index := strings.Index(username, serverlessTokenPrefixSeparator)
	if index != -1 {
		username = username[index+1:]
	}
	usernameWhitelist := viper.GetStringSlice(getBuiltinUserJwtUsernameWhitelistKey)
	tilog.Info(ctx, "username whitelist", zap.Any("whitelist", usernameWhitelist))
	if !slice.Contain(usernameWhitelist, username) {
		ret.Message = "username is not in the whitelist"
		tilog.Info(ctx, "genJwt4BuiltinUser fail", zap.Any("local_trace", trace), zap.Any("message", ret.Message))
		return ret
	}
	cluster, err := infra.GetHandler().GetClusterById(ctx, param.ClusterId, true)
	tilog.Info(ctx, "genJwt4BuiltinUser GetClusterById result", zap.Any("local_trace", trace), zap.Any("cluster", cluster), zap.Any("err", err))
	if err != nil {
		ret.Message = "call cm get cluster info error"
		tilog.Info(ctx, "genJwt4BuiltinUser fail", zap.Any("local_trace", trace), zap.Any("message", ret.Message))
		return ret
	}
	if cluster == nil {
		ret.Message = "cluster is not exist"
		tilog.Info(ctx, "genJwt4BuiltinUser fail", zap.Any("local_trace", trace), zap.Any("message", ret.Message))
		return ret
	}
	if cluster.TenantId != param.OrgId {
		ret.Message = "cluster is not belong to the org"
		tilog.Info(ctx, "genJwt4BuiltinUser fail", zap.Any("local_trace", trace), zap.Any("message", ret.Message))
		return ret
	}

	clusterUsername := param.Username
	if !strings.Contains(clusterUsername, serverlessTokenPrefixSeparator) && cluster.IsDevTier {
		clusterUsername = fmt.Sprintf("%s%s%s", *cluster.ServerlessToken, serverlessTokenPrefixSeparator, clusterUsername)
	}
	userEmail := fmt.Sprintf("%s@pingcap.com", clusterUsername)
	if param.TokenExpiredSeconds == 0 || param.TokenExpiredSeconds > builtinUserJwtMaxExpiredSeconds {
		ret.TokenExpiredSeconds = builtinUserJwtMaxExpiredSeconds
	}
	tokenExpiredTime := time.Second * time.Duration(ret.TokenExpiredSeconds)

	tilog.Info(ctx, "genJwt4BuiltinUser GenJwtToken param", zap.Any("local_trace", trace), zap.Any("cluster_id", param.ClusterId),
		zap.Any("username", clusterUsername), zap.Any("email", userEmail), zap.Any("token_expired_time", tokenExpiredTime))
	svc, _ := authcenter2.GetClusterUserSvc()
	jwtToken, err := svc.GenJwtToken(ctx, param.ClusterId, clusterUsername, userEmail, tokenExpiredTime)
	tilog.Info(ctx, "genJwt4BuiltinUser GenJwtToken result", zap.Any("local_trace", trace), zap.Any("err", err))
	if err != nil {
		ret.Message = "gen jwt error"
		return ret
	}

	ret.Success = true
	ret.Jwt = jwtToken
	tilog.Info(ctx, "genJwt4BuiltinUser success", zap.Any("local_trace", trace), zap.Any("ret", ret))
	return ret
}

func (s *dbJwtService) GetCloudAdminPasswd(ctx context.Context, req *central.GetCloudAdminPasswdReq) (*central.GetCloudAdminPasswdResp, error) {
	param := dbjwt.GetCloudAdminPasswdParam{
		ClusterID: req.ClusterId,
	}
	passwd, err := dbjwt.GlobalDBJwtSvc.GetCloudAdminPasswd(ctx, &param)
	if err != nil {
		return nil, err
	}

	response := &central.GetCloudAdminPasswdResp{
		Passwd:   passwd,
		BaseResp: &base.BaseResp{},
	}
	return response, nil

}

//func (s *dbJwtService) GetLimitedAdminByType(ctx context.Context, req *central.GetLimitedAdminByTypeReq) (*central.GetLimitedAdminByTypeResp, error) {
//	bm := utils.Begin(ctx, "GetLimitedAdminByType")
//	defer bm.OnExit(ctx)
//
//	if req.ClusterId == 0 {
//		return nil, errs.NewBadRequest("cluster_id is empty", nil)
//	}
//	var username string
//	if req.Type == central.LimitedAdminType_LIMITED_ADMIN_TYPE_CLOUD_SYSTEM_SETTING_ADMIN {
//		username = authcenter2.CloudSystemSettingAdminUsername
//	} else if req.Type == central.LimitedAdminType_LIMITED_ADMIN_TYPE_CLOUD_RESTRICT_USER_ADMIN {
//		username = authcenter2.CloudRestrictUserAdminUsername
//	} else {
//		return nil, errs.NewBadRequest("admin type invalid", nil)
//	}
//	var duration = time.Duration(req.TokenExpiredSeconds) * time.Second
//
//	svc, _ := authcenter2.GetClusterUserSvc()
//	clusterUserInfo, err := svc.GetClusterUser(ctx, req.ClusterId, username, duration)
//	if err != nil {
//		return nil, err
//	}
//	rsp := &central.GetLimitedAdminByTypeResp{
//		ClusterUsername: clusterUserInfo.ClusterUsername,
//		Credential:      clusterUserInfo.Credential,
//		CredentialType:  central.CredentialType_CLUSTER_USER_CREDENTIAL_TYPE_TOKEN,
//		BaseResp:        &base.BaseResp{},
//	}
//	if clusterUserInfo.CredentialType == entity.CredentialTypePassword {
//		rsp.CredentialType = central.CredentialType_CLUSTER_USER_CREDENTIAL_TYPE_PASSWORD
//	}
//
//	return rsp, nil
//
//}
