require 'spec_helper'
require 'thread'

LoginClient = Class.new do
  attr_accessor :onmsgblock, :onopenblock, :encode_json, :channel_id, :client_id, :user_id, :context, :trusted, :subscriptions


  def initialize()
    @subscriptions = Set.new
  end

  def connected?
    true
  end

  def onopen(&block)
    @onopenblock = block
  end

  def onmessage(&block)
    @onmsgblock = block
  end

  def close(&block)
    @oncloseblock = block
  end

  def close_websocket()

  end

  def send(msg)
    puts msg
  end

  def get_peername
    return "\x00\x02\x93\v\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" # 37643, "localhost"
  end

end


# does a login and returns client
def login(router, user, password, client_id, token, client_type)

  message_factory = MessageFactory.new
  client = LoginClient.new

  router.should_receive(:send_to_client) do |*args| 
    args.count.should == 2
    args[0].should == client
    args[1].is_a?(Jampb::ClientMessage).should be_true
  end
  router.should_receive(:extract_ip).at_least(:once).with(client, nil).and_return("127.0.0.1")
  client.should_receive(:onclose)
  client.should_receive(:onerror)


  #client.should_receive(:get_peername).and_return("\x00\x02\x93\v\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00")

  @router.new_client(client, false)
  handshake = double("handshake")
  handshake.should_receive(:query).exactly(3).times.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid, "client_id" => client_id, "token" => token, "client_type" => client_type })
  handshake.should_receive(:headers).at_least(:once).and_return({})
  client.onopenblock.call handshake

  client
end

def heartbeat(router, client)
  message_factory = MessageFactory.new
  heartbeat = message_factory.heartbeat()

  client.onmsgblock.call heartbeat.to_s
end

# does a login and returns client
def login_latency_tester(router, latency_tester, client_id)

  client = LoginClient.new
  message_factory = MessageFactory.new

  router.should_receive(:send_to_client).at_least(1).times do |*args|
    args.count.should == 2
    args[0].should == client
    args[1].is_a?(Jampb::ClientMessage).should be_true
  end
  router.should_receive(:extract_ip).at_least(:once).with(client, nil).and_return("127.0.0.1")
  client.should_receive(:onclose)
  client.should_receive(:onerror)

  #client.should_receive(:get_peername).and_return("\x00\x02\x93\v\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00")

  @router.new_client(client, true)
  handshake = double("handshake")
  handshake.should_receive(:query).exactly(3).times.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid, "client_type" => "latency_tester", "client_id" => client_id })
  handshake.should_receive(:headers).at_least(:once).and_return({})
  client.onopenblock.call handshake

  client
end


describe Router do 
  include EventedSpec::EMSpec

  message_factory = MessageFactory.new

  em_before do
    @router = Router.new()
    @router.connect_time_expire_client = 60
    @router.connect_time_stale_client = 40
    @router.heartbeat_interval_client = @router.connect_time_stale_client / 2
    @router.connect_time_expire_browser = 60
    @router.connect_time_stale_browser = 40
    @router.max_connections_per_user = 10
    @router.heartbeat_interval_browser = @router.connect_time_stale_browser / 2
    @router.amqp_connection_manager = AmqpConnectionManager.new(true, 4, host: 'localhost', port: 5672)
    @router.gateway_name = 'gateway1'
  end

  subject { @router }

  em_after do

  end

  describe "periodical_check_clients" do
    let(:user) { FactoryGirl.create(:user) }
    it "with no data" do
      @router.client_lookup.length.should == 0
      @router.periodical_check_clients
      done
    end

    it "with one OK client" do
      client = double("client")
      client.should_receive(:context=).any_number_of_times
      conn1 = FactoryGirl.create(:connection, :user => user, :client_id => "pc1")
      @router.add_tracker(user, client, 'client', conn1.client_id)
      @router.client_lookup[conn1.client_id].should_not be_nil
      @router.periodical_check_clients
      @router.client_lookup[conn1.client_id].should_not be_nil
      done
    end

    it "with one missing client" do
      client = double("client")
      client.should_receive(:context=).any_number_of_times
      context = ClientContext.new(user, client, "client")
      client.should_receive(:context).any_number_of_times.and_return(context)
      client.should_receive(:close)
      client.should_receive(:subscriptions).and_return(Set.new)
      conn1 = FactoryGirl.create(:connection, :user => user, :client_id => "pc1")
      client.should_receive(:client_id).and_return(conn1.client_id)
      @router.add_tracker(user, client, 'client', conn1.client_id)
      conn1.delete
      @router.client_lookup[conn1.client_id].should_not be_nil
      @router.periodical_check_clients
      @router.client_lookup[conn1.client_id].should be_nil
      done
    end
  end

  describe "serviceability" do
    it "should start and stop", :mq => true do
      #em do
        done
      #end
    end

    it "should register for client events", :mq => true do
      #em do
        client = double("client")
        client.should_receive(:onopen)
        client.should_receive(:onclose)
        client.should_receive(:onerror)
        client.should_receive(:onmessage)
        client.should_receive(:encode_json=)
        client.should_receive(:trusted=)

        @router.new_client(client, false)
        done
      #end
    end
  end

  describe "topic routing helpers" do
    it "create and delete user lookup set" do
      #em do
        user = double(User)
        user.should_receive(:id).any_number_of_times.and_return("1")
        client = double("client")
        client.should_receive(:context=).any_number_of_times
        context = ClientContext.new(user, client, "client")

        @router.user_context_lookup.length.should == 0

        @router.add_user(context)

        @router.user_context_lookup.length.should == 1

        @router.remove_user(context)

        @router.user_context_lookup.length.should == 0
        done
      #end
    end
  end


  describe "login" do
    it "should not allow login of bogus user", :mq => true do
      pending "broken currently due to some"
      #em do
        TestClient = Class.new do

          attr_accessor :onmsgblock, :onopenblock, :encode_json, :client_id, :subscriptions

          def initialize()
            @subscriptions = Set.new
          end

          def onopen(&block)
            @onopenblock = block
          end

          def onmessage(&block)
            @onmsgblock = block
          end

          def close_websocket()
          end

          def close()
          end

          def get_peername
            return "\x00\x02\x93\v\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" # 37643, "localhost"
          end

        end

        client = TestClient.new

        error_msg = message_factory.server_rejection_error("invalid login", "invalid_login")

        @router.should_receive(:send_to_client).with(client, error_msg)
        client.should_receive(:close_websocket)
        client.should_receive(:onclose)
        client.should_receive(:onerror)


        @router.new_client(client)
        handshake = double("handshake")
        handshake.should_receive(:query).and_return({"pb" => "true"})
        client.onopenblock.call handshake

        # create a login message, and pass it into the router via onmsgblock.call
        login = message_factory.login_with_user_pass("baduser@example.com", "foobar")

        client.onmsgblock.call login.to_s
        done
      #end
    end

    it "should allow login of valid user", :mq => true do
      @user = FactoryGirl.create(:user,
                       :password => "foobar", :password_confirmation => "foobar")
      client1 = login(@router, @user, "foobar", "1", @user.remember_token, "client")
      @user.reload

      # verify that last_jam_reason, last_jam_addr, and last_jam_updated_at were updated
      @user.last_jam_addr.should == ip_address_to_int('127.0.0.1')
      @user.last_jam_updated_reason.should == User::JAM_REASON_LOGIN
      @user.last_jam_updated_at.should_not be_nil
      puts @user.last_jam_addr
      @user.last_jam_locidispid.should == GeoIpLocations.lookup(@user.last_jam_addr)[:locidispid]
      done
    end

    it "should not update last_jam info if already set", :mq => true do
      last_jam_addr = ip_address_to_int('10.0.0.1')
      last_jam_updated_reason = User::JAM_REASON_REGISTRATION
      last_jam_updated_at = 5.hours.ago
      last_jam_locidispid = 1
      @user = FactoryGirl.create(:user,
                                 :password => "foobar", :password_confirmation => "foobar",
                                 last_jam_addr: last_jam_addr, last_jam_updated_reason: last_jam_updated_reason, last_jam_updated_at: last_jam_updated_at, last_jam_locidispid: last_jam_locidispid)
      client1 = login(@router, @user, "foobar", "1", @user.remember_token, "client")
      @user.reload

      # verify that last_jam_reason, last_jam_addr, and last_jam_updated_at were updated
      @user.last_jam_addr.should == last_jam_addr
      @user.last_jam_updated_reason.should == last_jam_updated_reason
      @user.last_jam_updated_at.round.should == last_jam_updated_at.round
      @user.last_jam_locidispid.should == last_jam_locidispid
      done
    end


    it "should allow login of a latency_tester", :mq => true do
      @latency_tester = FactoryGirl.create(:latency_tester)
      client1 = login_latency_tester(@router, @latency_tester, @latency_tester.client_id)
      done
    end

    it "should allow heartbeat of a latency_tester", :mq => true do
      @latency_tester = FactoryGirl.create(:latency_tester)
      client1 = login_latency_tester(@router, @latency_tester, @latency_tester.client_id)
      heartbeat(@router, client1)
      done
    end

    it "should allow music_session_join of valid user", :mq => true do

      user1 = FactoryGirl.create(:user) # in the music session
      user2 = FactoryGirl.create(:user) # in the music session
      user3 = FactoryGirl.create(:user) # not in the music session

      music_session = FactoryGirl.create(:active_music_session, :creator => user1)

      music_session_member1 = FactoryGirl.create(:connection, :user => user1, :music_session => music_session, :client_id => "4")
      music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => music_session, :client_id => "5")

      # make a music_session and define two members

      # create client 1, log him in, and log him in to music session
      client1 = login(@router, user1, "foobar", "1", user1.remember_token, "client")
      done
    end

    it "should allow two valid subscribers to communicate with session-directed messages", :mq => true do
      #em do
        user1 = FactoryGirl.create(:user) # in the music session
        user2 = FactoryGirl.create(:user) # in the music session

        music_session = FactoryGirl.create(:active_music_session, :creator => user1)


        # create client 1, log him in, and log him in to music session
        client1 = login(@router, user1, "foobar", "1", user1.remember_token, "client")

        client2 = login(@router, user2, "foobar", "2", user2.remember_token, "client")

        # make a music_session and define two members

        music_session_member1 = FactoryGirl.create(:connection, :user => user1, :music_session => music_session, :client_id => "6")
        music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => music_session, :client_id => "7")

        done
      #end
    end

    it "should allow two valid subscribers to communicate with p2p messages", :mq => true do
      #em do
          user1 = FactoryGirl.create(:user) # in the music session
          user2 = FactoryGirl.create(:user) # in the music session

          music_session = FactoryGirl.create(:active_music_session, :creator => user1)

          # create client 1, log him in, and log him in to music session
          client1 = login(@router, user1, "foobar", "1", user1.remember_token, "client")
          #login_music_session(@router, client1, music_session)

          client2 = login(@router, user2, "foobar", "2", user2.remember_token, "client")
          #login_music_session(@router, client2, music_session)

          # by creating
          music_session_member1 = FactoryGirl.create(:connection, :user => user1, :music_session => music_session, :client_id => "8")

          # now attempt to message p2p!

          # first test: user 2 should be able to send a ping message to user 1, even though he isn't in the same session yet

          # create a login message, and pass it into the router via onmsgblock.call
          ping = message_factory.ping_request("1", "2")

          #@router.should_receive(:send_to_client) #.with(client1, ping)

          ## send ping to client 2
          #client2.onmsgblock.call ping.to_s

          #music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => music_session, :client_id => "2")

          done
      #end
    end
  end
end

