package cache

import (
	"context"
	"errors"
	"strings"
	"time"

	"hyapp-admin-server/internal/config"

	"github.com/redis/go-redis/v9"
)

type Redis struct {
	client *redis.Client
	prefix string
}

type UnlockFunc func(context.Context) error

func NewRedis(ctx context.Context, cfg config.RedisConfig) (*Redis, error) {
	if !cfg.Enabled {
		return nil, nil
	}
	client := redis.NewClient(&redis.Options{
		Addr:     cfg.Addr,
		Password: cfg.Password,
		DB:       cfg.DB,
	})
	if err := client.Ping(ctx).Err(); err != nil {
		_ = client.Close()
		return nil, err
	}
	return &Redis{client: client, prefix: cfg.KeyPrefix}, nil
}

func (r *Redis) Close() error {
	if r == nil || r.client == nil {
		return nil
	}
	return r.client.Close()
}

func (r *Redis) Ping(ctx context.Context) error {
	if r == nil || r.client == nil {
		return nil
	}
	return r.client.Ping(ctx).Err()
}

func (r *Redis) TryLock(ctx context.Context, key string, owner string, ttl time.Duration) (bool, UnlockFunc, error) {
	if r == nil || r.client == nil {
		return true, func(context.Context) error { return nil }, nil
	}
	if strings.TrimSpace(owner) == "" {
		return false, nil, errors.New("lock owner is required")
	}
	redisKey := r.key(key)
	ok, err := r.client.SetNX(ctx, redisKey, owner, ttl).Result()
	if err != nil || !ok {
		return ok, nil, err
	}
	return true, func(unlockCtx context.Context) error {
		const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
end
return 0`
		return r.client.Eval(unlockCtx, script, []string{redisKey}, owner).Err()
	}, nil
}

func (r *Redis) key(key string) string {
	if r.prefix == "" {
		return key
	}
	return r.prefix + strings.TrimPrefix(key, r.prefix)
}
