require 'test_helper'

class TodoTest < ActiveSupport::TestCase
  fixtures :todos, :recurring_todos, :users, :contexts, :preferences, :tags, :taggings, :projects

  def setup
    @not_completed1 = Todo.find(1).reload
    @not_completed2 = Todo.find(2).reload
    @completed = Todo.find(8).reload
  end

  def next_week
    1.week.from_now.beginning_of_day.to_formatted_s(:db)
  end

  # Test loading a todo item
  def test_load
    assert_kind_of Todo, @not_completed1
    assert_equal 1, @not_completed1.id
    assert_equal 1, @not_completed1.context_id
    assert_equal 2, @not_completed1.project_id
    assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
    assert_nil @not_completed1.notes
    assert @not_completed1.completed? == false
    assert_equal 1.week.ago.utc.beginning_of_day, @not_completed1.created_at.utc
    assert_equal 2.week.from_now.utc.beginning_of_day, @not_completed1.due.utc
    assert_nil @not_completed1.completed_at
    assert_equal 1, @not_completed1.user_id
  end

  def test_completed
    assert_kind_of Todo, @completed
    assert @completed.completed?
    assert_not_nil @completed.completed_at
  end

  def test_completed_at_cleared_after_toggle_to_active
    assert_kind_of Todo, @completed
    assert @completed.completed?
    @completed.toggle_completion!
    assert @completed.active?
    assert_nil @completed.completed_at
  end


  # Validation tests
  #
  def test_validate_presence_of_description
    assert_equal "Call dinosaur exterminator", @not_completed2.description
    @not_completed2.description = ""
    assert !@not_completed2.save
    assert_equal 1, @not_completed2.errors.count
    assert_equal "can't be blank", @not_completed2.errors[:description][0]
  end

  def test_validate_length_of_description
    assert_equal "Call dinosaur exterminator", @not_completed2.description
    @not_completed2.description = generate_random_string(Todo::MAX_DESCRIPTION_LENGTH+1)
    assert !@not_completed2.save
    assert_equal 1, @not_completed2.errors.count
    assert_equal "is too long (maximum is #{Todo::MAX_DESCRIPTION_LENGTH} characters)", @not_completed2.errors[:description][0]
  end

  def test_validate_length_of_notes
    assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
    @not_completed2.notes = generate_random_string(Todo::MAX_NOTES_LENGTH+1)
    assert !@not_completed2.save
    assert_equal 1, @not_completed2.errors.count
    assert_equal "is too long (maximum is #{Todo::MAX_NOTES_LENGTH} characters)", @not_completed2.errors[:notes][0]
  end

  def test_validate_show_from_must_be_a_date_in_the_future
    t = @not_completed2
    t.show_from = 1.week.ago

    assert !t.save, "todo should not be saved without validation errors"
    assert_equal 1, t.errors.count
    assert_equal "must be a date in the future", t.errors[:show_from][0]
  end

  def test_validate_circular_dependencies
    @completed.activate!
    @not_completed3=@completed

    # 2 -> 1
    @not_completed1.add_predecessor(@not_completed2)
    assert @not_completed1.save!
    assert_equal 1, @not_completed2.successors.count

    # 3 -> 2 -> 1
    @not_completed2.add_predecessor(@not_completed3)
    assert @not_completed2.save!
    assert_equal 1, @not_completed3.successors.count

    # 1 -> 3 -> 2 -> 1 == circle
    assert_raises ActiveRecord::RecordInvalid do
      @not_completed3.add_predecessor(@not_completed1)
    end
  end

  def test_defer_an_existing_todo
    @not_completed2
    assert_equal :active, @not_completed2.aasm.current_state
    @not_completed2.show_from = Time.zone.now + 1.week
    assert @not_completed2.save, "should have saved successfully " + @not_completed2.errors.full_messages.to_s
    assert_equal :deferred, @not_completed2.aasm.current_state
  end

  def test_create_a_new_deferred_todo
    user = users(:other_user)
    todo = user.todos.build
    todo.show_from = next_week
    todo.context_id = 1
    todo.description = 'foo'
    assert todo.save, "should have saved successfully" + todo.errors.full_messages.to_s
    assert_equal :deferred, todo.aasm.current_state
  end

  def test_create_a_new_deferred_todo_by_passing_attributes
    user = users(:other_user)
    todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
    assert todo.save, "should have saved successfully " + todo.errors.full_messages.to_s
    assert_equal :deferred, todo.aasm.current_state
  end

  def test_toggle_completion
    t = @not_completed1
    assert_equal :active, t.aasm.current_state
    t.toggle_completion!
    assert_equal :completed, t.aasm.current_state
    t.toggle_completion!
    assert_equal :active, t.aasm.current_state
  end

  def test_toggle_completion_with_show_from_in_future
    t = @not_completed1
    t.show_from= 1.week.from_now
    t.save!
    assert_equal :deferred, t.aasm.current_state
    t.toggle_completion!
    assert_equal :completed, t.aasm.current_state
  end

  def test_toggle_completion_with_show_from_in_past
    t = @not_completed1
    t.update_attribute(:show_from, 1.week.ago)
    assert_equal :active, t.aasm.current_state

    assert t.toggle_completion!, "shoud be able to mark active todo complete even if show_from is set in the past"
    assert_equal :completed, t.aasm.current_state
  end

  def test_activate_also_saves
    t = @not_completed1
    t.show_from = 1.week.from_now
    t.save!
    assert t.deferred?
    t.reload
    t.activate!
    assert t.active?
    t.reload
    assert t.active?
  end

  def test_activate_also_clears_show_from
    dates = [1.week.from_now, 1.week.ago]

    dates.each do |show_from_date|
      # setup test case
      t = @not_completed1
      travel_to show_from_date - 1.day do
        t.show_from = show_from_date
        t.save!
        assert t.deferred?
        t.reload
      end

      # activate and check show_from
      t.activate!
      assert t.active?
      assert t.show_from.nil?
    end
  end

  def test_clearing_show_from_activates_todo
    t = @not_completed1
    t.show_from = 1.week.from_now
    t.save!
    t.reload

    assert t.deferred?

    t.show_from = nil
    t.save!
    t.reload

    assert t.active?
  end

  def test_project_returns_null_object_when_nil
    t = @not_completed1
    assert !t.project.is_a?(NullProject)
    t.project = nil
    assert t.project.is_a?(NullProject)
  end

  def test_update_from_project
    # Given a hidden project
    assert_not_nil @not_completed1.project
    project = @not_completed1.project
    project.hide!
    assert project.hidden?
    assert @not_completed1.reload.hidden?

    # When I manually create a new todo in the hidden projct
    new_todo = @not_completed1.user.todos.build(description: "test", context: @not_completed1.context, project: project)
    new_todo.save!
    assert new_todo.active?
    # Then the todo should be hidden
    assert new_todo.hidden?
  end

  def test_initial_state_defaults_to_active
    t = Todo.new
    t.description = 'foo'
    t.context_id = 1
    t.save!
    t.reload
    assert_equal :active, t.aasm.current_state
  end

  def test_initial_state_is_deferred_when_show_from_in_future
    t = Todo.new
    t.user = users(:admin_user)
    t.description = 'foo'
    t.context_id = 1
    t.show_from = 1.week.from_now.to_date
    t.save!
    t.reload
    assert_equal :deferred, t.aasm.current_state
  end

  def test_todo_is_not_starred
    assert !@not_completed1.starred?
  end

  def test_todo_2_is_not_starred
    assert !Todo.find(2).starred?
  end

  def test_todo_is_starred_after_starred_tag_is_added
    @not_completed1._add_tags('starred')
    assert @not_completed1.starred?
  end

  def test_todo_is_starred_after_toggle_starred
    @not_completed1.toggle_star!
    assert @not_completed1.starred?
  end

  def test_todo_is_not_starred_after_toggle_starred_twice
    @not_completed1.toggle_star!
    @not_completed1.toggle_star!
    assert !@not_completed1.starred?
  end

  def test_hidden_todo_remains_hidden_after_getting_unblocked
    todo = todos(:call_bill)
    project=todo.project
    project.hide!

    assert todo.reload.hidden?, "todo in hidden project should be hidden"

    todo2 = todos(:call_dino_ext)
    todo.add_predecessor(todo2)
    todo.block!

    assert todo.pending?, "todo with predecessor should be blocked"

    # cannot activate if part of hidden project
    assert_raise(AASM::InvalidTransition) { todo.activate! }

    todo.remove_predecessor(todo2)
    assert todo.reload.hidden?, "todo should be put back in hidden state"
  end

  def test_todo_specification_handles_null_project
    # @not_completed1 has a project
    todo_desc = @not_completed1.description
    assert_equal "'#{todo_desc}' <'agenda'; 'Make more money than Billy Gates'>", @not_completed1.specification

    # now check on null
    @not_completed1.project = nil
    @not_completed1.save
    assert_equal "'#{todo_desc}' <'agenda'; '(none)'>", @not_completed1.specification
  end

  def test_add_predecessor_list
    todo = Todo.new

    single = @not_completed1.id.to_s
    multi = single + ", " + @not_completed2.id.to_s # note one space after comma

    @predecessor_array = todo.add_predecessor_list(single)
    assert_not_nil @predecessor_array
    assert_equal 1, @predecessor_array.size

    @predecessor_array = todo.add_predecessor_list(multi)
    assert_not_nil @predecessor_array
    assert_equal 2, @predecessor_array.size
  end

  def test_add_predecessor_list_with_comma
    # test for #975
    todo = Todo.new

    @not_completed1.description = "test,1,2,3"
    @not_completed1.save
    @not_completed2.description = "test,4,5,6"
    @not_completed2.save

    single = @not_completed1.id.to_s
    multi = single + "," + @not_completed2.id.to_s  # note no space after comma

    @predecessor_array = todo.add_predecessor_list(single)
    assert_not_nil @predecessor_array
    assert_equal 1, @predecessor_array.size

    @predecessor_array = todo.add_predecessor_list(multi)
    assert_not_nil @predecessor_array
    assert_equal 2, @predecessor_array.size
  end

  def test_add_and_remove_precesessor
    @not_completed1.add_predecessor(@not_completed2)
    @not_completed1.save_predecessors
    # blocking is not done automagically
    @not_completed1.block!

    assert @not_completed1.uncompleted_predecessors?
    assert @not_completed1.pending?, "a todo with predecessors should be pending"

    @not_completed1.remove_predecessor(@not_completed2)
    @not_completed1.save_predecessors

    assert !@not_completed1.uncompleted_predecessors?
    assert @not_completed1.active?, "removing last predecessor should activate todo"
  end

  def test_removing_precesessor_using_new_dependency_list
    # Given three active todos (@not_completed{1,2.3})
    @completed.activate!
    @not_completed3 = @completed

    #When I add two todos as dependency to one todo
    @not_completed1.add_predecessor_list("#{@not_completed2.id}, #{@not_completed3.id}")
    @not_completed1.save_predecessors
    # blocking is not done automagically
    @not_completed1.block!

    # Then @completed1 should have predecessors and should be blocked
    assert @not_completed1.uncompleted_predecessors?
    assert @not_completed1.pending?, "a todo with predecessors should be pending"

    # When I set the predecessors to only todo2
    @not_completed1.add_predecessor_list("#{@not_completed2.id}") #
    @not_completed1.save_predecessors

    # Then todo1 should have only one predecessor and it should be todo2
    assert @not_completed1.uncompleted_predecessors?
    assert_equal 1, @not_completed1.predecessors.count
    assert_equal @not_completed2, @not_completed1.predecessors.first
  end

  def test_finding_todos_with_a_tag
    todo = @not_completed1
    todo.tag_list = "a, b, c"
    todo.save!

    tag_a = Tag.where(:name => "a").first
    tag_b = Tag.where(:name => "b").first
    tag_c = Tag.where(:name => "c").first

    todos_with_a = Todo.with_tag(tag_a)
    assert_equal 1, todos_with_a.count
    assert_equal todo.description, todos_with_a.first.description

    todos_with_b = Todo.with_tag(tag_b)
    assert_equal 1, todos_with_b.count
    assert_equal todo.id, todos_with_b.first.id

    todo2 = @not_completed2
    todo2.tag_list = "a, c, d"
    todo2.save!

    tag_d = Tag.where(:name => "d").first

    todos_with_a = Todo.with_tag(tag_a)
    assert_equal 2, todos_with_a.count

    todos_with_d = Todo.with_tag(tag_d)
    assert_equal 1, todos_with_d.count
  end

  def test_finding_todos_with_more_tags_using_OR
    todo1 = @not_completed1
    todo1.tag_list = "a, b, c"
    todo1.save!

    todo2 = @not_completed2
    todo2.tag_list = "a, c, d"
    todo2.save!

    tag_a = Tag.where(:name => "a").first
    tag_b = Tag.where(:name => "b").first
    tag_c = Tag.where(:name => "c").first
    tag_d = Tag.where(:name => "d").first

    # overlapping tags
    tag_ids = [tag_a.id, tag_c.id]
    todos_with_a_or_c = Todo.with_tags(tag_ids)
    assert_equal 2, todos_with_a_or_c.count

    # non-overlapping tags
    tag_ids = [tag_b.id, tag_d.id]
    todos_with_b_or_d = Todo.with_tags(tag_ids)
    assert_equal 2, todos_with_b_or_d.count
  end

  def test_finding_todos_with_more_tags_using_AND
    todo1 = @not_completed1
    todo1.tag_list = "a, b, c"
    todo1.save!

    todo2 = @not_completed2
    todo2.tag_list = "a, c, d"
    todo2.save!

    tag_a_id = Tag.where(:name => "a").first.id
    tag_b_id = Tag.where(:name => "b").first.id

    todos_with_a_and_b = Todo.with_tags([tag_a_id]).with_tags([tag_b_id])
    assert_equal 1, todos_with_a_and_b.count
    assert_equal todo1.id, todos_with_a_and_b.first.id
  end

  def test_finding_todos_with_more_tags_using_AND_and_OR
    todo1 = @not_completed1
    todo1.tag_list = "a, b, c"
    todo1.save!

    todo2 = @not_completed2
    todo2.tag_list = "a, c, d"
    todo2.save!

    tag_a_id = Tag.where(:name => "a").first.id
    tag_b_id = Tag.where(:name => "b").first.id
    tag_c_id = Tag.where(:name => "c").first.id

    todos_with_aORc_and_b = Todo.with_tags([tag_a_id, tag_c_id]).with_tags([tag_b_id])
    assert_equal 1, todos_with_aORc_and_b.count
    assert_equal todo1.id, todos_with_aORc_and_b.first.id

    # let todo2 fit the expression
    todo2.tag_list = "a, b, r"
    todo2.save!
    todos_with_aORc_and_b = Todo.with_tags([tag_a_id, tag_c_id]).with_tags([tag_b_id])
    assert_equal 2, todos_with_aORc_and_b.count
  end

  def test_find_tagged_todos_of_correct_taggable_type
    recurring = Todo.where(:recurring_todo_id => 1).first.reload
    recurring.tag_list = "recurring_tag"
    recurring.save!
    recurring.recurring_todo.tag_list = "recurring_tag"
    recurring.recurring_todo.save!

    tag_id = Tag.where(:name => "recurring_tag").first.id
    tagged_todos = Todo.with_tag(tag_id)
    assert_equal 1, tagged_todos.count
    assert_equal recurring.id, tagged_todos.first.id
  end

  # test named_scopes
  def test_find_completed
    # Given 2 completed todos, one completed now and one completed 2 months ago
    @not_completed1.toggle_completion!
    @completed.completed_at = 2.months.ago
    @completed.save!

    completed_old = @completed
    completed_now = @not_completed1

    # When I use the finders
    recent_completed_todos = Todo.completed_after(1.month.ago)
    older_completed_todos = Todo.completed_before(1.month.ago)

    # Then completed1 should be before and completed2 should be after a month ago
    assert older_completed_todos.include?(completed_old)
    assert recent_completed_todos.include?(completed_now)

    # And completed1 should not be after and completed2 should not be before a month ago
    assert !older_completed_todos.include?(completed_now)
    assert !recent_completed_todos.include?(completed_old)
  end

  def test_find_created
    # Given 2 created todos, one created now and one created 2 months ago
    user = @completed.user
    todo_old = user.todos.create!({:description => "created long long ago", :context => @completed.context})
    todo_old.created_at = 2.months.ago
    todo_old.save!
    todo_now = user.todos.create!({:description => "just created", :context => @completed.context})

    # When I use the finders
    recent_created_todos = Todo.created_after(1.month.ago)
    older_created_todos = Todo.created_before(1.month.ago)

    # Then todo1 should be before and todo2 should be after a month ago
    assert older_created_todos.include?(todo_old)
    assert recent_created_todos.include?(todo_now)

    # And todo1 should not be after and todo2 should not be before a month ago
    assert !older_created_todos.include?(todo_now)
    assert !recent_created_todos.include?(todo_old)
  end

  def test_notes_are_rendered_on_save
    user = @completed.user
    todo = user.todos.create(:description => "test", :context => @completed.context)

    assert_nil todo.notes

    todo.notes = "*test*"
    todo.save!
    todo.reload

    assert_equal "*test*", todo.notes
  end

  def test_attachments_are_removed_after_delete
    # Given a user and a todo withou any attachments
    todo = @not_completed1
    assert_equal 0, todo.attachments.count, "we start without attachments"
    assert_equal 0, todo.user.attachments.count, "the user has no attachments"

    # When I add a file as attachment to a todo of this user
    attachment = todo.attachments.build
    attachment.file = File.open(File.join(Rails.root, 'test', 'fixtures', 'email_with_multipart.txt'))
    attachment.save!
    new_path = attachment.file.path

    # then the attachment should be there
    assert File.exist?(new_path), "attachment should be on file system"
    assert_equal 1, todo.attachments.reload.count, "should have one attachment"

    # When I destroy the todo
    todo.destroy!

    # Then the attachement and file should nogt be there anymore
    assert_equal 0, todo.user.attachments.reload.count
    assert !File.exist?(new_path), "attachment should not be on file system"
  end

  def test_destroying_action_activates_successors
    @not_completed1.add_predecessor(@not_completed2)
    @not_completed1.block!

    @not_completed2.destroy

    @not_completed1.reload
    assert @not_completed1.active?
  end

end
