require 'spec_helper'

# these tests avoid the use of ActiveRecord and FactoryGirl to do blackbox, non test-instrumented tests
describe ConnectionManager do

  TRACKS = [{"instrument_id" => "electric guitar", "sound" => "mono"}]

  before do
    @conn = PG::Connection.new(:dbname => SpecDb::TEST_DB_NAME, :user => "postgres", :password => "postgres", :host => "localhost")
    @connman = ConnectionManager.new(:conn => @conn)
    @message_factory = MessageFactory.new
  end

  def create_user(first_name, last_name, email, options = {:musician => true})
    @conn.exec("INSERT INTO users (first_name, last_name, email, musician, encrypted_password, city, state, country) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id", [first_name, last_name, email, options[:musician], '1', 'Apex', 'NC', 'USA']) do |result|
      return result.getvalue(0, 0)
    end
  end

  def create_music_session(user_id, options={})
    default_options = {:musician_access => true, :fan_chat => true, :fan_access => true, :approval_required=> false}
    options = default_options.merge(options)
    description = "some session"
    @conn.exec("INSERT INTO music_sessions (user_id, description, musician_access, approval_required, fan_chat, fan_access) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", [user_id, description, options[:musician_access], options[:approval_required], options[:fan_chat], options[:fan_access]]) do |result|
      session_id = result.getvalue(0, 0)
      @conn.exec("INSERT INTO music_sessions_history (music_session_id, description, user_id) VALUES ($1, $2, $3)", [session_id, description, user_id])
      return session_id
    end
  end

  def assert_num_connections(client_id, expected_num_connections)
    # make sure the connection is still there
    @conn.exec("SELECT count(*) FROM connections where client_id = $1", [client_id]) do |result|
      result.getvalue(0, 0).to_i.should == expected_num_connections
    end
  end

  def assert_session_exists(music_session_id, exists)
    @conn.exec("SELECT count(*) FROM music_sessions where id = $1", [music_session_id]) do |result|
      if exists
        result.getvalue(0, 0).should == "1"
      else
        result.getvalue(0, 0).should == "0"
      end
    end
  end

  it "can't create bogus user_id" do
      expect { @connman.create_connection("aeonuthaoentuh", "client_id", "1.1.1.1") }.to raise_error(PG::Error)
  end

  it "can't create two client_ids of same value" do

    client_id = "client_id1"
    user_id = create_user("test", "user1", "user1@jamkazam.com")

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    expect { @connman.create_connection(user_id, client_id, "1.1.1.1") }.to raise_error(PG::Error)
  end

  it "create connection then delete it" do
    
    client_id = "client_id2"
    user_id = create_user("test", "user2", "user2@jamkazam.com")
    count = @connman.create_connection(user_id, client_id, "1.1.1.1")
    count.should == 1
    # make sure the connection is seen

    @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user_id]) do |result|
      result.getvalue(0, 0).should == "1"
    end

    cc = Connection.find_by_client_id!(client_id)
    cc.connected?.should be_true

    count = @connman.delete_connection(client_id)
    count.should == 0

    @conn.exec("SELECT count(*) FROM connections where user_id = $1", [user_id]) do |result|
      result.getvalue(0, 0).should == "0"
    end
  end

  # it "create connection creates user joined message appropriately" do

  #   client_id = "client_id3"
  #   client_id2 = "client_id3_1"

  #   user_id = create_user("test", "user3", "user3@jamkazam.com")

  #   # we should get a message saying that this user is online
  #   friend_update = @message_factory.friend_update(user_id, true)
  #   @connman.mq_router.should_receive(:publish_to_friends).with([], friend_update, user_id)

  #   @connman.create_connection(user_id, client_id, "1.1.1.1")

  #   # but a second connection from the same user should cause no such message
  #   @connman.should_receive(:publish_to_friends).exactly(0).times

  #   @connman.create_connection(user_id, client_id2, "1.1.1.1")

  # end


  # it "deletes connection creates user left message appropriately" do

  #   client_id = "client_id4"
  #   client_id2 = "client_id4_1"

  #   user_id = create_user("test", "user4", "user4@jamkazam.com")

  #   # we should get a message saying that this user is online

  #   @connman.create_connection(user_id, client_id, "1.1.1.1")
  #   @connman.create_connection(user_id, client_id2, "1.1.1.1")

  #   # deleting one of the two connections should cause no messages
  #   @connman.should_receive(:publish_to_friends).exactly(0).times

  #   @connman.delete_connection(client_id)

  #   # but deleting the final connection should cause a left message
  #   friend_update = @message_factory.friend_update(user_id, false)
  #   @connman.mq_router.should_receive(:publish_to_friends).with([], friend_update, user_id)

  #   @connman.delete_connection(client_id2)
  # end

  it "lookup of friends should find mutual friends only" do

    def create_friend(user_id, friend_id)
      @conn.exec("INSERT INTO friendships(user_id, friend_id) VALUES ($1, $2)", [user_id, friend_id])
    end

    def delete_friend(user_id, friend_id)
      @conn.exec("DELETE FROM friendships WHERE user_id = $1 AND friend_id = $2", [user_id, friend_id])
    end

    client_id = "client_id5"

    user_id1 = create_user("test", "user5", "user5@jamkazam.com")
    user_id2 = create_user("test", "user6", "user6@jamkazam.com")
    user_id3 = create_user("test", "user7", "user7@jamkazam.com")

    @connman.gather_friends(@conn, user_id1).should == []
    @connman.gather_friends(@conn, user_id2).should == []
    @connman.gather_friends(@conn, user_id3).should == []

    # create one-way link
    create_friend(user_id1, user_id2)

    @connman.gather_friends(@conn, user_id1).should == []
    @connman.gather_friends(@conn, user_id2).should == []
    @connman.gather_friends(@conn, user_id3).should == []

    # create one-way link back the other way
    create_friend(user_id2, user_id1)

    @connman.gather_friends(@conn, user_id1).should == [user_id2]
    @connman.gather_friends(@conn, user_id2).should == [user_id1]
    @connman.gather_friends(@conn, user_id3).should == []

    # make sure a new link to user 1 > user 3 doesn't disrupt anything
    create_friend(user_id1, user_id3)

    @connman.gather_friends(@conn, user_id1).should == [user_id2]
    @connman.gather_friends(@conn, user_id2).should == [user_id1]
    @connman.gather_friends(@conn, user_id3).should == []

    # make sure a new link to user 1 > user 3 doesn't disrupt anything
    create_friend(user_id3, user_id1)

    @connman.gather_friends(@conn, user_id1).should =~ [user_id2, user_id3]
    @connman.gather_friends(@conn, user_id2).should == [user_id1]
    @connman.gather_friends(@conn, user_id3).should == [user_id1]
  end

  it "flag stale connection" do
    client_id = "client_id8"
    user_id = create_user("test", "user8", "user8@jamkazam.com")
    @connman.create_connection(user_id, client_id, "1.1.1.1")

    num = JamRuby::Connection.count(:conditions => ['aasm_state = ?','connected'])
    num.should == 1
    assert_num_connections(client_id, num)
    @connman.flag_stale_connections(60)
    assert_num_connections(client_id, num)

    sleep(1)

    num = JamRuby::Connection.count(:conditions => ["updated_at < (NOW() - interval '#{1} second') AND aasm_state = 'connected'"])
    num.should == 1
    # this should change the aasm_state to stale
    @connman.flag_stale_connections(1)

    num = JamRuby::Connection.count(:conditions => ["updated_at < (NOW() - interval '#{1} second') AND aasm_state = 'connected'"])
    num.should == 0

    num = JamRuby::Connection.count(:conditions => ["updated_at < (NOW() - interval '#{1} second') AND aasm_state = 'stale'"])
    num.should == 1
    assert_num_connections(client_id, 1)

    cids = @connman.stale_connection_client_ids(1)
    cids.size.should == 1
    cids[0].should == client_id
    cids.each { |cid| @connman.delete_connection(cid) }

    sleep(1)
    assert_num_connections(client_id, 0)
  end

  it "expires stale connection" do
    client_id = "client_id8"
    user_id = create_user("test", "user8", "user8@jamkazam.com")
    @connman.create_connection(user_id, client_id, "1.1.1.1")

    sleep(1)
    @connman.flag_stale_connections(1)
    assert_num_connections(client_id, 1)    
    # assert_num_connections(client_id, JamRuby::Connection.count(:conditions => ['aasm_state = ?','stale']))

    @connman.expire_stale_connections(60)
    assert_num_connections(client_id, 1)

    sleep(1)
    # this should delete the stale connection
    @connman.expire_stale_connections(1)
    assert_num_connections(client_id, 0)
  end

  it "connections with music_sessions associated" do

    client_id = "client_id9"
    user_id = create_user("test", "user9", "user9@jamkazam.com")
    music_session_id = create_music_session(user_id)

    user = User.find(user_id)
    music_session = MusicSession.find(music_session_id)

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)

    connection.errors.any?.should be_false
    
    assert_session_exists(music_session_id, true)

    @conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result|
      result.getvalue(0, 0).should == music_session_id
    end

    @connman.delete_connection(client_id)
    assert_num_connections(client_id, 0)

    assert_session_exists(music_session_id, false)
  end

  it "join_music_session fails if no connection" do

    client_id = "client_id10"
    user_id = create_user("test", "user10", "user10@jamkazam.com")
    music_session_id = create_music_session(user_id)

    user = User.find(user_id)
    music_session = MusicSession.find(music_session_id)

    expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) }.to raise_error(ActiveRecord::RecordNotFound)

  end

  it "join_music_session fails if user is a fan but wants to join as a musician" do

    client_id = "client_id10.11"
    client_id2 = "client_id10.12"
    user_id = create_user("test", "user10.11", "user10.11@jamkazam.com", :musician => true)
    user_id2 = create_user("test", "user10.12", "user10.12@jamkazam.com", :musician => false)
    @connman.create_connection(user_id, client_id, "1.1.1.1")
    @connman.create_connection(user_id2, client_id2, "1.1.1.1")

    music_session_id = create_music_session(user_id)

    user = User.find(user_id)
    music_session = MusicSession.find(music_session_id)

    @connman.join_music_session(user, client_id, music_session, true, TRACKS)

    user = User.find(user_id2)

    connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS)
    connection.errors.size.should == 1
    connection.errors.get(:as_musician).should == [Connection::FAN_CAN_NOT_JOIN_AS_MUSICIAN]
  end

  it "as_musician is coerced to boolean" do
    client_id = "client_id10.2"
    user_id = create_user("test", "user10.2", "user10.2@jamkazam.com", :musician => false)
    @connman.create_connection(user_id, client_id, "1.1.1.1")

    music_session_id = create_music_session(user_id)

    user = User.find(user_id)
    music_session = MusicSession.find(music_session_id)

    connection = @connman.join_music_session(user, client_id, music_session, 'blarg', TRACKS)
    connection.errors.size.should == 0
    connection.as_musician.should be_false
  end

  it "join_music_session fails if fan_access=false and the user is a fan" do

    musician_client_id = "client_id10.3"
    fan_client_id = "client_id10.4"
    musician_id = create_user("test", "user10.3", "user10.3@jamkazam.com")
    fan_id = create_user("test", "user10.4", "user10.4@jamkazam.com", :musician => false)
    @connman.create_connection(musician_id, musician_client_id, "1.1.1.1")
    @connman.create_connection(fan_id, fan_client_id, "1.1.1.1")

    music_session_id = create_music_session(musician_id, :fan_access => false)

    user = User.find(musician_id)
    music_session = MusicSession.find(music_session_id)

    @connman.join_music_session(user, musician_client_id, music_session, true, TRACKS)

    # now join the session as a fan, bt fan_access = false
    user = User.find(fan_id)
    connection = @connman.join_music_session(user, fan_client_id, music_session, false, TRACKS)
    connection.errors.size.should == 1  
  end

  it "join_music_session fails if incorrect user_id specified" do

    client_id = "client_id20"
    user_id = create_user("test", "user20", "user20@jamkazam.com")
    user_id2 = create_user("test", "user21", "user21@jamkazam.com")
    music_session_id = create_music_session(user_id)

    user = User.find(user_id2)
    music_session = MusicSession.find(music_session_id)

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    # specify real user id, but not associated with this session
    expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) } .to raise_error(ActiveRecord::RecordNotFound)
  end

  it "join_music_session fails if no music_session" do
    client_id = "client_id11"
    user_id = create_user("test", "user11", "user11@jamkazam.com")

    user = User.find(user_id)
    music_session = MusicSession.new

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
    connection.errors.size.should == 1
    connection.errors.get(:music_session).should == [Connection::MUSIC_SESSION_MUST_BE_SPECIFIED]
  end

  it "join_music_session fails if approval_required and no invitation, but generates join_request" do
    client_id = "client_id11.1"
    user_id = create_user("test", "user11.1", "user11.1@jamkazam.com")
    user_id2 = create_user("test", "user11.2", "user11.2@jamkazam.com")
    music_session_id = create_music_session(user_id, :approval_required => true)

    user = User.find(user_id2)
    music_session = MusicSession.find(music_session_id)

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    # specify real user id, but not associated with this session
    expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) } .to raise_error(ActiveRecord::RecordNotFound)
  end


  it "leave_music_session fails if no music_session" do

    client_id = "client_id12"
    user_id = create_user("test", "user12", "user12@jamkazam.com")

    user = User.find(user_id)
    dummy_music_session = MusicSession.new

    @connman.create_connection(user_id, client_id, "1.1.1.1")

    expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError)
  end

  it "leave_music_session fails if in different music_session" do

    client_id = "client_id13"
    user_id = create_user("test", "user13", "user13@jamkazam.com")
    music_session_id = create_music_session(user_id)

    user = User.find(user_id)
    music_session = MusicSession.find(music_session_id)

    dummy_music_session = MusicSession.new

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    @connman.join_music_session(user, client_id, music_session, true, TRACKS)
    expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError)
  end

  it "leave_music_session works" do

    client_id = "client_id14"
    user_id = create_user("test", "user14", "user14@jamkazam.com")
    music_session_id = create_music_session(user_id)

    user = User.find(user_id)
    music_session = MusicSession.find(music_session_id)

    @connman.create_connection(user_id, client_id, "1.1.1.1")
    @connman.join_music_session(user, client_id, music_session, true, TRACKS)

    assert_session_exists(music_session_id, true)

    @conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result|
      result.getvalue(0, 0).should == music_session_id
    end

    @connman.leave_music_session(user, Connection.find_by_client_id(client_id), music_session)

    @conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result|
      result.getvalue(0, 0).should == nil
    end

    assert_session_exists(music_session_id, false)

    @connman.delete_connection(client_id)

    assert_num_connections(client_id, 0)

  end
end

