# frozen_string_literal: true

require "spec_helper"

describe Lita::Handler::Common, lita: true do
  let(:robot) { Lita::Robot.new(registry) }

  let(:handler) do
    Class.new do
      include Lita::Handler::Common

      namespace "foo"
    end
  end

  subject { handler.new(robot) }

  describe ".config" do
    it "sets configuration attributes" do
      handler.config :foo

      config = handler.configuration_builder.build

      expect(config.foo).to be_nil
      config.foo = :bar
      expect(config.foo).to eq(:bar)
    end
  end

  describe ".configuration_builder" do
    it "returns a ConfigurationBuilder object" do
      expect(handler.configuration_builder).to be_a(Lita::ConfigurationBuilder)
    end

    it "is memoized" do
      expect(handler.configuration_builder).to equal(handler.configuration_builder)
    end
  end

  describe ".namespace" do
    it "returns a snake cased namesapce for the handler based on class name" do
      handler = Class.new do
        include Lita::Handler::Common

        def self.name
          "Lita::Handlers::FooBarBaz"
        end
      end

      expect(handler.namespace).to eq("foo_bar_baz")
    end

    it "allows the namespace to be set with a simple string" do
      handler = Class.new do
        include Lita::Handler::Common

        namespace "common"
      end

      expect(handler.namespace).to eq("common")
    end

    it "allows the namespace to be set with the full path to an object as a string" do
      handler = Class.new do
        include Lita::Handler::Common

        namespace "Lita::Handler::Common"
      end

      expect(handler.namespace).to eq("common")
    end

    it "allows the namespace to be set with an object" do
      handler = Class.new do
        include Lita::Handler::Common

        namespace Lita::Handler::Common
      end

      expect(handler.namespace).to eq("common")
    end

    it "raises an exception if the handler doesn't have a name to derive the namespace from" do
      handler = Class.new { include Lita::Handler::Common }
      expect { handler.namespace }.to raise_error(RuntimeError, /must set a namespace/)
    end
  end

  describe "#config" do
    before { registry.register_handler(handler) }

    let(:handler) do
      Class.new do
        include Lita::Handler::Common

        namespace "foo_bar_baz"

        config :style, default: :new
      end
    end

    it "returns the handler's config settings" do
      expect(subject.config.style).to eq(:new)
    end
  end

  describe "#http" do
    it "returns a Faraday connection" do
      expect(subject.http).to be_a(Faraday::Connection)
    end

    it "sets a default user agent" do
      expect(subject.http.headers["User-Agent"]).to eq("Lita v#{Lita::VERSION}")
    end

    it "merges in user-supplied options" do
      connection = subject.http(headers: {
        "User-Agent" => "Foo", "X-Bar" => "Baz"
      })
      expect(connection.headers["User-Agent"]).to eq("Foo")
      expect(connection.headers["X-Bar"]).to eq("Baz")
    end

    it "passes blocks on to Faraday" do
      connection = subject.http { |builder| builder.response :logger }
      expect(connection.builder.handlers).to include(Faraday::Response::Logger)
    end
  end

  describe "#log" do
    it "returns the robot's logger" do
      expect(subject.log).to eq(robot.logger)
    end
  end

  describe "#render_template" do
    context "with the template root set" do
      before do
        handler.template_root(File.expand_path(File.join("..", "..", "..", "templates"), __FILE__))
      end

      it "renders the given template to a string" do
        expect(subject.render_template("basic")).to eq("Template rendered from a file!")
      end

      it "interpolates variables into the rendered template" do
        result = subject.render_template("interpolated", first: "Carl", last: "Pug")

        expect(result).to eq("I love Carl Pug!")
      end

      it "renders adapter-specific templates if available" do
        robot.config.robot.adapter = :irc
        expect(subject.render_template("basic")).to eq("IRC template rendered from a file!")
      end
    end

    it "raises an exception if the template root hasn't been set" do
      expect { subject.render_template("basic") }.to raise_error(Lita::MissingTemplateRootError)
    end
  end

  describe "#render_template_with_helpers" do
    before do
      handler.template_root(File.expand_path(File.join("..", "..", "..", "templates"), __FILE__))
    end

    it "extends custom helpers into the template evaluation context" do
      helpers = Module.new do
        def reverse_name(first, last)
          "#{last}, #{first}"
        end
      end

      result = subject.render_template_with_helpers(
        "helpers",
        [helpers],
        first: "Carl",
        last: "Pug",
      )

      expect(result).to eq("Pug, Carl")
    end
  end

  describe "#store" do
    it "shares data between instances" do
      subject.store[:foo] = :bar

      second_instance = handler.new(robot)

      expect(second_instance.store[:foo]).to eq(:bar)
    end
  end

  describe "timer methods" do
    let(:queue) { Queue.new }

    subject { handler.new(robot) }

    before do
      # Don't wait for sleeps while testing.
      allow_any_instance_of(Lita::Timer).to receive(:sleep)

      # Since we're not using the `:lita_handler` RSpec metadata in this test, we need to do this
      # manually or we will reach an `exit(false)` when `robot.run_concurrently` delegates to the
      # adapter and there isn't one!
      registry.register_adapter(:test, Lita::Adapters::Test)
      registry.config.robot.adapter = :test
      registry.config.adapters.test.blocking = false
    end

    describe "#after" do
      let(:handler) do
        Class.new do
          include Lita::Handler::Common

          namespace "foo"

          def after_test(queue)
            after(2) { queue.push("Waited 2 seconds!") }
          end
        end
      end

      it "triggers the block after the given number of seconds" do
        subject.after_test(queue)
        expect(queue.pop).to eq("Waited 2 seconds!")
        expect { queue.pop(true) }.to raise_error(ThreadError)
      end
    end

    describe "#every" do
      let(:handler) do
        Class.new do
          include Lita::Handler::Common

          namespace "foo"

          def every_test(queue)
            array = [1, 2, 3]

            every(2) do |timer|
              value = array.shift

              if value
                queue.push(value)
              else
                timer.stop
              end
            end
          end
        end
      end

      it "triggers the block until the timer is stopped" do
        subject.every_test(queue)
        expect(queue.pop).to eq(1)
        expect(queue.pop).to eq(2)
        expect(queue.pop).to eq(3)
        expect { queue.pop(true) }.to raise_error(ThreadError)
      end
    end

    context "with an infinite timer" do
      let(:response) { instance_double("Lita::Response") }

      let(:handler) do
        Class.new do
          include Lita::Handler::Common

          namespace "foo"

          def infinite_every_test(response)
            thread = every(5) { "Looping forever!" }
            response.reply("Replying after timer!")
            thread
          end
        end
      end

      it "doesn't block the handler's thread" do
        expect(response).to receive(:reply)
        thread = subject.infinite_every_test(response)
        thread.kill
      end
    end
  end
end
