package apikey

import (
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"sync"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/google/uuid"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	"github.com/tidbcloud/pkgv2/util"
)

const (
	//openApiAkLen = 8
	dataApiAkLen = 65
)

var (
	keyHelperInitOnce sync.Once
	keyHelper         *KeyUtil
)

type KeyUtil struct {
	Encrypter *encrypt.AESCrypt
}

func NewKeyUtil() *KeyUtil {
	gatewayConfig := config.Global().OpenAPIGatewayConfig
	encrypter, err := encrypt.NewAESCrypt([]byte(gatewayConfig.OpenAPISecretKey))
	if err != nil {
		panic(fmt.Sprintf("NewAESCrypt err: %+v", err))
	}

	return &KeyUtil{
		Encrypter: encrypter,
	}
}

func GetKeyUtil() *KeyUtil {
	keyHelperInitOnce.Do(func() {
		keyHelper = NewKeyUtil()
	})
	return keyHelper
}

func (h *KeyUtil) GenAccessSecretKey(ctx context.Context) (ak, sk string, err error) {
	return OpenApiGenKeyFunc(ctx)
}

var GenKeyFuncMap = map[ApiKeyType]GenApiKeyFunc{
	ApiKeyTypeOpenAPI: OpenApiGenKeyFunc,
	ApiKeyTypeDataAPI: DataApiGenKeyFunc,
}

type GenApiKeyFunc func(ctx context.Context) (string, string, error)

func OpenApiGenKeyFunc(ctx context.Context) (string, string, error) {
	accessKey := GetKeyGenerator().NextKey()
	tilog.Info(ctx, "gen access key", zap.Any("key", accessKey))
	secretKey := uuid.NewString()
	return accessKey, secretKey, nil
}

func DataApiGenKeyFunc(_ context.Context) (string, string, error) {
	accessKey := util.RandString(dataApiAkLen)
	return accessKey, accessKey, nil

}

func (h *KeyUtil) EncryptKey(rawKey string) string {
	iv := config.Global().OpenAPIGatewayConfig.OpenAPISecretIv
	crypted := h.Encrypter.EncryptWithIv([]byte(rawKey), []byte(iv))
	return hex.EncodeToString(crypted)
}

func (h *KeyUtil) DecryptKey(decryptKey string) (string, error) {
	iv := config.Global().OpenAPIGatewayConfig.OpenAPISecretIv
	crypted, err := hex.DecodeString(decryptKey)
	if err != nil {
		return "", errors.New("failed to decode secret key as hex")
	}
	privateKey := h.Encrypter.DecryptWithIv(crypted, []byte(iv))
	return string(privateKey), nil
}
