package replay

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"strings"
	"time"

	"galaxy/gateway/internal/config"

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

// RedisStore implements Store with Redis SETNX reservations over a dedicated
// key namespace.
type RedisStore struct {
	client         *redis.Client
	keyPrefix      string
	reserveTimeout time.Duration
}

// NewRedisStore constructs a Redis-backed replay store that uses client and
// applies the replay-specific namespace and timeout controls from replayCfg.
// The store does not own the client; the runtime supplies a shared
// *redis.Client.
func NewRedisStore(client *redis.Client, replayCfg config.ReplayRedisConfig) (*RedisStore, error) {
	if client == nil {
		return nil, errors.New("new redis replay store: nil redis client")
	}
	if strings.TrimSpace(replayCfg.KeyPrefix) == "" {
		return nil, errors.New("new redis replay store: replay key prefix must not be empty")
	}
	if replayCfg.ReserveTimeout <= 0 {
		return nil, errors.New("new redis replay store: reserve timeout must be positive")
	}

	return &RedisStore{
		client:         client,
		keyPrefix:      replayCfg.KeyPrefix,
		reserveTimeout: replayCfg.ReserveTimeout,
	}, nil
}

// Reserve records the authenticated deviceSessionID and requestID pair for
// ttl. It rejects duplicates while the reservation remains active.
func (s *RedisStore) Reserve(ctx context.Context, deviceSessionID string, requestID string, ttl time.Duration) error {
	if s == nil || s.client == nil {
		return errors.New("reserve replay request in redis: nil store")
	}
	if ctx == nil {
		return errors.New("reserve replay request in redis: nil context")
	}
	if strings.TrimSpace(deviceSessionID) == "" {
		return errors.New("reserve replay request in redis: empty device session id")
	}
	if strings.TrimSpace(requestID) == "" {
		return errors.New("reserve replay request in redis: empty request id")
	}
	if ttl <= 0 {
		return errors.New("reserve replay request in redis: ttl must be positive")
	}

	reserveCtx, cancel := context.WithTimeout(ctx, s.reserveTimeout)
	defer cancel()

	reserved, err := s.client.SetNX(reserveCtx, s.reservationKey(deviceSessionID, requestID), "1", ttl).Result()
	if err != nil {
		return fmt.Errorf("reserve replay request in redis: %w", err)
	}
	if !reserved {
		return fmt.Errorf("reserve replay request in redis: %w", ErrDuplicate)
	}

	return nil
}

func (s *RedisStore) reservationKey(deviceSessionID string, requestID string) string {
	return s.keyPrefix + encodeKeyComponent(deviceSessionID) + ":" + encodeKeyComponent(requestID)
}

func encodeKeyComponent(value string) string {
	return base64.RawURLEncoding.EncodeToString([]byte(value))
}

var _ Store = (*RedisStore)(nil)
