# frozen_string_literal: true
# rubocop:todo all

require "spec_helper"
require_relative '../has_many_models'

describe Mongoid::Association::Referenced::HasMany::Eager do

  describe ".grouped_docs" do

    let(:docs) do
      Person.all.to_a
    end

    let(:person) do
      Person.create!
    end

    let(:association) do
      Person.reflect_on_association(:posts)
    end

    let(:eager) do
      described_class.new([association], docs).tap do |b|
        b.send(:shift_association)
      end
    end

    before do
      Post.create!(person: person)
    end

    it "aggregates by the parent primary key" do
      expect(eager.send(:grouped_docs).keys).to eq([person.id])
    end
  end

  describe ".set_on_parent" do

    let(:docs) do
      Person.all.to_a
    end

    let(:person) do
      Person.create!
    end

    let(:association) do
      Person.reflect_on_association(:posts)
    end

    let(:eager) do
      described_class.new([association], docs).tap do |b|
        b.send(:shift_association)
      end
    end

    before do
      Post.create!(person: person)
      eager
    end

    it "sets the relation into the parent" do
      docs.each do |doc|
        expect(doc).to receive(:__build__).once.with(:posts, :foo, association)
      end
      eager.send(:set_on_parent, person.id, :foo)
    end

    context 'when able to make query count assertions' do
      # Query count assertions require that all queries are sent using the
      # same connection object.
      require_no_multi_shard

      it "doesnt call an extra query" do
        expect_query(0) do
          eager.send(:set_on_parent, person.id, :foo)
        end
      end
    end
  end

  describe ".includes" do

    let(:person) do
      Person.create!
    end

    context "when including the has_many relation" do
      # Query count assertions require that all queries are sent using the
      # same connection object.
      require_no_multi_shard

      before do
        3.times { Drug.create!(person: person) }
        Drug.create!(person: Person.create!)
      end

      it "queries twice" do

         expect_query(2) do
          Person.all.includes(:drugs).each do |person|
            expect(person.drugs.entries).to_not be_empty
          end
        end
      end

      it "does not query when accessing the base on each document" do
        persons = Person.all.includes(:drugs).to_a
        expect_query(0) do
          persons.each do |person|
            person.drugs.collect(&:person)
          end
        end
      end
    end

    context "when the relation is not polymorphic" do
      # Query count assertions require that all queries are sent using the
      # same connection object.
      require_no_multi_shard

      context "when the eager load has returned documents" do

        let!(:post) do
          person.posts.create!(title: "testing")
        end

        let!(:eager) do
          Person.includes(:posts).last
        end

        it "puts the documents in the parent document" do
          expect(eager.ivar(:posts)).to eq([post])
        end

        it "does not query when touching the association" do
          expect_query(0) do
            expect(eager.posts).to eq([post])
          end
        end

        it "does not query when updating the association" do
          expect_query(0) do
            eager.posts.first.title = "New title"
          end
        end

        it "does not query when accessing the base on each document" do
          expect_query(0) do
            eager.posts.collect(&:person)
          end
        end
      end

      context "when the eager load has not returned documents" do

        before { person }

        let!(:eager) do
          Person.includes(:posts).last
        end

        it "does not set anything on the parent" do
          expect(eager.ivar(:posts)).to be_empty
        end

        it "has an empty proxy" do
          expect(eager.posts).to eq([])
        end

        it "does not query when touching the association" do
          expect_query(0) do
            eager.posts.entries
          end
        end

        it "returns the proxy" do
          expect do
            eager.posts.create!(title: "testing")
          end.to_not raise_error
        end
      end

      context "when the eager load has not returned documents for some" do

        let!(:person_one) do
          person
        end

        let!(:person_two) do
          Person.create!(username: "durran")
        end

        let!(:post) do
          person_one.posts.create!(title: "testing")
        end

        let!(:eager) do
          Person.includes(:posts).asc(:username).to_a
        end

        it "puts the found documents in the parent document" do
          expect(eager.first.ivar(:posts)).to eq([post])
        end

        it "does not set documents not found" do
          expect(eager.last.ivar(:posts)).to be_empty
        end
      end

      context "when the child has a default scope" do

        let(:criteria) do
          Exhibitor.where(:status.ne => "removed")
        end

        let(:exhibitorPresent) do
          Exhibitor.create!(status: "present")
        end

        let(:exhibitorRemoved) do
          Exhibitor.create!(status: "removed")
        end

        let(:exhibitionIncludesExhibitors) do
          Exhibition.includes(:exhibitors).first
        end

        before do
          Exhibitor.default_scope ->{ criteria }
          exhibition = Exhibition.create!
          exhibition.exhibitors << exhibitorPresent
          exhibition.exhibitors << exhibitorRemoved
          exhibitionIncludesExhibitors
        end

        after do
          Exhibitor.default_scoping = nil
        end

        it "does not send another query when the children are accessed" do
          expect_query(0) do
            expect(exhibitionIncludesExhibitors.exhibitors).to eq( [exhibitorPresent] )
          end
        end
      end
    end

    context "when the relation is polymorphic" do

      let!(:movie) do
        Movie.create!(name: "Bladerunner")
      end

      let!(:rating) do
        movie.ratings.create!(value: 10)
      end

      let!(:eager) do
        Movie.includes(:ratings).first
      end

      it "puts the found documents in the parent document" do
        expect(eager.ivar(:ratings)).to eq([rating])
      end

      it "does not query when touching the association" do
        expect_query(0) do
          expect(eager.ratings).to eq([rating])
        end
      end
    end

    context "when the association has scope" do
      let!(:trainer1) { HmmTrainer.create!(name: 'Dave') }
      let!(:trainer2) { HmmTrainer.create!(name: 'Ash') }
      let!(:animal1) { HmmAnimal.create!(taxonomy: 'reptile', trainer: trainer1) }
      let!(:animal2) { HmmAnimal.create!(taxonomy: 'bird', trainer: trainer1) }

      let(:eager) do
        HmmTrainer.includes(:animals).where(_id: trainer1._id).to_a.first
      end

      it 'eager loads the included docs' do
        expect(eager.animals._loaded).to eq(animal1._id => animal1)
        expect(eager.animals).to eq [animal1]
      end
    end
  end
end
