package apikey

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	rawredis "github.com/go-redis/redis/v8"
	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/commons/redis"
	"github.com/tidbcloud/account/internel/dao/entity"
	"go.uber.org/zap"
)

const (
	akCacheKeyFormat = "account_api_key_ak_%s"
	idCacheKeyFormat = "account_api_key_id_%d"

	notExist      = "0"
	cacheDuration = time.Minute * 120
)

func GetApiKeyEntWithCache(ctx context.Context, id uint64, ak string) (*entity.ApiKey, error) {
	if id == 0 && ak == "" {
		return nil, errs.NewBadRequest("invalid param", nil)
	}
	var key string
	if id != 0 {
		key = fmt.Sprintf(idCacheKeyFormat, id)
	} else {
		key = fmt.Sprintf(akCacheKeyFormat, ak)
	}
	client := redis.GetClient()
	val, err := client.Get(ctx, key).Result()
	if err != nil {
		if err == rawredis.Nil {
			val = ""
		} else {
			return nil, errs.NewInternalRedis("get key from redis error", err)
		}
	}
	// confirm not exit
	if val == notExist {
		tilog.Info(ctx, "GetApiKeyEntWithCache hit no exist cache", zap.Any("id", id), zap.Any("ak", ak))
		return nil, nil
	}
	// confirm exist
	if val != "" {
		tilog.Info(ctx, "GetApiKeyEntWithCache hit cache", zap.Any("id", id), zap.Any("ak", ak))
		var apiKey entity.ApiKey
		err = json.Unmarshal([]byte(val), &apiKey)
		if err != nil {
			return nil, err
		}
		return &apiKey, nil

	}
	// get from db and cache
	tilog.Info(ctx, "GetApiKeyEntWithCache not hit cache and load from db", zap.Any("id", id), zap.Any("ak", ak))
	var keyEnt *entity.ApiKey
	if id != 0 {
		keyEnt, err = dao.GetDaoHub().ApiKey.GetApiKeyByID(ctx, database.DBConn.DB, id)
	} else {
		keyEnt, err = dao.GetDaoHub().ApiKey.GetApiKeyByAk(ctx, database.DBConn.DB, ak)
	}
	if err != nil {
		return nil, errs.NewInternalDB("get key from db error", err)
	}
	if keyEnt == nil {
		client.Set(ctx, key, notExist, cacheDuration)
	} else {
		bytes, _ := json.Marshal(keyEnt)
		val = string(bytes)
		client.Set(ctx, key, val, cacheDuration)

		var otherKey string
		if id != 0 {
			otherKey = fmt.Sprintf(akCacheKeyFormat, keyEnt.AccessKey)
		} else {
			otherKey = fmt.Sprintf(idCacheKeyFormat, keyEnt.ID)
		}
		client.Set(ctx, otherKey, val, cacheDuration)
	}

	return keyEnt, nil
}

func DeleteApiKeyEntFromCache(ctx context.Context, id uint64, ak string) error {
	client := redis.GetClient()
	key := fmt.Sprintf(idCacheKeyFormat, id)
	okey := fmt.Sprintf(akCacheKeyFormat, ak)
	_, err := client.Del(ctx, key, okey).Result()
	return err
}
