require File.expand_path('../abstract_unit', __FILE__)

class TestAssociations < ActiveSupport::TestCase
  fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants,
           :dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings,
           :departments, :employees, :memberships, :membership_statuses

  def test_products
    assert_not_nil products(:first_product).product_tariffs
    assert_equal 2, products(:first_product).product_tariffs.length
    assert_not_nil products(:first_product).tariffs
    assert_equal 2, products(:first_product).tariffs.length
  end

  def test_product_tariffs
    assert_not_nil product_tariffs(:first_flat).product
    assert_not_nil product_tariffs(:first_flat).tariff
    assert_equal Product, product_tariffs(:first_flat).product.class
    assert_equal Tariff, product_tariffs(:first_flat).tariff.class
  end

  def test_tariffs
    assert_not_nil tariffs(:flat).product_tariffs
    assert_equal 1, tariffs(:flat).product_tariffs.length
    assert_not_nil tariffs(:flat).products
    assert_equal 1, tariffs(:flat).products.length
  end

  # Its not generating the instances of associated classes from the rows
  def test_find_includes
    products = Product.includes(:product_tariffs)
    assert_equal(3, products.length)
    assert_equal(3, products.inject(0) {|sum, product| sum + product.product_tariffs.length})
  end

  def test_find_includes_2
    products = ProductTariff.where(:tariff_id => 2).order('product_id, tariff_id').includes(:tariff)
    assert_equal(2, products.length)
  end

  def test_find_includes_eager_loading
    product = products(:second_product)
    product_tarrif = product_tariffs(:second_free)

    # First get a legitimate product tarrif
    products = Product.includes(:product_tariffs).where('product_tariffs.product_id = ?', product.id).references(:product_tariffs)
    assert_equal(1, products.length)
    assert_equal(product, products.first)
    assert_equal([product_tarrif], products.first.product_tariffs)
  end

  def test_find_eager_loading_none
    product = products(:third_product)

    products = Product.includes(:product_tariffs).where(:id => product.id).references(:product_tariffs)
    assert_equal(1, products.length)
    assert_equal(product, products.first)
    assert_empty(products.first.product_tariffs)
  end

  def test_find_includes_tariffs
    tariffs = Tariff.includes(:product_tariffs)
    assert_equal(3, tariffs.length)
    assert_equal(3, tariffs.inject(0) {|sum, tariff| sum + tariff.product_tariffs.length})
  end

  def test_find_each_does_not_throw_error
    tariffs = Tariff.includes(:product_tariffs)
    worked = false
    tariffs.first.product_tariffs.order(:tariff_id).find_each do |pt|
      worked = true
    end
    assert worked
  end

  def test_association_with_composite_primary_key_can_be_autosaved
    room = Room.new(dorm_id: 1000, room_id: 1001)
    room_assignment = RoomAssignment.new(student_id: 1000)
    room_assignment.room = room
    room_assignment.save
    room_assignment.reload
    assert_equal(room_assignment.dorm_id, 1000)
    assert_equal(room_assignment.room_id, 1001)
  end

  def test_has_one_association_is_not_cached_to_where_it_returns_the_wrong_one
    engineering = departments(:engineering)
    engineering_head = engineering.head
    assert_equal(employees(:sarah), engineering_head)

    accounting = departments(:accounting)
    accounting_head = accounting.head
    assert_equal(employees(:steve), accounting_head)

    refute_equal accounting_head, engineering_head
  end

  def test_has_one_association_primary_key_and_foreign_key_are_present
    # department = departments(:engineering)
    # assert_equal(2, department.employees.count)
    # assert_equal('Sarah', department.employees[0].name)
    # assert_equal('Robert', department.employees[1].name)
    # assert_equal('Sarah', department.head.name)

    department = departments(:human_resources)
    assert_equal(1, department.employees.count)
    assert_equal('Mindy', department.employees[0].name)
    assert_equal('Mindy', department.head.name)

    head = department.create_head(name: 'Rick')
    assert_equal(department, head.department)
    assert_equal('Rick', department.head.name)

    department.reload
    assert_equal(1, department.employees.count)
  end

  def test_has_one_autosave
    department = departments(:engineering)
    assert_equal('Sarah', department.head.name)

    department.head.name = 'Sarah1'
    department.save!
    assert_equal('Sarah1', department.head.name)
  end

  def test_has_no_autosave
    department = departments(:engineering)
    assert_equal('Sarah', department.head_without_autosave.name)

    department.head_without_autosave.name = 'Sarah1'
    department.save!
    assert_equal('Sarah1', department.head_without_autosave.name)
  end

  def test_has_many_association_is_not_cached_to_where_it_returns_the_wrong_ones
    engineering = departments(:engineering)
    engineering_employees = engineering.employees

    accounting = departments(:accounting)
    accounting_employees = accounting.employees

    refute_equal accounting_employees, engineering_employees
  end

  def test_has_many_association_primary_key_and_foreign_key_are_present
    department = departments(:accounting)
    assert_equal(2, department.employees.count)
    assert_equal('Steve', department.employees[0].name)
    assert_equal('Jill', department.employees[1].name)

    department.employees.create(name: 'Rick')

    department.reload
    assert_equal(3, department.employees.count)
    employees = department.employees.sort_by(&:name)
    assert_equal('Jill', employees[0].name)
    assert_equal('Rick', employees[1].name)
    assert_equal('Steve', employees[2].name)
  end

  def test_find_includes_product_tariffs_product
    # Old style
    product_tariffs = ProductTariff.includes(:product)
    assert_not_nil(product_tariffs)
    assert_equal(3, product_tariffs.length)

    # New style
    product_tariffs = ProductTariff.includes(:product)
    assert_not_nil(product_tariffs)
    assert_equal(3, product_tariffs.length)
  end

  def test_find_includes_product_tariffs_tariff
    # Old style
    product_tariffs = ProductTariff.includes(:tariff)
    assert_equal(3, product_tariffs.length)

    # New style
    product_tariffs = ProductTariff.includes(:tariff)
    assert_equal(3, product_tariffs.length)
  end

  def test_has_many_through
    products = Product.includes(:tariffs)
    assert_equal(3, products.length)

    tarrifs_length = products.inject(0) {|sum, product| sum + product.tariffs.length}
    assert_equal(3, tarrifs_length)
  end

  def test_has_many_through_2
    assert_equal(3, Article.count)
    user = users(:santiago)
    article_names = user.articles.map(&:name).sort
    assert_equal(['Article One', 'Article Two'], article_names)
  end

  def test_new_style_includes_with_conditions
    product_tariff = ProductTariff.includes(:tariff).where('tariffs.amount < 5').references(:tariffs).first
    assert_equal(0, product_tariff.tariff.amount)
  end

  def test_find_product_includes
    products = Product.includes(:product_tariffs => :tariff)
    assert_equal(3, products.length)

    product_tariffs_length = products.inject(0) {|sum, product| sum + product.product_tariffs.length}
    assert_equal(3, product_tariffs_length)
  end

  def test_has_many_through_when_not_pre_loaded
    student = Student.first
    rooms = student.rooms
    assert_equal(1, rooms.size)
    assert_equal(1, rooms.first.dorm_id)
    assert_equal(1, rooms.first.room_id)
  end

  def test_has_many_through_when_through_association_is_composite
    dorm = Dorm.first
    assert_equal(3, dorm.rooms.length)
    assert_equal(1, dorm.rooms.first.room_attributes.length)
    assert_equal('type', dorm.rooms.first.room_attributes.first.name)
  end

  def test_associations_with_conditions
    suburb = Suburb.find([2, 2])
    assert_equal 2, suburb.streets.size
    assert_equal 1, suburb.first_streets.size
  end

  def test_composite_has_many_composites
    room = rooms(:branner_room_1)
    assert_equal(2, room.room_assignments.length)
    assert_equal(room_assignments(:jacksons_room), room.room_assignments[0])
    assert_equal(room_assignments(:bobs_room), room.room_assignments[1])
  end

  def test_composite_belongs_to_composite
    room_assignment = room_assignments(:jacksons_room)
    assert_equal(rooms(:branner_room_1), room_assignment.room)
  end

  def test_composite_belongs_to_changes
    room_assignment = room_assignments(:jacksons_room)
    room_assignment.room = rooms(:branner_room_2)
    # This was raising an error before:
    #   TypeError: [:dorm_id, :room_id] is not a symbol
    # changes returns HashWithIndifferentAccess
    assert_equal({"room_id"=>[1, 2]}, room_assignment.changes)

    steve = employees(:steve)
    steve.department = departments(:engineering)
    # It was returning this before:
    #   {"[:id, :location_id]"=>[nil, [2, 1]]}
    assert_equal({"department_id"=>[1, 2]}, steve.changes)
  end

  def test_composite_belongs_to_setting_to_nil
    room_assignment = room_assignments(:jacksons_room)
    # This was raising an error before:
    #   NoMethodError: undefined method `length' for nil:NilClass
    assert_nothing_raised { room_assignment.room = nil }
  end

  def test_has_one_with_composite
    # In this case a regular model has_one composite model
    department = departments(:engineering)
    assert_not_nil(department.head)
  end

  def test_has_many_build_simple_key
    user = users(:santiago)
    reading = user.readings.build
    assert_equal user.id, reading.user_id
    assert_equal user,    reading.user
  end

  def test_has_many_build_composite_key
    department = departments(:engineering)
    employee = department.employees.build
    assert_equal(department[:id], employee.department_id)
    assert_equal(department.location_id, employee.location_id)
    assert_equal(department, employee.department)
  end

  def test_has_many_with_primary_key
    @membership = Membership.find([1, 1])
    assert_equal 2, @membership.readings.size
  end

  def test_has_many_with_composite_key
    # In this case a regular model (Dorm) has_many composite models (Rooms)
    dorm = dorms(:branner)
    assert_equal(3, dorm.rooms.length)
    assert_equal(1, dorm.rooms[0].room_id)
    assert_equal(2, dorm.rooms[1].room_id)
    assert_equal(3, dorm.rooms[2].room_id)
  end

  def test_has_many_with_foreign_composite_key
    tariff = Tariff.find_by('tariff_id = ?', 2)
    assert_equal(2, tariff.product_tariffs.length)
  end

  def test_joins_has_many_with_primary_key
    #@membership = Membership.find(:first, :joins => :readings, :conditions => { :readings => { :id => 1 } })
    @membership = Membership.joins(:readings).where(readings: { id: 1 }).first

    assert_equal [1, 1], @membership.id
  end

  def test_joins_has_one_with_primary_key
    @membership = Membership.joins(:readings).where(readings: { id: 2 }).first

    assert_equal [1, 1], @membership.id
  end

  def test_has_many_through_with_conditions_when_through_association_is_not_composite
    user = User.first
    assert_equal 1, user.articles.where("articles.name = ?", "Article One").size
  end

  def test_has_many_through_with_conditions_when_through_association_is_composite
    room = Room.first
    assert_equal 0, room.room_attributes.where("room_attributes.name != ?", "type").size
  end

  def test_has_many_through_on_custom_finder_when_through_association_is_composite_finder_when_through_association_is_not_composite
    user = User.first
    assert_equal(1, user.find_custom_articles.size)
  end

  def test_has_many_through_on_custom_finder_when_through_association_is_composite
    room = Room.first
    assert_equal(0, room.find_custom_room_attributes.size)
  end

  def test_has_many_with_primary_key_with_associations
    memberships = Membership.includes(:statuses).where("membership_statuses.status = ?", 'Active').references(:membership_statuses)
    assert_equal(2, memberships.length)
    assert_equal([1,1], memberships[0].id)
    assert_equal([3,2], memberships[1].id)
  end

  def test_limitable_reflections
    memberships = Membership.includes(:statuses).where("membership_statuses.status = ?", 'Active').references(:membership_statuses)
    assert_equal(2, memberships.length)

    assert_equal([1,1], memberships[0].id)
    assert_equal([3,2], memberships[1].id)
  end

  def test_scoped_has_many_with_primary_key_with_associations
    memberships = Membership.joins(:active_statuses)
    assert_equal(2, memberships.length)

    assert_equal([1,1], memberships[0].id)
    assert_equal([3,2], memberships[1].id)
  end

  def test_foreign_key_present_with_null_association_ids
    group = Group.new
    group.memberships.build
    associations = group.association(:memberships)
    assert_equal(false, associations.send('foreign_key_present?'))
  end

  def test_assignment_by_ids_as_arrays
    room = Room.create dorm: dorms(:branner), room_id: 4
    room.room_assignment_ids = [[3, 1, 2], [4, 1, 2]]
    room.save!
    assert_equal room.room_assignments.map(&:student_id), [3, 4]
  end

  def test_assignment_by_ids_as_arrays_that_contains_a_comma
    room = Room.create dorm: dorms(:branner), room_id: 4
    e = assert_raises ActiveRecord::RecordNotFound do
      room.room_assignment_ids = [['5,,', '5,,', '5,,']]
    end
    assert_match /'student_id,dorm_id,room_id'=\[\[5, 5, 5\]\]/, e.message
  end

  def test_assignment_by_ids_as_strings
    room = Room.create dorm: dorms(:branner), room_id: 4
    room.room_assignment_ids = ['3,1,2', '4,1,2']
    room.save!
    assert_equal room.room_assignments.map(&:student_id), [3, 4]
  end

  def test_assignment_by_ids_for_non_CPK_case
    article = Article.new
    article.reading_ids = Reading.pluck(:id)
    assert_equal article.reading_ids, Reading.pluck(:id)
  end

  def test_find_by_association
    assert_equal Membership.where(user: '1').count, 1
    assert_equal Membership.where(user_id: '1').count, 1
    assert_equal Membership.where(user: User.find(1)).count, 1
  end
end
