# frozen_string_literal: true

require "spec_helper"

handler = Class.new do
  extend Lita::Handler::ChatRouter

  def self.name
    "Test"
  end

  route(/message/, :message)
  route(/command/, :command, command: true)
  route(/admin/, :admin, restrict_to: :admins)
  route(/error/, :error)
  route(/validate route hook/, :validate_route_hook, code_word: true)
  route(/trigger route hook/, :trigger_route_hook, custom_data: "trigger route hook")

  def message(response)
    response.reply("message")
  end

  def command(response)
    response.reply("command")
  end

  def admin(response)
    response.reply("admin")
  end

  def error(_response)
    raise
  end

  def validate_route_hook(response)
    response.reply("validate route hook")
  end

  def trigger_route_hook(response)
    response.reply(response.extensions[:custom_data])
  end

  route(/block/) do |response|
    response.reply("block")
  end
end

describe handler, lita_handler: true do
  describe ".dispatch" do
    it "routes a matching message to the supplied method" do
      send_message("message")
      expect(replies.last).to eq("message")
    end

    it "routes a matching message even if addressed to the robot" do
      send_command("message")
      expect(replies.last).to eq("message")
    end

    it "routes a command message to the supplied method" do
      send_command("command")
      expect(replies.last).to eq("command")
    end

    it "requires command routes to be addressed to the robot" do
      send_message("command")
      expect(replies).to be_empty
    end

    it "doesn't route messages that don't match anything" do
      send_message("nothing")
      expect(replies).to be_empty
    end

    it "dispatches to restricted routes if the user is in the auth group" do
      allow(robot.auth).to receive(:user_is_admin?).with(user).and_return(true)
      send_message("admin")
      expect(replies.last).to eq("admin")
    end

    it "doesn't route unauthorized users' messages to restricted routes" do
      send_message("admin")
      expect(replies).to be_empty
    end

    it "triggers a :route_authorization_failed event when a user is not authorized" do
      allow(robot).to receive(:trigger)
      expect(robot).to receive(:trigger).with(:route_authorization_failed, anything)
      send_message("admin")
    end

    it "ignores messages from itself" do
      allow(user).to receive(:name).and_return(robot.name)
      send_message("message")
      expect(replies).to be_empty
    end

    it "allows route callbacks to be provided as blocks" do
      send_message("block")
      expect(replies.last).to eq("block")
    end

    it "logs exceptions without crashing" do
      test_mode = Lita.test_mode?

      begin
        Lita.test_mode = false
        expect(robot.logger).to receive(:error).with(/Test crashed/)
        send_message("error")
      ensure
        Lita.test_mode = test_mode
      end
    end

    it "triggers a message_dispatched event" do
      expect(robot).to receive(:trigger).with(:message_received, anything)
      expect(robot).to receive(:trigger).with(:message_dispatched, anything)
      send_message("message")
    end

    it "raises exceptions in test mode" do
      expect { send_message("error") }.to raise_error(RuntimeError)
    end

    context "with a custom validate_route hook" do
      let(:hook) do
        proc do |payload|
          if payload[:route].extensions[:code_word]
            payload[:message].body.include?("code word")
          else
            true
          end
        end
      end

      before { registry.register_hook(:validate_route, hook) }

      it "matches if the hook returns true" do
        send_message("validate route hook - code word")
        expect(replies.last).to eq("validate route hook")
      end

      it "does not match if the hook returns false" do
        send_message("validate route hook")
        expect(replies).to be_empty
      end
    end

    context "with a custom trigger_route hook" do
      let(:hook) do
        proc do |payload|
          payload[:response].extensions[:custom_data] = payload[:route].extensions[:custom_data]
        end
      end

      before { registry.register_hook(:trigger_route, hook) }

      it "adds data to the response's extensions" do
        send_message("trigger route hook")
        expect(replies.last).to eq("trigger route hook")
      end
    end

    context "with a custom post_route hook" do
      let(:hook) do
        proc do |payload|
          Lita.logger.info(
            "#{payload[:response].message.body} triggered /#{payload[:route].pattern.source}/"
          )
        end
      end

      before { registry.register_hook(:post_route, hook) }

      it "executes after the message has been routed" do
        expect(Lita.logger).to receive(:info).with("message triggered /message/")

        send_message("message")
      end
    end
  end
end

handler = Class.new do
  extend Lita::Handler::ChatRouter
  extend Lita::Handler::EventRouter

  def self.name
    "Test"
  end

  route(/boom/) { |_response| 2 + nil }

  route(/one/) { |response| response.reply "got one" }
  route(/two/) { |response| response.reply "got two" }

  on :unhandled_message do |payload|
    message = payload[:message]
    robot.send_message(message.source, message.body)
  end
end

describe handler, lita_handler: true do
  it "triggers the unhandled message event if no route matches" do
    send_message("this won't match any routes")
    expect(replies.last).to eq("this won't match any routes")
  end

  it "doesn't stop checking routes when the first one matches" do
    send_message("one two")
    expect(replies.last).to eq("got two")
  end

  context "with another handler registered" do
    before do
      registry.register_handler(:test_2) do
        route(/three/) { |response| response.reply "got three" }
      end
    end

    it "doesn't stop dispatching to handlers when there is a matching route in one" do
      send_message("two three")
      expect(replies.last).to eq("got three")
    end
  end

  context "when the handler raises an exception" do
    it "calls the error handler with the exception as argument" do
      expect(registry.config.robot.error_handler).to receive(:call) do |error, *_args|
        expect(error).to be_an_instance_of(TypeError)
      end

      expect { send_message("boom!") }.to raise_error(TypeError)
    end
  end
end
