# encoding: utf-8

require 'spec_helper'

describe Adhearsion::Rayo::Initializer do

  def reset_default_config
    Adhearsion.config.core do |config|
      config.type               = :xmpp
      config.username           = "usera@127.0.0.1"
      config.password           = "1"
      config.host               = nil
      config.port               = nil
      config.certs_directory    = nil
      config.root_domain        = nil
      config.connection_timeout = 60
      config.reconnect_attempts = 1.0/0.0
      config.reconnect_timer    = 5
    end
  end

  def initialize_rayo(options = {})
    reset_default_config
    allow(described_class).to receive(:connect)
    Adhearsion.config.core do |config|
      config.type               = options[:type] if options.has_key?(:type)
      config.username           = options[:username] if options.has_key?(:username)
      config.password           = options[:password] if options.has_key?(:password)
      config.host               = options[:host] if options.has_key?(:host)
      config.port               = options[:port] if options.has_key?(:port)
      config.certs_directory    = options[:certs_directory] if options.has_key?(:certs_directory)
      config.root_domain        = options[:root_domain] if options.has_key?(:root_domain)
      config.connection_timeout = options[:connection_timeout] if options.has_key?(:connection_timeout)
      config.reconnect_attempts = options[:reconnect_attempts] if options.has_key?(:reconnect_attempts)
      config.reconnect_timer    = options[:reconnect_timer] if options.has_key?(:reconnect_timer)
    end

    described_class.init
    Adhearsion.config.core
  end

  let(:call_id)     { rand.to_s }
  let(:offer)       { Adhearsion::Event::Offer.new :target_call_id => call_id }
  let(:mock_call)   { Adhearsion::Call.new }
  let(:mock_client) { double 'Client' }

  before do
    allow(mock_call).to receive_messages :id => call_id
    mock_client.as_null_object
    allow(mock_client).to receive_messages :event_handler= => true
    Adhearsion::Events.refresh!
    allow(Adhearsion::Process).to receive_messages :fqdn => 'hostname'
    allow(::Process).to receive_messages :pid => 1234
  end

  describe "starts the client with the default values" do
    subject { initialize_rayo }

    it "should set properly the username value" do
      expect(subject.username).to eq('usera@127.0.0.1')
    end

    it "should set properly the password value" do
      expect(subject.password).to eq('1')
    end

    it "should set properly the host value" do
      expect(subject.host).to be_nil
    end

    it "should set properly the port value" do
      expect(subject.port).to be_nil
    end

    it "should set properly the certs_directory value" do
      expect(subject.certs_directory).to be_nil
    end

    it "should set properly the root_domain value" do
      expect(subject.root_domain).to be_nil
    end

    it "should properly set the reconnect_attempts value" do
      expect(subject.reconnect_attempts).to eq(1.0/0.0)
    end

    it "should properly set the reconnect_timer value" do
      expect(subject.reconnect_timer).to eq(5)
    end
  end

  it "starts the client with the correct resource" do
    username = "usera@127.0.0.1/hostname-1234"

    expect(Adhearsion::Rayo::Connection::XMPP).to receive(:new).once.with(hash_including :username => username).and_return mock_client
    initialize_rayo
  end

  context "when the fqdn is not available" do
    it "should use the local hostname instead" do
      allow(Adhearsion::Process).to receive(:fqdn).and_raise SocketError
      allow(Socket).to receive(:gethostname).and_return 'local_hostname'

      username = "usera@127.0.0.1/local_hostname-1234"

      expect(Adhearsion::Rayo::Connection::XMPP).to receive(:new).once.with(hash_including :username => username).and_return mock_client
      initialize_rayo
    end
  end

  it "starts the client with any overridden settings" do
    expect(Adhearsion::Rayo::Connection::XMPP).to receive(:new).once.with(username: 'userb@127.0.0.1/foo', password: '123', host: 'foo.bar.com', port: 200, certs: '/foo/bar', connection_timeout: 20, root_domain: 'foo.com').and_return mock_client
    initialize_rayo username: 'userb@127.0.0.1/foo', password: '123', host: 'foo.bar.com', port: 200, certs_directory: '/foo/bar', connection_timeout: 20, root_domain: 'foo.com'
  end

  describe "#connect" do
    it 'should block until the connection is established' do
      reset_default_config
      mock_connection = double :mock_connection
      expect(mock_connection).to receive(:register_event_handler).once
      expect(Adhearsion::Rayo::Client).to receive(:new).once.and_return mock_connection
      expect(mock_connection).to receive(:run).once
      t = Thread.new { described_class.init; described_class.run }
      t.join 5
      expect(t.status).to eq("sleep")
      Adhearsion::Events.trigger_immediately :rayo, Adhearsion::Rayo::Connection::Connected.new
      t.join
    end
  end

  describe '#connect_to_server' do
    before :each do
      Adhearsion::Process.reset
      described_class.config = reset_default_config
      described_class.config.reconnect_attempts = 1
      expect(Adhearsion::Logging.get_logger(described_class)).to receive(:fatal).at_most(:once)
      allow(described_class).to receive(:client).and_return mock_client
    end

    after :each do
      Adhearsion::Process.reset
    end

    it 'should reset the Adhearsion process state to "booting"' do
      Adhearsion::Process.booted
      expect(Adhearsion::Process.state_name).to eq(:running)
      allow(mock_client).to receive(:run).and_raise Adhearsion::Rayo::DisconnectedError
      expect(Adhearsion::Process).to receive(:reset).at_least(:once)
      described_class.connect_to_server
    end

    it 'should retry the connection the specified number of times' do
      described_class.config.reconnect_attempts = 3
      allow(mock_client).to receive(:run).and_raise Adhearsion::Rayo::DisconnectedError
      described_class.connect_to_server
      expect(described_class.attempts).to eq(3)
    end

    it 'should preserve a Adhearsion::ProtocolError exception and give up' do
      allow(mock_client).to receive(:run).and_raise Adhearsion::ProtocolError
      expect { described_class.connect_to_server }.to raise_error Adhearsion::ProtocolError
    end

    it 'should not attempt to reconnect if Adhearsion is shutting down' do
      skip 'Strange interaction with specs restarting Celluloid; TODO re-consider along with migration to actors for Process/connection management'
      Adhearsion::Process.booted
      Adhearsion::Process.shutdown
      allow(mock_client).to receive(:run).and_raise Adhearsion::Rayo::DisconnectedError
      expect { described_class.connect_to_server }.not_to raise_error
    end
  end

  describe 'using Asterisk' do
    let(:overrides) { {:username => 'test', :password => '123', :host => 'foo.bar.com', :port => 200, :certs => nil, :connection_timeout => 20, :root_domain => 'foo.com'} }

    it 'should start an Asterisk PB connection' do
      expect(Adhearsion::Rayo::Connection::Asterisk).to receive(:new).once.with(overrides).and_return mock_client
      initialize_rayo overrides.merge(:type => :asterisk)
    end
  end

  it 'should place events into the event handler' do
    event = nil
    latch = CountDownLatch.new 1

    Adhearsion::Events.register_handler :rayo do |offer|
      event = offer
      latch.countdown!
    end

    initialize_rayo
    described_class.client.handle_event offer

    expect(latch.wait(2)).to be_truthy
    expect(event).to be(offer)
  end

  describe "dispatching an offer" do
    before do
      initialize_rayo
    end

    it "should create and route the call" do
      expect(Adhearsion::Call).to receive(:new).once.and_return mock_call
      expect(mock_call).to receive_message_chain("async.route")
      Adhearsion::Rayo::Initializer.dispatch_offer offer
    end
  end

  describe "dispatching a component event" do
    let(:component)   { double 'ComponentNode' }
    let(:mock_event)  { double 'Event' }

    before { allow(mock_event).to receive_messages target_call_id: call_id, source: component }

    before do
      initialize_rayo
    end

    it "should place the event in the call's inbox" do
      expect(component).to receive(:trigger_event_handler).once.with mock_event
      Adhearsion::Events.trigger_immediately :rayo, mock_event
    end
  end

  describe "dispatching a call event" do
    let(:mock_event)  { double 'Event' }

    before { allow(mock_event).to receive_messages target_call_id: call_id }

    describe "with an active call" do
      before do
        initialize_rayo
        Adhearsion.active_calls << mock_call
      end

      it "should forward the event to the call actor" do
        events = []
        mock_call.register_event_handler do |event|
          events << event
        end
        described_class.dispatch_call_event mock_event
        sleep 0.5
        expect(events).to eql([mock_event])
      end

      it "should not block on the call handling the event" do
        mock_call.register_event_handler do |event|
          sleep 5
        end
        start_time = Time.now
        described_class.dispatch_call_event mock_event
        sleep 0.5
        expect(Time.now - start_time).to be < 1
      end
    end

    describe "with an inactive call" do
      it "should log a warning" do
        expect(Adhearsion::Logging.get_logger(described_class)).to receive(:warn).once.with("Event received for inactive call #{call_id}: #{mock_event.inspect}")
        described_class.dispatch_call_event mock_event
      end

      it "should trigger an inactive call event" do
        expect(Adhearsion::Events).to receive(:trigger).once.with(:inactive_call, mock_event)
        described_class.dispatch_call_event mock_event
      end
    end
  end

  context "rayo configuration" do
    describe "with config specified" do
      before do
        Adhearsion.config.core do |config|
          config.username = 'userb@127.0.0.1'
          config.password = 'abc123'
        end
      end

      subject do
        Adhearsion.config.core
      end

      it "should set properly the username value" do
        expect(subject.username).to eq('userb@127.0.0.1')
      end

      it "should set properly the password value" do
        expect(subject.password).to eq('abc123')
      end
    end
  end

  it "should allow easily registering handlers for AMI events" do
    result = nil
    ami_event = Adhearsion::Event::Asterisk::AMI.new :name => 'foobar'
    latch = CountDownLatch.new 1

    Adhearsion::Events.draw do
      ami :name => 'foobar' do |event|
        result = event
        latch.countdown!
      end
    end

    described_class.handle_event ami_event

    expect(latch.wait(1)).to be true
    expect(result).to be ami_event
  end
end
