package redis

import (
	"context"
	"fmt"
	"time"

	"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
	"github.com/bsm/redislock"
	"github.com/redis/go-redis/v9"
)

type Client struct {
	client *redis.Client
	locker *redislock.Client
}

// Ensure Client implements Service interface
var _ Service = (*Client)(nil)

type Config struct {
	Host     string
	Port     int
	Password string
	DB       int
}

func New(cfg Config) (*Client, error) {
	client := redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
		Password: cfg.Password,
		DB:       cfg.DB,
	})

	// Test connection
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := client.Ping(ctx).Err(); err != nil {
		return nil, fmt.Errorf("failed to connect to redis: %w", err)
	}

	locker := redislock.New(client)

	return &Client{
		client: client,
		locker: locker,
	}, nil
}

func (c *Client) Lock(ctx context.Context, key string, ttl time.Duration) (func(context.Context), error) {
	lock, err := c.locker.Obtain(ctx, key, ttl, nil)
	if err != nil {
		if err == redislock.ErrNotObtained {
			return nil, errcodes.ErrRedisLockAlreadyLocked
		}
		return nil, errcodes.Wrap(err, "failed to obtain redis lock")
	}

	unlock := func(ctx context.Context) {
		lock.Release(ctx)
	}

	return unlock, nil
}

func (c *Client) Set(key string, value []byte, ttl time.Duration) error {
	ctx := context.Background()
	if err := c.client.Set(ctx, key, value, ttl).Err(); err != nil {
		return errcodes.Wrap(err, "failed to set redis key")
	}
	return nil
}

func (c *Client) Get(key string) ([]byte, error) {
	ctx := context.Background()
	result := c.client.Get(ctx, key)
	if result.Err() != nil {
		if result.Err() == redis.Nil {
			return nil, errcodes.ErrRedisCacheMiss
		}
		return nil, errcodes.Wrap(result.Err(), "failed to get redis key")
	}
	return result.Bytes()
}

func (c *Client) Close() error {
	return c.client.Close()
}
