$LOAD_PATH.unshift File.expand_path('../../test', __FILE__)
require 'test_helper'

class RecommenderTest < Minitest::Test
  def setup
    @user = Factory(:user)
    5.times  { |x| instance_variable_set(:"@user#{x+1}",  Factory(:user))  }
    5.times { |x| instance_variable_set(:"@movie#{x+1}", Factory(:movie)) }
    5.upto(9) { |x| instance_variable_set(:"@movie#{x+1}", Factory(:documentary)) }
    10.times { |x| instance_variable_set(:"@book#{x+1}",  Factory(:book))  }

    [@movie1, @movie2, @movie3, @book4, @book5, @book6].each { |obj| @user.like(obj) }
    [@book1, @book2, @book3, @movie4, @movie5, @movie6].each { |obj| @user.dislike(obj) }

    # @user.similarity_with(@user2) should ==  1.0
    [@movie1, @movie2, @movie3, @book4, @book5, @book6, @book7, @book8, @movie9, @movie10].each { |obj| @user2.like(obj) }
    [@book1, @book2, @book3, @movie4, @movie5, @movie6, @movie8, @movie8, @book9, @book10].each { |obj| @user2.dislike(obj) }

    # @user.similarity_with(@user4) should ==  0.25
    [@movie1, @movie2, @movie3, @book4, @book5, @book6].each { |obj| @user4.like(obj) }
    [@book1, @book2, @book3].each { |obj| @user4.like(obj) }

    # @user.similarity_with(@user3) should ==  0.0
    [@movie1, @movie2, @movie3].each { |obj| @user3.like(obj) }
    [@book1, @book2, @book3].each { |obj| @user3.like(obj) }

    # @user.similarity_with(@user1) should == -0.25
    [@movie1, @movie2, @movie3].each { |obj| @user1.like(obj) }
    [@book1, @book2, @book3, @movie4, @movie5, @movie6].each { |obj| @user1.like(obj) }

    # @user.similarity_with(@user5) should == -1.0
    [@movie1, @movie2, @movie3, @book4, @book5, @book6].each { |obj| @user5.dislike(obj) }
    [@book1, @book2, @book3, @movie4, @movie5, @movie6].each { |obj| @user5.like(obj) }

    Recommendable::Helpers::Calculations.update_similarities_for(@user.id)
    Recommendable::Helpers::Calculations.update_recommendations_for(@user.id)
  end

  def test_similar_raters_returns_sorted_similar_users
    similar_raters = @user.similar_raters(5)

    assert_equal similar_raters[0], @user2
    assert_equal similar_raters[1], @user4
    assert_equal similar_raters[2], @user3
    assert_equal similar_raters[3], @user1
    assert_equal similar_raters[4], @user5
  end

  def test_recommended_for_only_returns_relevant_recommendations
    [@book7, @book8, @book9, @book10].each  { |book|  refute_includes @user.recommended_movies, book }
    [@movie7, @movie8, @movie9, @movie10].each { |movie| refute_includes @user.recommended_books, movie }
  end

  def test_that_it_is_removed_from_recommendable_after_destroy
    Recommendable::Helpers::Calculations.update_similarities_for(@user2.id)

    @user.hide(@movie10)
    @user.hide(@book10)
    @user.bookmark(@movie9)
    @user.bookmark(@book9)

    sets = [
      @user.liked_movie_ids,
      @user.disliked_movie_ids,
      @user.liked_book_ids,
      @user.disliked_book_ids,
      @user.hidden_movie_ids,
      @user.hidden_book_ids,
      @user.bookmarked_movie_ids,
      @user.bookmarked_book_ids,
      @user.recommended_books,
      @user.similar_raters
    ]

    sets.each { |set| refute_empty set }
    assert_includes @user2.similar_raters(5), @user

    redis_sets = [
      Recommendable::Helpers::RedisKeyMapper.liked_set_for(Movie, @user.id),
      Recommendable::Helpers::RedisKeyMapper.liked_set_for(Book, @user.id),
      Recommendable::Helpers::RedisKeyMapper.disliked_set_for(Movie, @user.id),
      Recommendable::Helpers::RedisKeyMapper.disliked_set_for(Book, @user.id),
      Recommendable::Helpers::RedisKeyMapper.hidden_set_for(Movie, @user.id),
      Recommendable::Helpers::RedisKeyMapper.hidden_set_for(Book, @user.id),
      Recommendable::Helpers::RedisKeyMapper.bookmarked_set_for(Movie, @user.id),
      Recommendable::Helpers::RedisKeyMapper.bookmarked_set_for(Book, @user.id),
    ]

    redis_zsets = [
      Recommendable::Helpers::RedisKeyMapper.recommended_set_for(Movie, @user.id),
      Recommendable::Helpers::RedisKeyMapper.recommended_set_for(Book, @user.id),
      Recommendable::Helpers::RedisKeyMapper.similarity_set_for(@user.id)
    ]

    id = @user.id
    @user.destroy

    redis_sets.each  { |set|  assert_equal Recommendable.redis.scard(set),  0 }
    redis_zsets.each { |zset| assert_equal Recommendable.redis.zcard(zset), 0 }

    [@movie1, @movie2, @movie3, @book4, @book5, @book6].each do |obj|
      refute_includes obj.liked_by_ids, id.to_s
    end

    [@book1, @book2, @book3, @movie4, @movie5, @movie6].each do |obj|
      refute_includes obj.disliked_by_ids, id.to_s
    end

    refute_includes @user2.similar_raters(5).map(&:id), id
  end

  def teardown
    Recommendable.redis.flushdb
  end
end
