package controller

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/loveuer/nf/nft/resp"
	"github.com/spf13/cast"
	"gorm.io/gorm"
	"strings"
	"time"
	"ultone/internal/database/cache"
	"ultone/internal/database/db"
	"ultone/internal/log"
	"ultone/internal/model"
	"ultone/internal/opt"
	"ultone/internal/tool"
)

type userController interface {
	GetUser(ctx context.Context, id uint64) (*model.User, error)
	GetUserByToken(ctx context.Context, token string) (*model.User, error)
	CacheUser(ctx context.Context, user *model.User) error
	CacheToken(ctx context.Context, token string, user *model.User) error
	RmToken(ctx context.Context, token string) error
	RmUserCache(ctx context.Context, id uint64) error
	DeleteUser(ctx context.Context, id uint64) error
}

type uc struct{}

var _ userController = (*uc)(nil)

func (u uc) GetUser(ctx context.Context, id uint64) (*model.User, error) {
	var (
		err    error
		target = new(model.User)
		key    = fmt.Sprintf("%s:user:id:%d", opt.CachePrefix, id)
		bs     []byte
	)

	if opt.EnableUserCache {
		if bs, err = cache.Client.Get(tool.Timeout(3), key); err != nil {
			log.Warn(ctx, "controller.GetUser: get user by cache key=%s err=%v", key, err)
			goto ByDB
		}

		if err = json.Unmarshal(bs, target); err != nil {
			log.Warn(ctx, "controller.GetUser: json unmarshal key=%s by=%s err=%v", key, string(bs), err)
			goto ByDB
		}

		return target, nil
	}

ByDB:
	if err = db.New(tool.Timeout(3)).
		Model(&model.User{}).
		Where("id = ?", id).
		Take(target).
		Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// tips: 公开项目需要考虑击穿处理
			return target, resp.NewError(400, "目标不存在", err, nil)
		}

		return target, resp.NewError(500, "", err, nil)
	}

	if opt.EnableUserCache {
		if err = u.CacheUser(ctx, target); err != nil {
			log.Warn(ctx, "controller.GetUser: cache user key=%s err=%v", key, err)
		}
	}

	return target, nil
}

func (u uc) GetUserByToken(ctx context.Context, token string) (*model.User, error) {
	strs := strings.Split(token, ".")
	if len(strs) != 3 {
		return nil, fmt.Errorf("controller.GetUserByToken: jwt token invalid, token=%s", token)
	}

	key := fmt.Sprintf("%s:user:token:%s", opt.CachePrefix, strs[2])
	bs, err := cache.Client.Get(tool.Timeout(3), key)
	if err != nil {
		return nil, err
	}

	log.Debug(ctx, "controller.GetUserByToken: key=%s cache bytes=%s", key, string(bs))

	userId := cast.ToUint64(string(bs))
	if userId == 0 {
		return nil, fmt.Errorf("controller.GetUserByToken: bs=%s cast to uint64 err", string(bs))
	}

	var op *model.User

	if op, err = u.GetUser(ctx, userId); err != nil {
		return nil, err
	}

	return op, nil
}

func (u uc) CacheUser(ctx context.Context, target *model.User) error {
	key := fmt.Sprintf("%s:user:id:%d", opt.CachePrefix, target.Id)
	return cache.Client.Set(tool.Timeout(3), key, target)
}

func (u uc) CacheToken(ctx context.Context, token string, user *model.User) error {
	strs := strings.Split(token, ".")
	if len(strs) != 3 {
		return fmt.Errorf("controller.CacheToken: jwt token invalid")
	}

	key := fmt.Sprintf("%s:user:token:%s", opt.CachePrefix, strs[2])
	return cache.Client.SetEx(tool.Timeout(3), key, user.Id, opt.TokenTimeout)
}

func (u uc) RmToken(ctx context.Context, token string) error {
	strs := strings.Split(token, ".")
	if len(strs) != 3 {
		return fmt.Errorf("controller.CacheToken: jwt token invalid")
	}

	key := fmt.Sprintf("%s:user:token:%s", opt.CachePrefix, strs[2])
	return cache.Client.Del(tool.Timeout(3), key)
}

func (u uc) RmUserCache(ctx context.Context, id uint64) error {
	key := fmt.Sprintf("%s:user:id:%d", opt.CachePrefix, id)
	return cache.Client.Del(tool.Timeout(3), key)
}

func (u uc) DeleteUser(ctx context.Context, id uint64) error {
	var (
		err      error
		now      = time.Now()
		username = "CONCAT(username, '@del')"
	)

	if opt.Cfg.DB.Type == "sqlite" {
		username = "username || '@del'"
	}

	if err = db.New(tool.Timeout(5)).
		Model(&model.User{}).
		Where("id = ?", id).
		Updates(map[string]any{
			"deleted_at": now.UnixMilli(),
			"username":   gorm.Expr(username),
		}).Error; err != nil {
		return resp.NewError(500, "", err, nil)
	}

	if opt.EnableUserCache {
		if err = u.RmUserCache(ctx, id); err != nil {
			log.Warn(ctx, "controller.DeleteUser: rm user=%d cache err=%v", id, err)
		}
	}

	return nil
}