require "rspec/rails/matchers/action_cable/have_broadcasted_to"

module RSpec
  module Rails
    module Matchers
      extend RSpec::Matchers::DSL

      # Namespace for various implementations of ActionCable features
      #
      # @api private
      module ActionCable
      end

      # @api public
      # Passes if a message has been sent to a stream/object inside a block.
      # May chain `at_least`, `at_most` or `exactly` to specify a number of times.
      # To specify channel from which message has been broadcasted to object use `from_channel`.
      #
      #
      # @example
      #     expect {
      #       ActionCable.server.broadcast "messages", text: 'Hi!'
      #     }.to have_broadcasted_to("messages")
      #
      #     expect {
      #       SomeChannel.broadcast_to(user)
      #     }.to have_broadcasted_to(user).from_channel(SomeChannel)
      #
      #     # Using alias
      #     expect {
      #       ActionCable.server.broadcast "messages", text: 'Hi!'
      #     }.to broadcast_to("messages")
      #
      #     expect {
      #       ActionCable.server.broadcast "messages", text: 'Hi!'
      #       ActionCable.server.broadcast "all", text: 'Hi!'
      #     }.to have_broadcasted_to("messages").exactly(:once)
      #
      #     expect {
      #       3.times { ActionCable.server.broadcast "messages", text: 'Hi!' }
      #     }.to have_broadcasted_to("messages").at_least(2).times
      #
      #     expect {
      #       ActionCable.server.broadcast "messages", text: 'Hi!'
      #     }.to have_broadcasted_to("messages").at_most(:twice)
      #
      #     expect {
      #       ActionCable.server.broadcast "messages", text: 'Hi!'
      #     }.to have_broadcasted_to("messages").with(text: 'Hi!')
      def have_broadcasted_to(target = nil)
        check_action_cable_adapter

        ActionCable::HaveBroadcastedTo.new(target, channel: described_class)
      end

      alias_matcher :broadcast_to, :have_broadcasted_to do |desc|
        desc.gsub("have broadcasted", "broadcast")
      end

    private

      # @private
      def check_action_cable_adapter
        return if ::ActionCable::SubscriptionAdapter::Test === ::ActionCable.server.pubsub

        raise StandardError, "To use ActionCable matchers set `adapter: test` in your cable.yml"
      end
    end
  end
end
