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 (
	userKeyPrefix = "user:"
	userTTL       = 3 * 24 * time.Hour
)

func NewUserService(redis *RedisCache, userSvc service.UserService) *userService {
	return &userService{redis: redis, userSvc: userSvc}
}

type userService struct {
	redis   *RedisCache
	userSvc service.UserService
}

func (s *userService) Get(ctx utils.Context, id model.ID) (*model.User, error) {
	key := userKeyPrefix + id.String()
	b, err := s.redis.Get(ctx.Request().Context(), key).Bytes()
	if err != nil {
		if err != redis.Nil {
			ctx.Log().Errorf(err, "redis: get user error")
		}
	} else {
		info := &model.User{}
		if err := json.Unmarshal(b, info); err != nil {
			ctx.Log().Errorf(err, "redis: unmarshal user error")
		} else {
			return info, nil
		}
	}

	u, err := s.userSvc.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	go func() {
		if _, err := s.redis.Set(context.Background(), key, utils.ToJSON(u), userTTL).Result(); err != nil {
			ctx.Log().Errorf(err, "redis: set user error")
		}
	}()
	return u, nil
}

func (s *userService) GetByEmail(ctx utils.Context, email string) (*model.User, error) {
	key := userKeyPrefix + email
	b, err := s.redis.Get(ctx.Request().Context(), key).Bytes()
	if err != nil {
		if err != redis.Nil {
			ctx.Log().Errorf(err, "redis: get user error")
		}
	} else {
		info := &model.User{}
		if err := json.Unmarshal(b, info); err != nil {
			ctx.Log().Errorf(err, "redis: unmarshal user error")
		} else {
			return info, nil
		}
	}

	u, err := s.userSvc.GetByEmail(ctx, email)
	if err != nil {
		return nil, err
	}

	go func() {
		if _, err := s.redis.Set(context.Background(), key, utils.ToJSON(u), userTTL).Result(); err != nil {
			ctx.Log().Errorf(err, "redis: set user error")
		}
	}()
	return u, nil
}

func (s *userService) Update(ctx utils.Context, user *model.User) (*model.User, error) {
	u, err := s.userSvc.Update(ctx, user)
	if err != nil {
		return nil, err
	}

	keyID := userKeyPrefix + u.ID.String()
	keyEmail := userKeyPrefix + u.Email
	s.redis.DoubleDel(ctx, keyID, keyEmail)

	return u, nil
}

func (s *userService) AutoComplete(ctx utils.Context, email string, count int) ([]*model.User, error) {
	return s.userSvc.AutoComplete(ctx, email, count)
}

// ListBasicInfo returns users' (id, email, name, avatar).
func (s *userService) ListBasicInfo(ctx utils.Context, ids []model.ID, emails []string) ([]*model.User, error) {
	return s.userSvc.ListBasicInfo(ctx, ids, emails)
}

func (s *userService) Create(ctx utils.Context, user *model.User) (*model.User, error) {
	return s.userSvc.Create(ctx, user)
}
