module Lita
  # A room in the chat service. Persisted in Redis.
  # @since 4.4.0
  class Room
    class << self
      # Creates a new room with the given ID, or merges and saves supplied
      # metadata to a room with the given ID.
      # @param id [Integer, String] A unique identifier for the room.
      # @param metadata [Hash] An optional hash of metadata about the room.
      # @option metadata [String] name (id) The display name of the room.
      # @return [Lita::Room] The room.
      def create_or_update(id, metadata = {})
        existing_room = find_by_id(id)
        metadata = Util.stringify_keys(metadata)
        metadata = existing_room.metadata.merge(metadata) if existing_room
        room = new(id, metadata)
        room.save
        room
      end

      # Finds a room by ID.
      # @param id [Integer, String] The room's unique ID.
      # @return [Lita::Room, nil] The room or +nil+ if no such room is known.
      def find_by_id(id)
        metadata = redis.hgetall("id:#{id}")
        new(id, metadata) if metadata.key?("name")
      end

      # Finds a room by display name.
      # @param name [String] The room's name.
      # @return [Lita::Room, nil] The room or +nil+ if no such room is known.
      def find_by_name(name)
        id = redis.get("name:#{name}")
        find_by_id(id) if id
      end

      # Finds a room by ID or name
      # @param identifier [Integer, String] The room's ID or name.
      # @return [Lita::Room, nil] The room or +nil+ if no room was found.
      def fuzzy_find(identifier)
        find_by_id(identifier) || find_by_name(identifier)
      end

      # The +Redis::Namespace+ for room persistence.
      # @return [Redis::Namespace] The Redis connection.
      def redis
        @redis ||= Redis::Namespace.new("rooms", redis: Lita.redis)
      end
    end

    # The room's unique ID.
    # @return [String] The room's ID.
    attr_reader :id

    # A hash of arbitrary metadata about the room.
    # @return [Hash] The room's metadata.
    attr_reader :metadata

    # The room's name as displayed in a standard user interface.
    # @return [String] The room's name.
    attr_reader :name

    # @param id [Integer, String] The room's unique ID.
    # @param metadata [Hash] Arbitrary room metadata.
    # @option metadata [String] name (id) The room's display name.
    def initialize(id, metadata = {})
      @id = id.to_s
      @metadata = Util.stringify_keys(metadata)
      @name = @metadata["name"] || @id
    end

    # Compares the room against another room object to determine equality. Rooms
    # are considered equal if they have the same ID.
    # @param other (Lita::Room) The room to compare against.
    # @return [Boolean] True if rooms are equal, false otherwise.
    def ==(other)
      other.respond_to?(:id) && id == other.id
    end
    alias_method :eql?, :==

    # Generates a +Fixnum+ hash value for this user object. Implemented to support equality.
    # @return [Fixnum] The hash value.
    # @see Object#hash
    def hash
      id.hash
    end

    # Saves the room record to Redis, overwriting any previous data for the current ID.
    # @return [void]
    def save
      ensure_name_metadata_set

      redis.pipelined do
        redis.hmset("id:#{id}", *metadata.to_a.flatten)
        redis.set("name:#{name}", id)
      end
    end

    private

    # Ensure the room's metadata contains its name, to ensure their Redis hash contains at least
    # one value. It's not possible to store an empty hash key in Redis.
    def ensure_name_metadata_set
      room_name = metadata.delete("name")
      metadata["name"] = room_name || id
    end

    # The Redis connection for room persistence.
    def redis
      self.class.redis
    end
  end
end
