# frozen_string_literal: true
# rubocop:todo all

require "spec_helper"
require_relative '../embeds_many_models'

describe Mongoid::Association::Embedded::EmbeddedIn::Proxy do

  describe "#===" do

    let(:base) do
      Name.new
    end

    let(:target) do
      Person.new
    end

    let(:association) do
      Name.relations["namable"]
    end

    let(:relation) do
      described_class.new(base, target, association)
    end

    context "when the proxied document is same class" do

      context "when the document is a different instance" do

        it "returns false" do
          expect((relation === Person.new)).to be false
        end
      end

      context "when the document is the same instance" do

        it "returns true" do
          expect((relation === target)).to be true
        end
      end
    end
  end

  describe "#=" do

    context "when the inverse of an embeds one" do

      context "when the child is a new record" do

        let(:person) do
          Person.new
        end

        let(:name) do
          Name.new
        end

        before do
          name.namable = person
        end

        it "sets the target of the relation" do
          expect(name.namable).to eq(person)
        end

        it "sets the base on the inverse relation" do
          expect(person.name).to eq(name)
        end

        it "sets the same instance on the inverse relation" do
          expect(person.name).to eql(name)
        end

        it "does not save the target" do
          expect(person).to_not be_persisted
        end
      end

      context "when the parent is not a new record" do

        let(:person) do
          Person.create!
        end

        let(:name) do
          Name.new
        end

        before do
          name.namable = person
        end

        it "sets the target of the relation" do
          expect(name.namable).to eq(person)
        end

        it "sets the base on the inverse relation" do
          expect(person.name).to eq(name)
        end

        it "sets the same instance on the inverse relation" do
          expect(person.name).to eql(name)
        end

        it "does not save the base" do
          expect(name).to_not be_persisted
        end
      end
    end

    context "when the inverse of an embeds many" do

      context "when the child is a new record" do

        let(:person) do
          Person.new
        end

        let(:address) do
          Address.new
        end

        before do
          address.addressable = person
        end

        it "sets the target of the relation" do
          expect(address.addressable).to eq(person)
        end

        it "appends the base on the inverse relation" do
          expect(person.addresses).to eq([ address ])
        end

        it "sets the same instance in the inverse relation" do
          expect(person.addresses.first).to eql(address)
        end

        it "does not save the target" do
          expect(person).to_not be_persisted
        end
      end

      context "when the parent is not a new record" do

        let!(:person) do
          Person.create!
        end

        let(:address) do
          Address.new
        end

        before do
          address.addressable = person
        end

        it "sets the target of the relation" do
          expect(address.addressable).to eq(person)
        end

        it "sets the same instance in the inverse relation" do
          expect(person.addresses.first).to eql(address)
        end

        it "appends the base on the inverse relation" do
          expect(person.addresses).to eq([ address ])
        end
      end
    end
  end

  describe "#= nil" do

    context "when the inverse of an embeds one" do

      context "when the parent is a new record" do

        let(:person) do
          Person.new
        end

        let(:name) do
          Name.new
        end

        before do
          name.namable = person
          name.namable = nil
        end

        it "sets the relation to nil" do
          expect(name.namable).to be_nil
        end

        it "removes the inverse relation" do
          expect(person.name).to be_nil
        end
      end

      context "when the inverse is already nil" do

        let(:person) do
          Person.new
        end

        let(:name) do
          Name.new
        end

        before do
          name.namable = nil
        end

        it "sets the relation to nil" do
          expect(name.namable).to be_nil
        end

        it "removes the inverse relation" do
          expect(person.name).to be_nil
        end
      end

      context "when the documents are not new records" do

        let(:person) do
          Person.create!
        end

        let(:name) do
          Name.new
        end

        before do
          name.namable = person
          name.namable = nil
        end

        it "sets the relation to nil" do
          expect(name.namable).to be_nil
        end

        it "removed the inverse relation" do
          expect(person.name).to be_nil
        end

        it "deletes the child document" do
          expect(name).to be_destroyed
        end
      end
    end

    context "when the inverse of an embeds many" do

      context "when the parent is a new record" do

        let(:person) do
          Person.new
        end

        let(:address) do
          Address.new
        end

        before do
          address.addressable = person
          address.addressable = nil
        end

        it "sets the relation to nil" do
          expect(address.addressable).to be_nil
        end

        it "removes the inverse relation" do
          expect(person.addresses).to be_empty
        end
      end

      context "when the inverse is already nil" do

        let(:address) do
          Address.new
        end

        before do
          address.addressable = nil
        end

        it "sets the relation to nil" do
          expect(address.addressable).to be_nil
        end
      end

      context "when the documents are not new records" do

        let(:person) do
          Person.create!
        end

        let(:address) do
          Address.new
        end

        before do
          address.addressable = person
          address.addressable = nil
        end

        it "sets the relation to nil" do
          expect(address.addressable).to be_nil
        end

        it "removed the inverse relation" do
          expect(person.addresses).to be_empty
        end

        it "deletes the child document" do
          expect(address).to be_destroyed
        end
      end

      context "when a child already exists on the parent" do

        let(:person) do
          Person.create!
        end

        let(:address_one) do
          Address.new(street: "first")
        end

        let(:address_two) do
          Address.new(street: "second")
        end

        before do
          person.addresses = [ address_one, address_two ]
          address_one.addressable = nil
        end

        it "sets the relation to nil" do
          expect(address_one.addressable).to be_nil
        end

        it "removed the inverse relation" do
          expect(person.addresses).to eq([ address_two ])
        end

        it "deletes the child document" do
          expect(address_one).to be_destroyed
        end

        it "reindexes the children" do
          expect(address_two._index).to eq(0)
        end
      end
    end
  end

  describe ".embedded?" do

    it "returns true" do
      expect(described_class).to be_embedded
    end
  end

  describe "#respond_to?" do

    let(:person) do
      Person.new
    end

    let!(:name) do
      person.build_name(first_name: "Tony")
    end

    let(:document) do
      name.namable
    end

    Mongoid::Document.public_instance_methods(true).each do |method|

      context "when checking #{method}" do

        it "returns true" do
          expect(document.respond_to?(method)).to be true
        end
      end
    end
  end

  context "when creating the tree through initialization" do

    let!(:person) do
      Person.create!
    end

    let!(:address) do
      Address.create!(addressable: person)
    end

    let!(:first_location) do
      Location.create!(address: address)
    end

    let!(:second_location) do
      Location.create!(address: address)
    end

    it "saves the child" do
      expect(Person.last.addresses.last).to eq(address)
    end

    it "indexes the child" do
      expect(address._index).to eq(0)
    end

    it "saves the first location with the correct index" do
      expect(first_location._index).to eq(0)
    end

    it "saves the second location with the correct index" do
      expect(second_location._index).to eq(1)
    end

    it "has the locations in the association array" do
      expect(Person.last.addresses.last.locations).to eq(
                                                          [first_location, second_location]
                                                      )
    end
  end

  context "when instantiating a new child with a persisted parent" do

    let!(:person) do
      Person.create!
    end

    let!(:address) do
      Address.new(addressable: person)
    end

    let!(:location) do
      Location.new(address: address)
    end

    it "does not save the child" do
      expect(address).to_not be_persisted
    end

    it "does not save the deeply embedded children" do
      expect(address.locations.first).to_not be_persisted
    end
  end

  context "when replacing the relation with another" do

    let!(:person) do
      Person.create!
    end

    let!(:person_two) do
      Person.create!
    end

    let!(:address) do
      person.addresses.create!(street: "Kotbusser Damm")
    end

    let!(:name) do
      person_two.create_name(first_name: "Syd")
    end

    before do
      name.namable = address.addressable
      name.namable.save!
    end

    it "sets the new parent" do
      expect(name.namable).to eq(person)
    end

    it "removes the previous parent relation" do
      expect(person_two.name).to be_nil
    end

    it "sets the new child relation" do
      expect(person.name).to eq(name)
    end

    context "when reloading" do

      before do
        person.reload
      end

      it "sets the new parent" do
        expect(name.namable).to eq(person)
      end

      it "removes the previous parent relation" do
        expect(person_two.name).to be_nil
      end

      it "sets the new child relation" do
        expect(person.name).to eq(name)
      end
    end
  end

  context "when the same class is embedded multiple times; embeds_one" do

    let(:customer) do
      Customer.new
    end

    context "assignment after saving" do

      it "correctly sets the association for the embedded class" do

        customer.home_address = CustomerAddress.new
        customer.work_address = CustomerAddress.new

        expect(customer.home_address._association.store_as).to eq("home_address")
        expect(customer.work_address._association.store_as).to eq("work_address")

        expect(customer.home_address.instance_eval { _association.store_as }).to eq("home_address")
        expect(customer.work_address.instance_eval { _association.store_as }).to eq("work_address")

        customer.save!

        customer.home_address = CustomerAddress.new
        customer.work_address = CustomerAddress.new

        expect(customer.home_address._association.store_as).to eq("home_address")
        expect(customer.work_address._association.store_as).to eq("work_address")

        expect(customer.home_address.instance_eval { _association.store_as }).to eq("home_address")
        expect(customer.work_address.instance_eval { _association.store_as }).to eq("work_address")

        expect(customer.home_address.addressable).to eq(customer)
        expect(customer.work_address.addressable).to eq(customer)
      end
    end

    context "inverse assignment" do

      it "raises an error when trying to set the association" do
        customer.work_address = CustomerAddress.new

        expect do
          customer.work_address.addressable = customer
        end.to raise_error(Mongoid::Errors::InvalidSetPolymorphicRelation)
      end
    end

    context "when there is an explicit inverse_of" do

      let(:customer) { EmmCustomer.new }

      it "correctly sets the association for the embedded class" do
        customer.work_address = EmmCustomerAddress.new

        expect do
          customer.work_address.addressable = customer
        end.to_not raise_error

        expect(customer.work_address.addressable).to eq(customer)
      end
    end
  end

  context "when the same class is embedded multiple times; embeds_many" do

    let(:customer) do
      EmmCustomer.new
    end

    context "assignment after saving" do

      it "correctly sets the association for the embedded class" do

        customer.close_friends = [EmmFriend.new]
        customer.acquaintances = [EmmFriend.new]

        expect(customer.close_friends._association.store_as).to eq("close_friends")
        expect(customer.acquaintances._association.store_as).to eq("acquaintances")

        expect(customer.close_friends[0].instance_eval { _association.store_as }).to eq("close_friends")
        expect(customer.acquaintances[0].instance_eval { _association.store_as }).to eq("acquaintances")

        customer.save!

        customer.close_friends = [EmmFriend.new]
        customer.acquaintances = [EmmFriend.new]

        expect(customer.close_friends._association.store_as).to eq("close_friends")
        expect(customer.acquaintances._association.store_as).to eq("acquaintances")

        expect(customer.close_friends[0].instance_eval { _association.store_as }).to eq("close_friends")
        expect(customer.acquaintances[0].instance_eval { _association.store_as }).to eq("acquaintances")

        expect(customer.close_friends[0].befriendable).to eq(customer)
        expect(customer.acquaintances[0].befriendable).to eq(customer)
      end
    end

    context "inverse assignment" do

      it "raises an error when trying to set the association" do
        customer.acquaintances = [EmmFriend.new]

        expect do
          customer.acquaintances[0].befriendable = customer
        end.to raise_error(Mongoid::Errors::InvalidSetPolymorphicRelation)
      end
    end
  end

  context "when assigning a hash" do
    let(:building_address) { BuildingAddress.new }

    before do
      building_address.building = { name: "Chrysler" }
    end

    it "creates the objects correctly" do
      expect(building_address.building).to be_a(Building)
      expect(building_address.building.name).to eq("Chrysler")
    end
  end

  context "when replacing an association with a hash" do
    let(:building_address) { BuildingAddress.new }

    before do
      building_address.building = { name: "Chrysler" }
      building_address.building = { name: "Empire State" }
    end

    it "creates the objects correctly" do
      expect(building_address.building).to be_a(Building)
      expect(building_address.building.name).to eq("Empire State")
    end
  end
end
