package openapi

import (
	"bytes"
	"context"
	"fmt"
	"sort"

	"github.com/tidbcloud/account/internel/svc/comsvc"

	"github.com/tidbcloud/account/central/utils"
	openapiv1veta1 "github.com/tidbcloud/account/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/account/internel/dao/entity"
	apikey2 "github.com/tidbcloud/account/internel/svc/apikey"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
)

type ApiKeyService struct {
	openapiv1veta1.UnimplementedApiKeyServiceServer
}

func (s *ApiKeyService) CreateApiKey(ctx context.Context, req *openapiv1veta1.CreateApiKeyRequest) (*openapiv1veta1.ApiKey, error) {
	tilog.Info(ctx, "CreateApiKey request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "CreateApiKey getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	if req.ApiKey == nil {
		return nil, errs.NewBadRequest("api_key is nil", nil)
	}
	err = checkApiKeyDisplayNameRole(ctx, req.ApiKey.DisplayName, req.ApiKey.Role)
	if err != nil {
		tilog.Error(ctx, "CreateApiKey checkApiKeyDisplayNameRole error", zap.Any("err", err))
		return nil, err
	}

	param := &apikey2.CreateOneConsoleApiKeyParam{
		Attrs: &apikey2.ApiKeyAttr{
			Source:      "account_openapi",
			Type:        entity.ApiKeyTypeNormalKey,
			Description: req.ApiKey.DisplayName,
		},
		AssignRoleParam: &comsvc.AssignIdentRolesParam{
			OrgID: authOrgId,
			ORole: &req.ApiKey.Role,
		},
	}
	operator := getOperator(identInfo)

	keySvc, _ := apikey2.GetApiKeySvc()
	key, err := keySvc.CreateOneConsoleApiKey(ctx, param, operator)
	if err != nil {
		tilog.Error(ctx, "CreateApiKey CreateApiKey error", zap.Any("param", param), zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}
	rsp := convOneConsoleApiKey2OpenApiKey(key, authOrgId, req.ApiKey.Role, false)
	tilog.Info(ctx, "CreateApiKey response", zap.Any("rsp", rsp))
	return rsp, nil
}

func (s *ApiKeyService) GetApiKey(ctx context.Context, req *openapiv1veta1.GetApiKeyRequest) (*openapiv1veta1.ApiKey, error) {
	tilog.Info(ctx, "GetApiKey request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "GetApiKey getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	accessKey := req.AccessKey
	svc, _ := apikey2.GetApiKeySvc()
	key, err := svc.GetApiKeyByAccessKey(ctx, accessKey)
	if err != nil {
		tilog.Error(ctx, "GetApiKey GetApiKeyByAccessKey error", zap.Any("err", err))
		return nil, errs.NewInternalUnknown("internal server error", err)
	}
	if key == nil {
		tilog.Warn(ctx, "GetApiKey GetApiKeyByAccessKey return nil")
		return nil, errs.NewNotFound("api_key not found", err)
	}
	accessOrgId := getApiKeyRelateOrgId(key.ResourceInfos)
	if accessOrgId != authOrgId {
		tilog.Warn(ctx, "GetApiKey accessOrgId is not equal to authOrgId", zap.Any("accessOrgId", accessOrgId), zap.Any("authOrgId", authOrgId))
		return nil, errs.NewBadRequest("access other org's api_key is now allowed", err)
	}
	rbacRole := getApiKeyRole(key.ResourceInfos, accessOrgId)
	if rbacRole == "" {
		tilog.Error(ctx, "GetApiKey getApiKeyRole empty")
		return nil, errs.NewBadRequest("not found key in org", err)
	}

	keyRet := convEntity2OpenApiKey(key, accessOrgId, rbacRole, true)
	tilog.Info(ctx, "GetApiKey response", zap.Any("rsp", keyRet))
	return keyRet, nil

}
func (s *ApiKeyService) ListApiKeys(ctx context.Context, req *openapiv1veta1.ListApiKeysRequest) (*openapiv1veta1.ListApiKeysResponse, error) {
	tilog.Info(ctx, "ListApiKeys request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "ListApiKeys getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID

	param := apikey2.ListKeysByResourceIDParam{
		ScopeType:  entity.RoleBindingScopeOrg.String(),
		ResourceId: authOrgId,
	}
	svc, _ := apikey2.GetApiKeySvc()
	keys, err := svc.ListApiKeysByResourceID(ctx, &param)
	if err != nil {
		tilog.Error(ctx, "ListApiKeys ListApiKeysByResourceID error", zap.Any("orgId", authOrgId), zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}

	totalSize, pageApiKeys, nextPageToken, err := getPageApiKeys(keys.Items, req.PageToken, req.Skip, req.PageSize)
	if err != nil {
		tilog.Error(ctx, "ListApiKeys getPageApiKeys error", zap.Any("keys", keys.Items), zap.Any("err", err))
		return nil, err
	}
	var returnApiKeys []*openapiv1veta1.ApiKey
	for _, k := range pageApiKeys {
		rbacRole := getApiKeyRole(k.ResourceInfos, authOrgId)
		if rbacRole == "" {
			tilog.Warn(ctx, "ListApiKeys getApiKeyRole empty, ignore", zap.Any("key", k.AccessKey), zap.Any("orgId", authOrgId))
			continue
		}
		rk := convEntity2OpenApiKey(k, authOrgId, rbacRole, true)
		returnApiKeys = append(returnApiKeys, rk)
	}

	rsp := &openapiv1veta1.ListApiKeysResponse{
		ApiKeys:       returnApiKeys,
		NextPageToken: nextPageToken,
		TotalSize:     totalSize,
	}
	tilog.Info(ctx, "ListApiKeys response", zap.Any("rsp", rsp))
	return rsp, nil
}

func (s *ApiKeyService) ListApiKeyRoles(ctx context.Context, req *emptypb.Empty) (*openapiv1veta1.ListApiKeyRolesResponse, error) {
	tilog.Info(ctx, "ListApiKeyRoles request")
	var roles []string
	for r := range entity.ValidUserOrgRbacRoles {
		roles = append(roles, r)
	}
	rsp := &openapiv1veta1.ListApiKeyRolesResponse{
		OrgRoles: roles,
	}

	tilog.Info(ctx, "ListApiKeys response", zap.Any("rsp", rsp))
	return rsp, nil
}

func (s *ApiKeyService) DeleteApiKey(ctx context.Context, req *openapiv1veta1.DeleteApiKeyRequest) (*emptypb.Empty, error) {
	tilog.Info(ctx, "DeleteApiKey request", zap.Any("req", req))
	identInfo, err := getAuthnIdentityAndCheckOrgId(ctx)
	if err != nil {
		tilog.Error(ctx, "DeleteApiKey getAuthnIdentityAndCheckOrgId error", zap.Any("err", err))
		return nil, err
	}
	authOrgId := identInfo.OrgID
	accessKey := req.AccessKey
	if accessKey == identInfo.IdentityVal {
		tilog.Info(ctx, "DeleteApiKey can not delete itself", zap.Any("accessKey", accessKey))
		return nil, errs.NewBadRequest("the key can not delete itself", nil)
	}

	svc, _ := apikey2.GetApiKeySvc()
	key, err := svc.GetApiKeyByAccessKey(ctx, accessKey)
	if err != nil {
		tilog.Error(ctx, "DeleteApiKey GetApiKeyByAccessKey error", zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}
	if key == nil {
		tilog.Warn(ctx, "DeleteApiKey GetApiKeyByAccessKey return nil")
		return nil, errs.NewNotFound("api_key not found", err)
	}
	accessOrgId := getApiKeyRelateOrgId(key.ResourceInfos)
	if accessOrgId != authOrgId {
		tilog.Warn(ctx, "DeleteApiKey accessOrgId is not equal to authOrgId", zap.Any("accessOrgId", accessOrgId), zap.Any("authOrgId", authOrgId))
		return nil, errs.NewBadRequest("operate other org's api_key is now allowed", err)
	}

	operator := getOperator(identInfo)
	param := apikey2.DeleteApiKeyParam{
		ApiKeyId: key.Id,
	}
	err = svc.DeleteApiKey(ctx, &param, operator)
	if err != nil {
		tilog.Error(ctx, "DeleteApiKey DeleteApiKey error", zap.Any("param", param), zap.Any("err", err))
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}
	tilog.Info(ctx, "DeleteApiKey success", zap.Any("req", req))
	return &emptypb.Empty{}, nil
}

func getPageApiKeys(items []*apikey2.ApiKey, pageToken string, skip int32, pageSize int32) (int32, []*apikey2.ApiKey, string, error) {
	sort.Slice(items, func(i, j int) bool {
		it := items[i]
		jt := items[j]
		if it.CreatedAt.Equal(*jt.CreatedAt) {
			return items[i].Id > items[j].Id
		}
		return it.CreatedAt.Unix() > jt.CreatedAt.Unix()
	})
	totalSize, pageItems, nextPageToken, err := utils.GetPageItems(items, pageToken, int(skip), int(pageSize))
	if err != nil {
		return 0, nil, "", err
	}
	return int32(totalSize), pageItems.([]*apikey2.ApiKey), nextPageToken, nil
}

func checkApiKeyDisplayNameRole(ctx context.Context, displayName string, role string) error {
	if displayName == "" {
		return errs.NewBadRequest("display_name is blank", nil)
	}
	if len(displayName) > 500 {
		return errs.NewBadRequest("display_name's length exceeds the maximum length of 500 characters", nil)
	}
	if !apikey2.IsValidApiKeyRole(entity.RoleBindingScopeOrg.String(), role) {
		return errs.NewBadRequest("role is invalid", nil)
	}
	return nil
}

func convOneConsoleApiKey2OpenApiKey(key *apikey2.OneConsoleApiKey, orgId uint64, role string, maskEncSecret bool) *openapiv1veta1.ApiKey {
	secretKey := key.SecretKey
	if maskEncSecret {
		// secret key is encrypt
		decryptKey, _ := apikey2.GetKeyUtil().DecryptKey(secretKey)
		secretKey = maskSecretKey(decryptKey)
	}
	if role == entity.RoleApiKeyOrgOwner {
		role = entity.RoleOrgOwner
	}
	createTime := ""
	if key.CreatedAt != nil && !key.CreatedAt.IsZero() {
		createTime = formatTime(*key.CreatedAt)
	}
	updateTime := ""
	if key.UpdatedAt != nil && !key.UpdatedAt.IsZero() {
		updateTime = formatTime(*key.UpdatedAt)
	}

	return &openapiv1veta1.ApiKey{
		Name:        fmt.Sprintf("orgs/%d/apiKeys/%d", orgId, key.Id),
		AccessKey:   key.AccessKey,
		SecretKey:   secretKey,
		DisplayName: key.Description,
		Role:        role,
		Creator:     key.CreatedBy,
		CreateTime:  createTime,
		UpdateTime:  updateTime,
	}
}

func convEntity2OpenApiKey(key *apikey2.ApiKey, orgId uint64, role string, maskEncSecret bool) *openapiv1veta1.ApiKey {
	secretKey := key.SecretKey
	if maskEncSecret {
		// secret key is encrypt
		decryptKey, _ := apikey2.GetKeyUtil().DecryptKey(secretKey)
		secretKey = maskSecretKey(decryptKey)
	}
	if role == entity.RoleApiKeyOrgOwner {
		role = entity.RoleOrgOwner
	}
	createTime := ""
	if key.CreatedAt != nil && !key.CreatedAt.IsZero() {
		createTime = formatTime(*key.CreatedAt)
	}
	updateTime := ""
	if key.UpdatedAt != nil && !key.UpdatedAt.IsZero() {
		updateTime = formatTime(*key.UpdatedAt)
	}

	return &openapiv1veta1.ApiKey{
		Name:        fmt.Sprintf("orgs/%d/apiKeys/%d", orgId, key.Id),
		AccessKey:   key.AccessKey,
		SecretKey:   secretKey,
		DisplayName: key.Description,
		Role:        role,
		Creator:     key.CreatedBy,
		CreateTime:  createTime,
		UpdateTime:  updateTime,
	}
}

func maskSecretKey(secret string) string {
	l := len(secret)
	if l <= 4 {
		return secret
	}
	var buffer bytes.Buffer
	for i := 0; i < l-4; i++ {
		buffer.WriteString("*")
	}
	buffer.WriteString(secret[l-4 : l])

	return buffer.String()
}

func getApiKeyRelateOrgId(resourceInfos []*apikey2.ApiKeyResourceInfo) uint64 {
	if len(resourceInfos) == 0 {
		return 0
	}
	for _, r := range resourceInfos {
		if r.ScopeType == entity.RoleBindingScopeOrg.String() {
			return r.ResourceId
		}
	}
	return 0
}

func getApiKeyRole(resourceInfos []*apikey2.ApiKeyResourceInfo, orgId uint64) string {
	if len(resourceInfos) == 0 {
		return ""
	}
	for _, r := range resourceInfos {
		if r.ScopeType == entity.RoleBindingScopeOrg.String() && r.ResourceId == orgId {
			return r.Role
		}
	}
	return ""
}

func getAuthnIdentityAndCheckOrgId(ctx context.Context) (*auth.AuthnIdentityInfo, error) {
	identInfo, err := auth.GetIdentityInfoFromGrpcContext(ctx)
	if err != nil {
		return nil, errs.NewInternalUnknown(err.Error(), err)
	}
	authOrgId := identInfo.OrgID
	if authOrgId == 0 {
		return nil, errs.NewBadRequest("not found org_id in identity info", err)
	}
	return identInfo, nil
}
