package ddbtoredis

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/redis/go-redis/v9"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const keyFormat = "dbaas:account:api_keys:%s"

type APIKey struct {
	PrivateKey   string `json:"private_key"`
	RateLimitRpm int    `json:"rate_limit_rpm"`
	ExpiredTs    int    `json:"expired_ts"`
}

type RedisReaderWriter interface {
	SetAPIKey(ctx context.Context, publicKey, encryptedPrivateKey string, rateLimitRpm int, expiredTs int) error
	DeleteAPIKey(ctx context.Context, publicKey string) error
	ListAPIKeys(ctx context.Context) (map[string]APIKey, error)
}

func NewRedisReaderWriter(address, password string) RedisReaderWriter {
	client := redis.NewClient(&redis.Options{
		Addr:      address,
		Password:  password,
		TLSConfig: &tls.Config{},
	})
	return &redisReaderWriter{client: client}
}

type redisReaderWriter struct {
	client *redis.Client
}

func (r *redisReaderWriter) SetAPIKey(ctx context.Context, publicKey, encryptedPrivateKey string, rateLimitRpm int, expiredTs int) error {
	tilog.Info(ctx, "SetAPIKey", zap.String("publicKey", publicKey))
	key := fmt.Sprintf(keyFormat, publicKey)
	apiKey := APIKey{PrivateKey: encryptedPrivateKey, RateLimitRpm: rateLimitRpm, ExpiredTs: expiredTs}
	value, _ := json.Marshal(apiKey)
	_, err := r.client.Set(ctx, key, value, 0).Result()
	return err
}

func (r *redisReaderWriter) DeleteAPIKey(ctx context.Context, publicKey string) error {
	tilog.Info(ctx, "DeleteAPIKey", zap.String("publicKey", publicKey))
	key := fmt.Sprintf(keyFormat, publicKey)
	_, err := r.client.Del(ctx, key).Result()
	return err
}

func (r *redisReaderWriter) ListAPIKeys(ctx context.Context) (map[string]APIKey, error) {
	var cursor uint64
	var batch int64 = 10
	result := make(map[string]APIKey)
	size := 0
	for {
		keys, newCursor, err := r.client.Scan(ctx, cursor, fmt.Sprintf(keyFormat, "*"), batch).Result()
		if err != nil {
			return nil, err
		}
		for _, key := range keys {
			pk, err := r.extractPublicKey(key)
			if err != nil {
				tilog.Warn(ctx, "extractPublicKey failed", zap.Error(err))
				continue
			}
			value, err := r.client.Get(ctx, key).Result()
			if err != nil {
				return nil, err
			}
			var apiKey APIKey
			if err = json.Unmarshal([]byte(value), &apiKey); err != nil {
				return nil, err
			}
			result[pk] = apiKey
			size++
		}
		if newCursor == 0 {
			break
		}
		cursor = newCursor
	}
	tilog.Info(ctx, "ListAPIKeys success", zap.Int("size", size))
	return result, nil
}

func (r *redisReaderWriter) extractPublicKey(redisKey string) (string, error) {
	if !strings.HasPrefix(redisKey, fmt.Sprintf(keyFormat, "")) {
		return "", fmt.Errorf("invalid redis key: %s", redisKey)
	}
	parts := strings.Split(redisKey, ":")
	if len(parts) != 4 {
		return "", fmt.Errorf("invalid redis key: %s", redisKey)
	}
	return parts[3], nil
}
