package cache

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

	"github.com/go-redis/redis/v8"

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/services/tcms/service"
	"github.com/pingcap/test-infra/services/tcms/utils"
)

const (
	tokenKeyPrefix = "pat:"
	tokenTTL       = 3 * 24 * time.Hour
)

func NewTokenService(redis *RedisCache, tokenSvc service.TokenService) *PersonalTokenService {
	return &PersonalTokenService{redis: redis, tokenSvc: tokenSvc}
}

type PersonalTokenService struct {
	redis    *RedisCache
	tokenSvc service.TokenService
}

func (s *PersonalTokenService) Validate(ctx utils.Context, t string) (*model.PersonalToken, error) {
	b, err := s.redis.Get(ctx.Request().Context(), tokenKeyPrefix+t).Bytes()
	if err != nil {
		if err != redis.Nil {
			ctx.Log().Errorf(err, "redis: get personal token error")
		}
	} else {
		info := &model.PersonalToken{}
		if err := json.Unmarshal(b, info); err != nil {
			ctx.Log().Errorf(err, "redis: unmarshal personal token error")
		} else {
			return info, nil
		}
	}

	// miss or error, fallback to db
	info, err := s.tokenSvc.Validate(ctx, t)
	if err != nil {
		return nil, err
	}

	go func() {
		if _, err := s.redis.Set(context.Background(), tokenKeyPrefix+t, utils.ToJSON(info), tokenTTL).Result(); err != nil {
			ctx.Log().Errorf(err, "redis: set personal token error")
		}
	}()
	return info, nil
}

func (s *PersonalTokenService) Delete(ctx utils.Context, userID model.ID, ids []model.ID) ([]string, error) {
	hashedTokens, err := s.tokenSvc.Delete(ctx, userID, ids)
	if err != nil {
		return nil, err
	}
	if len(hashedTokens) == 0 {
		return hashedTokens, nil
	}

	// delete from cache
	for i := range hashedTokens {
		hashedTokens[i] = tokenKeyPrefix + hashedTokens[i]
	}
	s.redis.DoubleDel(ctx, hashedTokens...)
	return hashedTokens, nil
}

func (s *PersonalTokenService) List(ctx utils.Context, userID model.ID) ([]*model.PersonalToken, error) {
	return s.tokenSvc.List(ctx, userID)
}

func (s *PersonalTokenService) Create(ctx utils.Context, t *model.PersonalToken) (string, error) {
	return s.tokenSvc.Create(ctx, t)
}

func (s *PersonalTokenService) UpdateLastUsedAt(id model.ID) error {
	return s.tokenSvc.UpdateLastUsedAt(id)
}
