# frozen_string_literal: true

require 'redis'

module Cloudtasker
  # A wrapper with helper methods for redis
  class RedisClient
    # Suffix added to cache keys when locking them
    LOCK_KEY_PREFIX = 'cloudtasker/lock'

    #
    # Return the underlying redis client.
    #
    # @return [Redis] The redis client.
    #
    def client
      @client ||= Redis.new(Cloudtasker.config.redis || {})
    end

    #
    # Get a cache entry and parse it as JSON.
    #
    # @param [String, Symbol] key The cache key to fetch.
    #
    # @return [Hash, Array] The content of the cache key, parsed as JSON.
    #
    def fetch(key)
      return nil unless (val = client.get(key.to_s))

      JSON.parse(val, symbolize_names: true)
    rescue JSON::ParserError
      nil
    end

    #
    # Write a cache entry as JSON.
    #
    # @param [String, Symbol] key The cache key to write.
    # @param [Hash, Array] content The content to write.
    #
    # @return [String] Redis response code.
    #
    def write(key, content)
      client.set(key.to_s, content.to_json)
    end

    #
    # Acquire a lock on a cache entry.
    #
    # @example
    #   redis = RedisClient.new
    #   redis.with_lock('foo')
    #     content = redis.fetch('foo')
    #     redis.set(content.merge(bar: 'bar).to_json)
    #   end
    #
    # @param [String] cache_key The cache key to access.
    #
    def with_lock(cache_key)
      return nil unless cache_key

      # Wait to acquire lock
      lock_key = [LOCK_KEY_PREFIX, cache_key].join('/')
      true until client.setnx(lock_key, true)

      # yield content
      yield
    ensure
      client.del(lock_key)
    end

    #
    # Clear all redis keys
    #
    # @return [Integer] The number of keys deleted
    #
    def clear
      all_keys = keys
      return 0 if all_keys.empty?

      # Delete all keys
      del(*all_keys)
    end

    #
    # Return all keys matching the provided patterns.
    #
    # @param [String] pattern A redis compatible pattern.
    #
    # @return [Array<String>] The list of matching keys
    #
    def search(pattern)
      # Initialize loop variables
      cursor = nil
      list = []

      # Scan and capture matching keys
      while cursor != 0
        scan = client.scan(cursor || 0, match: pattern)
        list += scan[1]
        cursor = scan[0].to_i
      end

      list
    end

    #
    # Delegate all methods to the redis client.
    #
    # @param [String, Symbol] name The method to delegate.
    # @param [Array<any>] *args The list of method arguments.
    # @param [Proc] &block Block passed to the method.
    #
    # @return [Any] The method return value
    #
    def method_missing(name, *args, &block)
      if client.respond_to?(name)
        client.send(name, *args, &block)
      else
        super
      end
    end

    #
    # Check if the class respond to a certain method.
    #
    # @param [String, Symbol] name The name of the method.
    # @param [Boolean] include_private Whether to check private methods or not. Default to false.
    #
    # @return [Boolean] Return true if the class respond to this method.
    #
    def respond_to_missing?(name, include_private = false)
      client.respond_to?(name) || super
    end
  end
end