require 'spec_helper'
require 'thread'

def time_it(cat, &blk)
  start = Time.now

  blk.call

  time = Time.now - start

  puts("TIME: #{cat}: #{time}")
end
def safety_net(&blk)
  begin
    blk.call
  rescue => e
    #Bugsnag.notify(e)
    @log.error("unhandled exception in EM Timer #{e}")
    puts "Error during processing: #{$!}"
    puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"

  end
end

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


  def initialize(user)
    @subscriptions = Set.new
    @channel_id = user.id
    @encode_json = true
  end

  def connected?
    true
  end

  def onopen(&block)
    @onopenblock = Proc.new { |handshake| block }
  end

  def onmessage(&block)
    @onmsgblock= Proc.new { |data| block.call data }
  end

  def onclose(&block)
    @oncloseblock = Proc.new { || block.call }
  end

  def onerror(&block)
    @onerrorblock = Proc.new { |err| block.call err }
  end

  def close()

  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

class TestClient

  def initialize(user, router)
    @user = user
    @client =  LoginClient.new(@user)
    @router = router
  end

  def connect
    @router.new_client(@client, false)
  end

  def login
    login_message = login_options(@user)
    @router.handle_login(@client, login_message)
  end

  def heartbeat(notification_id = nil, notification_seen_at = nil)
    heartbeat = Jampb::Heartbeat.new
    if notification_id
      heartbeat.notification_seen = notification_id
    else
      heartbeat.notification_seen = 'junk'
    end

    if notification_seen_at
      heartbeat.notification_seen_at = notification_seen_at.to_s
    end

    @router.handle_heartbeat(heartbeat, '1', @client)
  end

  def disconnect
    @client.oncloseblock.call
  end

  private
  def login_options(user)
    options = {}
    options["token"] = user.remember_token
    options["client_id"] = user.id
    options["client_type"] = "client"
    options["client_id_int"] = 1
    options
  end

end

describe Router, :spec_timeout => 15 do

  def database_env (env)
    singleton = GenericState.new
    singleton.id = 'default'
    singleton.env = env
    singleton.save!
  end

  def rails_env (env)
    JamRuby::Environment.should_receive(:mode).any_number_of_times.and_return(env)
  end


  include EventedSpec::EMSpec
  default_timeout(1000000)

  em_before do
    puts "EM BEFORE ROUTER NEW"

  end

  subject { @router }

  em_after do
    puts "EM AFTER"
  end

  def sequence_1(user, special_heartbeat)

    client1 = TestClient.new(user, @router)
    client1.connect
    client1.login
    @router.client_lookup.count.should be 1
    client1.heartbeat
    update_notification_at = Time.now.utc
    #client1.heartbeat(special_heartbeat.id.to_s, nil)
    client1.heartbeat(nil, update_notification_at)

    client1.disconnect

    sleep 6

    user.reload
    user = User.find(user.id)
    @router.client_lookup.count.should be 0
    last_notification_seen_at = user.notification_seen_at
    last_notification_seen_at.should_not be_nil
    last_notification_seen_at.to_i.should be update_notification_at.to_i
  end

  def sequence_2(user)

    before_count = Connection.count
    client1 = TestClient.new(user, @router)
    client1.connect
    client1.login
    @router.client_lookup.count.should be 1
    after_count = Connection.count
    before_count.should be (after_count - 1)

    sleep 25

    @router.client_lookup.count.should be 0
    disc_count = Connection.count
    before_count.should be disc_count
  end

  def sequence_in_session(user)
    before_count = Connection.count
    client1 = TestClient.new(user, @router)
    client1.connect
    client1.login
    @router.client_lookup.count.should be 1

    music_session = FactoryGirl.create(:active_music_session, :creator => user)
    connection = Connection.find_by_user_id(user.id)
    connection.music_session = music_session
    connection.save!


    client1.disconnect
    @router.client_lookup.count.should be 0
    disc_count = Connection.count
    before_count.should be disc_count
  end
  def sequence_in_two_session(user1, user2)

    # create friendship between the two, to increase notifications
    FactoryGirl.create(:friendship, :user => user1, :friend => user2)
    FactoryGirl.create(:friendship, :user => user2, :friend => user1)

    before_count = Connection.count
    client1 = TestClient.new(user1, @router)
    client1.connect
    client1.login
    @router.client_lookup.count.should be 1

    client1 = TestClient.new(user2, @router)
    client1.connect
    client1.login
    @router.client_lookup.count.should be 2

    music_session = FactoryGirl.create(:active_music_session, :creator => user)
    connection = Connection.find_by_user_id(user1.id)
    connection.music_session = music_session
    connection.save!

    connection2 = Connection.find_by_user_id(user2.id)
    connection2.music_session = music_session
    connection2.save!


    client1.disconnect
    @router.client_lookup.count.should be 1
    disc_count = Connection.count
    before_count.should be (disc_count - 1)
  end

    def delete_conn_from_under(user)

    before_count = Connection.count
    client1 = TestClient.new(user, @router)
    client1.connect
    client1.login
    @router.client_lookup.count.should be 1
    after_count = Connection.count
    before_count.should be (after_count - 1)

    Connection.first.delete

    @router.periodical_check_clients

    @router.client_lookup.count.should be 0
    disc_count = Connection.count
    before_count.should be disc_count
  end


  describe "stress"  do
    before {
      database_env('production')
      rails_env('production')
      stub_const("ENV", {'BUILD_NUMBER' => 1})
    }
    let(:user1) { FactoryGirl.create(:user) }
    let(:user2) { FactoryGirl.create(:user) }
    let(:user3) { FactoryGirl.create(:user) }
    let(:user4) { FactoryGirl.create(:user) }
    let(:user5) { FactoryGirl.create(:user) }
    let!(:other) { FactoryGirl.create(:user, last_jam_locidispid: 1) }
    let!(:msg1) {FactoryGirl.create(:notification_text_message, source_user: other, target_user: user1) }

    it "the test" do

        @router = Router.new()
        @router.connect_time_expire_client = 20
        @router.connect_time_stale_client = 14
        @router.heartbeat_interval_client = @router.connect_time_stale_client / 2
        @router.connect_time_expire_browser = 20
        @router.connect_time_stale_browser = 14
        @router.max_connections_per_user = 10
        @router.heartbeat_interval_browser = @router.connect_time_stale_browser / 2
        @router.maximum_minutely_heartbeat_rate_browser = 10
        @router.maximum_minutely_heartbeat_rate_client = 10
        @router.amqp_connection_manager = AmqpConnectionManager.new(true, 4, host: 'localhost', port: 5672)
        @router.gateway_name = 'gateway1'

        @router.init

        em do
          EventMachine::PeriodicTimer.new(5) do
            time_it('stats_dump') { safety_net { @router.periodical_stats_dump } }
          end

          sequence_in_session(user3)
          sequence_in_two_session(user4, user5)
          delete_conn_from_under(user3)
          sequence_1(user1, msg1)
          sequence_1(user1, msg1)
          sequence_2(user2)

          done

        end
    end
  end
end