# frozen_string_literal: true
# rubocop:todo all

require "spec_helper"

describe "Syncable Association" do
  config_override :raise_not_found_error, true

  before(:all) do
    Person._synced(Person.relations["preferences"])
    Person.has_and_belongs_to_many :preferences
    Preference.has_and_belongs_to_many :people
  end

  after(:all) do
    Person.reset_callbacks(:save)
    Person.reset_callbacks(:destroy)
  end

  describe 'Model loading' do

    let(:model_synced) do
      class TestModel
        include Mongoid::Document
        field :_synced
      end
    end

    let(:model_synced?) do
      class TestModel
        include Mongoid::Document
        field :_synced?
      end
    end

    let(:model_syncable?) do
      class TestModel
        include Mongoid::Document
        field :_syncable?
      end
    end

    after do
      Object.send(:remove_const, :TestModel)
    end

    it 'prohibits the use of :_synced as an attribute' do
      expect {
        model_synced
      }.to raise_exception(Mongoid::Errors::InvalidField)
    end

    it 'prohibits the use of :_synced? as an attribute' do
      expect {
        model_synced?
      }.to raise_exception(Mongoid::Errors::InvalidField)
    end

    it 'prohibits the use of :_syncable? as an attribute' do
      expect {
        model_syncable?
      }.to raise_exception(Mongoid::Errors::InvalidField)
    end
  end

  describe ".update_inverse_keys" do

    let(:agent) do
      Agent.create!
    end

    let(:user) do
      User.create!
    end

    let(:person) do
      Person.create!
    end

    context "when an unpersisted account is created" do

      let(:account) do
        Account.new do |a|
          a.name = "testing"
          a.creator = user
          a.person = person
        end
      end

      it "has persisted :agent" do
        expect(agent.persisted?).to be true
      end

      it "has persisted :user" do
        expect(user.persisted?).to be true
      end

      it "has persisted :person" do
        expect(person.persisted?).to be true
      end

      it "does not have persisted :account" do
        expect(account.persisted?).to be false
      end

      it "has instantiated a .valid? :account" do
        account.valid?
        expect(account.valid?).to be true
      end

      context "and is Persisted" do

        it "is able to :save" do
          expect(account.save!).to be true
        end
      end

      context "check for existing Agent, then Persisted" do

        before do
          account.agents.where(_id: agent.id).exists?
        end

        it "is able to :save" do
          expect(account.save!).to be true
        end
      end
    end
  end

  context "when the inverse of is nil" do

    let(:preference) do
      Preference.new(name: "test")
    end

    let(:article) do
      Article.new
    end

    before do
      article.preferences << preference
    end

    it "does not attempt synchronization" do
      expect { article.save! }.to_not raise_error
    end

    it "sets the one side of the relation" do
      expect(article.preferences).to eq([ preference ])
    end
  end

  context "when first setting by the relation itself" do

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

    let!(:one) do
      Preference.create!(name: "one")
    end

    before do
      person.preferences << one
    end

    it "sets the inverse foreign key" do
      expect(one.person_ids).to eq([ person.id ])
    end

    it "resets the synced flag" do
      expect(person._synced["preference_ids"]).to be false
    end

    context "when subsequently setting with keys" do

      let!(:two) do
        Preference.create!(name: "two")
      end

      before do
        person.preference_ids << two.id
        person.save!
      end

      it "sets the inverse foreign key" do
        expect(two.reload.person_ids).to eq([ person.id ])
      end
    end
  end

  context "when setting new ids" do

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

    let!(:one) do
      Preference.create!(name: "one")
    end

    let!(:two) do
      Preference.create!(name: "two")
    end

    before do
      person.preference_ids = [ one.id, two.id ]
    end

    it "sets the foreign_key" do
      expect(person.preference_ids).to eq([ one.id, two.id ])
    end

    it "does not set the first inverse key" do
      expect(one.reload.person_ids).to be_empty
    end

    it "does not set the second inverse key" do
      expect(two.reload.person_ids).to be_empty
    end

    context "when saving the base" do

      context "when validation passes" do

        before do
          person.save!
        end

        it "persists the foreign_key" do
          expect(person.reload.preference_ids).to eq([ one.id, two.id ])
        end

        it "sets the first inverse key" do
          expect(one.reload.person_ids).to eq([ person.id ])
        end

        it "sets the second inverse key" do
          expect(two.reload.person_ids).to eq([ person.id ])
        end
      end
    end
  end

  context "when replacing ids" do

    let!(:one) do
      Preference.create!(name: "one")
    end

    let!(:two) do
      Preference.create!(name: "two")
    end

    let!(:person) do
      Person.create!(preference_ids: [ one.id, two.id ])
    end

    let!(:three) do
      Preference.create!(name: "three")
    end

    before do
      person.preference_ids = [ three.id ]
    end

    it "sets the foreign_key" do
      expect(person.preference_ids).to eq([ three.id ])
    end

    it "does not remove the first inverse key" do
      expect(one.reload.person_ids).to eq([ person.id ])
    end

    it "does not remove the second inverse key" do
      expect(two.reload.person_ids).to eq([ person.id ])
    end

    it "does not set the third inverse key" do
      expect(three.reload.person_ids).to be_empty
    end

    context "when saving the base" do

      context "when validation passes" do

        before do
          person.save!
        end

        it "persists the foreign_key" do
          expect(person.reload.preference_ids).to eq([ three.id ])
        end

        it "removes the first inverse key" do
          expect(one.reload.person_ids).to be_empty
        end

        it "removes the second inverse key" do
          expect(two.reload.person_ids).to be_empty
        end

        it "sets the third inverse key" do
          expect(three.reload.person_ids).to eq([ person.id ])
        end
      end
    end
  end

  context "when setting ids to empty" do

    let!(:one) do
      Preference.create!(name: "one")
    end

    let!(:two) do
      Preference.create!(name: "two")
    end

    let!(:person) do
      Person.create!(preference_ids: [ one.id, two.id ])
    end

    before do
      person.preference_ids = []
    end

    it "sets the foreign_key" do
      expect(person.preference_ids).to be_empty
    end

    it "does not remove the first inverse key" do
      expect(one.reload.person_ids).to eq([ person.id ])
    end

    it "does not remove the second inverse key" do
      expect(two.reload.person_ids).to eq([ person.id ])
    end

    context "when saving the base" do

      context "when validation passes" do

        before do
          person.save!
        end

        it "persists the foreign_key" do
          expect(person.reload.preference_ids).to be_empty
        end

        it "removes the first inverse key" do
          expect(one.reload.person_ids).to be_empty
        end

        it "removes the second inverse key" do
          expect(two.reload.person_ids).to be_empty
        end
      end
    end
  end

  context "when setting ids to nil" do

    let!(:one) do
      Preference.create!(name: "one")
    end

    let!(:two) do
      Preference.create!(name: "two")
    end

    let!(:person) do
      Person.create!(preference_ids: [ one.id, two.id ])
    end

    before do
      person.preference_ids = nil
    end

    it "sets the foreign_key" do
      expect(person.preference_ids).to be_empty
    end

    it "does not remove the first inverse key" do
      expect(one.reload.person_ids).to eq([ person.id ])
    end

    it "does not remove the second inverse key" do
      expect(two.reload.person_ids).to eq([ person.id ])
    end

    context "when saving the base" do

      context "when validation passes" do

        before do
          person.save!
        end

        it "persists the foreign_key" do
          expect(person.reload.preference_ids).to be_empty
        end

        it "removes the first inverse key" do
          expect(one.reload.person_ids).to be_empty
        end

        it "removes the second inverse key" do
          expect(two.reload.person_ids).to be_empty
        end
      end
    end
  end

  context "when destroying" do

    let!(:one) do
      Preference.create!(name: "one")
    end

    let!(:two) do
      Preference.create!(name: "two")
    end

    let!(:person) do
      Person.create!(preferences: [ one, two ])
    end

    context "when destroying the parent" do

      before do
        person.destroy
      end

      it "removes the first inverse key" do
        expect(one.reload.person_ids).to be_empty
      end

      it "removes the second inverse key" do
        expect(two.reload.person_ids).to be_empty
      end
    end

    context "when destroying the child" do

      before do
        one.destroy
      end

      it "removes the inverse key" do
        expect(person.reload.preference_ids).to eq([ two.id ])
      end
    end
  end

  context "when appending an existing document to a new one" do

    let!(:persisted) do
      Tag.create!
    end

    let(:article) do
      Article.new
    end

    before do
      article.tags << persisted
      article.save!
    end

    it "persists the foreign key on the inverse" do
      expect(persisted.article_ids).to eq([ article.id ])
    end

    it "persists the inverse relation" do
      expect(persisted.articles).to eq([ article ])
    end
  end

  context "when the document has an ordering default scope" do

    let!(:dog) do
      Dog.create!(name: "Fido")
    end

    let!(:breed) do
      Breed.new(dog_ids: [ dog.id ])
    end

    before do
      breed.save!
    end

    it "adds the id to the inverse relation" do
      expect(dog.reload.breed_ids).to eq([ breed.id ])
    end
  end

  context "when setting one document on unpersisted HABTM association" do
    let!(:dog) { Dog.create! }
    let!(:breed) { Breed.new }

    before do
      breed.dogs = Dog.all
      breed.save!
    end

    it "sets the foreign key on the other side" do
      expect(dog.reload.breed_ids).to eq([breed._id])
    end
  end
end
