require 'test_helper'

class NavigationTest < ActionDispatch::IntegrationTest
  def teardown
    DummyObserver.unstub(:update)
  end

  test 'user sign up should grant badge to itself' do
    DummyObserver.any_instance.expects(:update).times(1).with do |hash|
      hash[:description] == 'granted just-registered badge' &&
        hash[:sash_id] == user('Jack').sash_id &&
        hash[:granted_at].to_date == Time.now.utc.to_date
    end

    visit '/users/new'
    fill_in 'Name', with: 'Jack'
    assert_difference('Merit::ActivityLog.count') do
      click_button('Create User')
    end

    just_registered = Merit::Badge.by_name('just-registered').first
    assert_equal [just_registered], user('Jack').badges
  end

  test 'User#add_badge should add one badge, #rm_badge should delete one' do
    DummyObserver.any_instance.expects(:update).times 0

    user = User.create(name: 'test-user')
    assert_equal [], user.badges

    badge = Merit::Badge.first
    user.add_badge badge.id
    user.add_badge badge.id
    assert_equal [badge, badge], user.badges
    assert_equal [user], badge.users

    user.rm_badge badge.id
    assert_equal [badge], user.reload.badges

    assert_raise NoMethodError do
      user.add_badge badge
    end

    assert_raise NoMethodError do
      user.rm_badge badge
    end
  end

  test 'Remove inexistent badge should do nothing' do
    DummyObserver.any_instance.expects(:update).times 0
    user = User.create(name: 'test-user')
    assert_equal [], user.badges
    user.rm_badge 1
    assert_equal [], user.badges
  end

  test 'users#index should grant badge multiple times' do
    DummyObserver.any_instance.expects(:update).times(1).with do |hash|
      hash[:description] == 'granted visited_admin badge' &&
      hash[:sash_id] == user.sash_id
    end
    DummyObserver.any_instance.expects(:update).times(5).with do |hash|
      hash[:description] == 'granted gossip badge' &&
      hash[:sash_id] == user.sash_id
    end
    DummyObserver.any_instance.expects(:update).times(8).with do |hash|
      hash[:description] == 'granted wildcard_badge badge' &&
      hash[:sash_id] == user.sash_id
    end

    # Multiple rule
    assert_difference 'badges_by_name(user, "gossip").count', 3 do
      3.times { visit '/users' }
    end

    # Namespaced controller
    assert_no_difference 'badges_by_name(user, "visited_admin").count' do
      visit '/users'
    end
    assert_difference 'badges_by_name(user, "visited_admin").count' do
      visit '/admin/users'
    end

    # Wildcard controllers
    assert_difference 'badges_by_name(user, "wildcard_badge").count', 3 do
      visit '/admin/users'
      visit '/api/users'
      visit '/users'
    end
  end

  test 'user workflow should grant some badges at some times' do
    DummyObserver.any_instance.expects(:update).at_least_once
    # Commented 9 times, no badges yet
    user # creates user
    # Create needed friend user object
    friend = User.create(name: 'friend')

    (1..9).each do |i|
      Comment.create(
        name: "Title #{i}",
        comment: "Comment #{i}",
        user_id: user.id,
        votes: 8
      )
    end
    assert user.badges.empty?, 'Should not have badges'

    assert_equal 0, user.points
    assert_equal 0, Merit::Score::Point.count
    user.add_points 15
    assert_equal 15, user.points
    user.subtract_points 15
    assert_equal 0, user.points
    assert_equal 2, Merit::Score::Point.count

    # Tenth comment with errors doesn't change reputation
    badges = user.reload.badges
    points = user.points
    visit '/comments/new'
    assert_no_difference('Merit::ActivityLog.count') do
      click_button('Create Comment')
    end
    assert_equal badges, user.reload.badges
    assert_equal points, user.points

    # Tenth comment without errors, assert 10-commenter badge granted
    fill_in 'Name', with: 'Hi!'
    fill_in 'Comment', with: 'Hi bro!'
    fill_in 'User', with: user.id
    assert_difference('Merit::ActivityLog.count', 2) do
      click_button('Create Comment')
    end

    assert_equal [Merit::Badge.by_name('commenter').by_level(10).first], user.reload.badges
    assert_equal [Merit::Badge.by_name('has_commenter_friend').first], friend.reload.badges

    # Vote (to 5) a user's comment, assert relevant-commenter badge granted
    relevant_comment = user.comments.where(votes: 8).first
    visit '/comments'
    within("tr#c_#{relevant_comment.id}") do
      click_link '2'
    end

    relevant_badge = Merit::Badge.by_name('relevant-commenter').first
    assert user.badges.include?(relevant_badge), "User badges: #{user.badges.collect(&:name).inspect} should contain relevant-commenter badge."

    # Edit user's name by long name
    # tests ruby code in grant_on is being executed, and gives badge
    visit "/users/#{user.id}/edit"
    fill_in 'Name', with: 'long_name!'
    click_button('Update User')

    user = User.where(name: 'long_name!').first
    autobiographer_badge = Merit::Badge.by_name('autobiographer').first
    assert user.badges.include?(autobiographer_badge), "User badges: #{user.badges.collect(&:name).inspect} should contain autobiographer badge."

    # Edit user's name by short name
    # tests ruby code in grant_on is being executed, and removes badge
    visit "/users/#{user.id}/edit"
    fill_in 'Name', with: 'abc'
    assert_difference('Merit::ActivityLog.count', 2) do
      click_button('Update User')
    end

    # Check created Merit::ActivityLogs
    assert_equal 'granted commenter badge', Merit::ActivityLog.all[0].description
    assert_equal 'granted 20 points', Merit::ActivityLog.all[-1].description
    assert_equal 'removed autobiographer badge', Merit::ActivityLog.all[-2].description

    user = User.where(name: 'abc').first
    assert !user.badges.include?(autobiographer_badge), "User badges: #{user.badges.collect(&:name).inspect} should remove autobiographer badge."
  end

  test 'user workflow should add up points at some times' do
    DummyObserver.any_instance.expects(:update).at_least_once
    User.delete_all
    user = User.create(name: 'test-user')
    assert_equal 0, user.points, 'User should start with 0 points'

    visit "/users/#{user.id}/edit"
    fill_in 'Name', with: 'a'
    assert_difference('Merit::ActivityLog.count', 1) do
      click_button('Update User')
    end

    user = User.where(name: 'a').first
    assert_equal 20, user.points, 'Updating info should grant 20 points'

    visit '/comments/new'
    click_button('Create Comment')

    user = User.where(name: 'a').first
    assert_equal 20, user.points, 'Empty comment should grant no points'

    visit '/comments/new'
    fill_in 'Name', with: 'Hi!'
    fill_in 'Comment', with: 'Hi bro!'
    fill_in 'User', with: user.id
    click_button('Create Comment')

    user = User.where(name: 'a').first
    assert_equal 20, user.points, 'Commenting should not grant 20 points if name.length <= 4'

    visit '/comments/new'
    fill_in 'Name', with: 'Hi there!'
    fill_in 'Comment', with: 'Hi bro!'
    fill_in 'User', with: user.id
    click_button('Create Comment')

    user = User.where(name: 'a').first
    assert_equal 40, user.points, 'Commenting should grant 20 points if name.length > 4'

    visit "/comments/#{Comment.last.id}/vote/4"
    user = User.first
    assert_equal 47, user.points, 'Voting comments should grant 5 points for
      voted, and 1 point for voting twice (repeated rule)'
    assert_equal 5, user.points(category: 'vote'), 'Voting comments should
      grant 5 points for voted in vote category'

    visit '/comments/new'
    fill_in 'Name', with: 'Hi'
    fill_in 'Comment', with: '4'
    fill_in 'User', with: user.id
    click_button('Create Comment')

    user = User.where(name: 'a').first
    assert_equal 51, user.points, 'Commenting should grant the integer in
      comment points if comment is an integer'

    # Destroying a comment should remove points from the comment creator.
    comment_to_destroy = user.comments.last
    visit '/comments'
    skip "see bug https://github.com/merit-gem/merit/issues/365"
    assert_difference lambda { user.reload.points }, -5 do
      within("tr#c_#{comment_to_destroy.id}") do
        click_link 'Destroy'
      end
    end
  end

  test 'user workflow should grant levels at some times' do
    DummyObserver.any_instance.expects(:update).at_least_once
    user = User.create(name: 'test-user')
    assert user.badges.empty?

    # Edit user's name by 2 chars name
    visit "/users/#{user.id}/edit"
    fill_in 'Name', with: 'ab'
    click_button('Update User')

    user = User.where(name: 'ab').first
    assert_equal 0, user.level, "User level should be 0."
    Merit::RankRules.new.check_rank_rules
    user.reload
    assert_equal 2, user.level, "User level should be 2."

    # Edit user's name by short name. Doesn't go back to previous rank.
    visit "/users/#{user.id}/edit"
    fill_in 'Name', with: 'a'
    click_button('Update User')

    user = User.where(name: 'a').first
    Merit::RankRules.new.check_rank_rules
    user.reload
    assert_equal 2, user.level, "User level should be 2."

    # Edit user's name by 5 chars name
    visit "/users/#{user.id}/edit"
    fill_in 'Name', with: 'abcde'
    click_button('Update User')

    user = User.where(name: 'abcde').first
    Merit::RankRules.new.check_rank_rules
    user.reload
    assert_equal 5, user.level, "User level should be 5."
  end

  test 'assigning points to a group of records' do
    DummyObserver.any_instance.expects(:update).times(2).with do |hash|
      hash[:description] == 'granted 1 points' &&
      hash[:sash_id] == user('commenter').sash_id
    end
    DummyObserver.any_instance.expects(:update).times(1).with do |hash|
      hash[:description] == 'granted 2 points' &&
      hash[:sash_id] == user('commenter').comments.first.sash_id
    end
    DummyObserver.any_instance.expects(:update).times(1).with do |hash|
      hash[:description] == 'granted 2 points' &&
      hash[:sash_id] == user('commenter').comments.last.sash_id
    end
    DummyObserver.any_instance.expects(:update).times(1).with do |hash|
      hash[:description] == 'granted 5 points' &&
      hash[:sash_id] == user('commenter').sash_id
    end

    comment_1 = user('commenter').comments.create(name: 'a', comment: 'a')
    comment_2 = user('commenter').comments.create(name: 'b', comment: 'b')

    visit comments_path
    # Thanks for voting point, to voted user and it's comments
    # (repeated rule, called twice)
    assert_difference('Merit::ActivityLog.count', 5) do
      within "tr#c_#{comment_2.id}" do
        click_link '1'
      end
    end

    _(comment_1.reload.points).must_be :==, 2
    _(comment_2.reload.points).must_be :==, 2
  end

  test 'api/comments#show should grant 1 point to user' do
    DummyObserver.any_instance.expects(:update).times(1).with do |hash|
      hash[:description] == 'granted 1 points' &&
      hash[:sash_id] == user.sash_id
    end

    assert_equal 0, user.points
    comment = user.comments.create!(name: 'test-comment', comment: 'comment body')

    visit "/api/comments/#{comment.id}"
    assert_equal 1, user.points
  end

  def user(name = 'test-user')
    User.where(name: name).first || User.create(name: name)
  end

  def badges_by_name(user, name)
    user.reload.badges.select{|b| b.name == name }
  end
end
