# frozen_string_literal: true
# rubocop:todo all

require "spec_helper"

describe Mongoid::Persistable::Creatable do

  describe ".create" do

    context "when provided an array of attributes" do

      context "when no block is passed" do

        let(:people) do
          Person.create([{ title: "sir" }, { title: "madam" }])
        end

        it "creates the first document" do
          expect(people.first.title).to eq("sir")
        end

        it "persists the first document" do
          expect(people.first).to be_persisted
        end

        it "creates the second document" do
          expect(people.last.title).to eq("madam")
        end

        it "persists the second document" do
          expect(people.last).to be_persisted
        end
      end

      context "when no block is passed" do

        let(:people) do
          Person.create([{ title: "sir" }, { title: "madam" }]) do |person|
            person.age = 36
          end
        end

        it "creates the first document" do
          expect(people.first.title).to eq("sir")
        end

        it "persists the first document" do
          expect(people.first).to be_persisted
        end

        it "passes the block to the first document" do
          expect(people.first.age).to eq(36)
        end

        it "creates the second document" do
          expect(people.last.title).to eq("madam")
        end

        it "persists the second document" do
          expect(people.last).to be_persisted
        end

        it "passes the block to the second document" do
          expect(people.last.age).to eq(36)
        end
      end
    end

    context "when providing attributes" do

      let(:person) do
        Person.create(title: "Sensei")
      end

      it "it saves the document" do
        expect(person).to be_persisted
      end

      it "returns the document" do
        expect(person).to be_a_kind_of(Person)
      end

      context "when creating an embedded document" do

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

        it "persists the document" do
          expect(address).to be_persisted
        end
      end

      context "when creating an embedded document with store_as option" do

        let(:user) do
          User.create
        end

        before(:all) do
          User.embeds_many(
            :addresses,
            class_name: "Address",
            store_as: "user_adresses",
            validate: false
          )
          Address.embedded_in :user
        end

        before do
          user.addresses.create!(city: "nantes")
        end

        let(:document) do
          user.collection.find(_id: user.id).first
        end

        it "should not persist in address key on User document" do
          expect(document.keys).to_not include("addresses")
        end

        it "should persist on user_addesses key on User document" do
          expect(document.keys).to include("user_adresses")
        end
      end
    end

    context "when passing in a block" do

      let(:person) do
        Person.create do |peep|
          peep.ssn = "666-66-6666"
        end
      end

      it "sets the attributes" do
        expect(person.ssn).to eq("666-66-6666")
      end

      it "persists the document" do
        expect(person).to be_persisted
      end
    end

    context "when the document is a subclass of a root class" do
      context "when using the default discriminator key" do
        let!(:browser) do
          Browser.create(version: 3, name: "Test")
        end

        let(:collection) do
          Canvas.collection
        end

        let(:attributes) do
          collection.find({ name: "Test"}).first
        end

        it "persists the versions" do
          expect(attributes["version"]).to eq(3)
        end

        it "persists the type" do
          expect(attributes["_type"]).to eq("Browser")
        end

        it "persists the attributes" do
          expect(attributes["name"]).to eq("Test")
        end
      end

      context "when using a custom discriminator key" do
        before do
          Canvas.discriminator_key = "dkey"
        end

        after do
          Canvas.discriminator_key = nil
        end

        let!(:browser) do
          Browser.create(version: 3, name: "Test")
        end

        let(:collection) do
          Canvas.collection
        end

        let(:attributes) do
          collection.find({ name: "Test"}).first
        end

        it "persists the new discriminator key" do
          expect(attributes["dkey"]).to eq("Browser")
        end
      end

      context "when using a custom discriminator key and discriminator value" do
        before do
          Canvas.discriminator_key = "dkey"
          Browser.discriminator_value = "dvalue"
        end

        after do
          Canvas.discriminator_key = nil
          Browser.discriminator_value = nil
        end

        let!(:browser) do
          Browser.create(version: 3, name: "Test")
        end

        let(:collection) do
          Canvas.collection
        end

        let(:attributes) do
          collection.find({ name: "Test"}).first
        end

        it "persists the new discriminator key" do
          expect(attributes["dkey"]).to eq("dvalue")
        end
      end
    end

    context "when the document is a subclass of a subclass" do

      let!(:firefox) do
        Firefox.create(version: 2, name: "Testy")
      end

      let(:collection) do
        Canvas.collection
      end

      let(:attributes) do
        collection.find({ name: "Testy" }).first
      end

      before do
        Browser.create(name: 'Safari', version: 4.0)
      end

      it "persists the versions" do
        expect(attributes["version"]).to eq(2)
      end

      it "persists the type" do
        expect(attributes["_type"]).to eq("Firefox")
      end

      it "persists the attributes" do
        expect(attributes["name"]).to eq("Testy")
      end

      it "returns the document when querying for superclass" do
        expect(Browser.where(name: "Testy").first).to eq(firefox)
      end

      it "returns the document when querying for root class" do
        expect(Canvas.where(name: "Testy").first).to eq(firefox)
      end

      it "returns on of this subclasses if you find by _type" do
        expect(Canvas.where(:_type.in => ['Firefox']).count).to eq(1)
      end
    end

    context "when the document is subclassed" do

      let!(:firefox) do
        Firefox.create(name: "firefox")
      end

      it "finds the document with String args" do
        expect(Firefox.find(firefox.id.to_s)).to eq(firefox)
      end

      context "when querying for parent documents" do

        let(:canvas) do
          Canvas.where(name: "firefox").first
        end

        it "returns matching subclasses" do
          expect(canvas).to eq(firefox)
        end
      end
    end

    context "when document is a subclass and its parent is an embedded document" do

      let!(:canvas) do
        Canvas.create(name: "canvas")
      end

      before do
        canvas.create_palette
        canvas.palette.tools << Pencil.new
        canvas.palette.tools << Eraser.new
      end

      let(:from_db) do
        Canvas.find(canvas.id)
      end

      it "properly saves the subclasses" do
        expect(from_db.palette.tools.map(&:class)).to eq([Pencil, Eraser])
      end
    end

    context "when creating references_many documents from a parent association" do

      let!(:container) do
        ShippingContainer.create
      end

      let(:driver) do
        Driver.create
      end

      it "does not bleed relations from one subclass to another" do
        expect(Truck.relations.keys).to eq(%w/ shipping_container driver crates seats bed /)
        expect(Car.relations.keys).to eq(%w/ shipping_container driver crates seats /)
      end

      context "when appending new documents" do

        before do
          container.vehicles << Car.new
          container.vehicles << Truck.new
        end

        it "allows STI from << using model.new" do
          expect(container.vehicles.map(&:class)).to eq([ Car, Truck ])
        end
      end

      context "when appending persisted documents" do

        before do
          container.vehicles << Car.create
          container.vehicles << Truck.create
        end

        it "allows STI from << using model.create" do
          expect(container.vehicles.map(&:class)).to eq([ Car, Truck ])
        end
      end

      context "when building related documents" do

        before do
          container.vehicles.build({}, Car).save!
          container.vehicles.build({}, Truck).save!
        end

        it "allows STI from the build call" do
          expect(container.vehicles.map(&:class)).to eq([ Car, Truck ])
        end
      end

      context "when building with a type attribute" do

        before do
          container.vehicles.build({ "_type" => "Car" })
          container.vehicles.build({ "_type" => "Truck" })
        end

        it "respects the _type attribute from the build call" do
          expect(container.vehicles.map(&:class)).to eq([ Car, Truck ])
        end
      end

      context "when creating related documents" do

        before do
          container.vehicles.create({}, Car)
          container.vehicles.create({}, Truck)
        end

        it "allows STI from the create call" do
          expect(container.vehicles.map(&:class)).to eq([ Car, Truck ])
        end
      end

      context "when creating with a type attribute" do

        before do
          container.vehicles.create({ "_type" => "Car" })
          container.vehicles.create({ "_type" => "Truck" })
        end

        it "respects the _type attribute from the create call" do
          expect(container.vehicles.map(&:class)).to eq([ Car, Truck ])
        end
      end

      context "#find_or_initialize_by" do
        context "when using the default discriminator key" do
          before do
            container.vehicles.find_or_initialize_by({ driver_id: driver.id }, Car)
          end

          it "initializes the given type document" do
            expect(container.vehicles.map(&:class)).to eq([ Car ])
          end

          it "initializes with the given attributes" do
            expect(container.vehicles.map(&:driver)).to eq([ driver ])
          end

          it "initializes with a type field that equals the class" do
            expect(
              container.vehicles.first.attributes["_type"]
            ).to eq("Car")
          end
        end

        context "when using a custom discriminator key" do
          before do
            Vehicle.discriminator_key = "dkey"
            container.vehicles.find_or_initialize_by({ driver_id: driver.id }, Car)
          end

          it "initializes the given type document" do
            expect(container.vehicles.map(&:class)).to eq([ Car ])
          end

          it "initializes with the given attributes" do
            expect(container.vehicles.map(&:driver)).to eq([ driver ])
          end

          it "initializes with a dkey field that equals the class" do
            expect(
              container.vehicles.first.attributes["dkey"]
            ).to eq("Car")
          end
        end
      end

      context "#find_or_create_by" do

        before do
          container.vehicles.find_or_create_by({ driver_id: driver.id }, Car)
        end

        it "creates the given type document" do
          expect(container.vehicles.map(&:class)).to eq([ Car ])
        end

        it "creates with the given attributes" do
          expect(container.vehicles.map(&:driver)).to eq([ driver ])
        end

        it "creates the correct number of documents" do
          expect(container.vehicles.size).to eq(1)
        end

        context "when executing with a found document" do

          before do
            container.vehicles.find_or_create_by({ driver_id: driver.id }, Car)
          end

          it "does not create an additional document" do
            expect(container.vehicles.size).to eq(1)
          end
        end

        context "when executing with an additional new document" do

          before do
            container.vehicles.find_or_create_by({ driver_id: driver.id }, Truck)
          end

          it "creates the new additional document" do
            expect(container.vehicles.size).to eq(2)
          end
        end

        context 'when searching by a Time value' do

          let!(:account) do
            Account.create!(name: 'test', period_started_at: Time.now.utc)
          end

          let!(:queried_consumption) do
            account.consumption_periods.find_or_create_by(started_at: account.period_started_at)
          end

          before do
            account.reload
          end

          it 'does not change the Time value' do
            expect(queried_consumption).to eq(account.current_consumption)
          end
        end
      end

      context "#find_or_create_by!" do

        before do
          container.vehicles.find_or_create_by!({ driver_id: driver.id }, Car)
        end

        it "creates the given type document" do
          expect(container.vehicles.map(&:class)).to eq([ Car ])
        end

        it "creates with the given attributes" do
          expect(container.vehicles.map(&:driver)).to eq([ driver ])
        end

        it "creates the correct number of documents" do
          expect(container.vehicles.size).to eq(1)
        end

        context "when executing with a found document" do

          before do
            container.vehicles.find_or_create_by!({ driver_id: driver.id }, Car)
          end

          it "does not create an additional document" do
            expect(container.vehicles.size).to eq(1)
          end
        end

        context "when executing with an additional new document" do

          before do
            container.vehicles.find_or_create_by!({ driver_id: driver.id }, Truck)
          end

          it "creates the new additional document" do
            expect(container.vehicles.size).to eq(2)
          end
        end
      end
    end
  end

  describe ".create!" do

    context "when provided an array of attributes" do

      context "when no block is passed" do

        let(:people) do
          Person.create!([{ title: "sir" }, { title: "madam" }])
        end

        it "creates the first document" do
          expect(people.first.title).to eq("sir")
        end

        it "persists the first document" do
          expect(people.first).to be_persisted
        end

        it "creates the second document" do
          expect(people.last.title).to eq("madam")
        end

        it "persists the second document" do
          expect(people.last).to be_persisted
        end
      end

      context "when no block is passed" do

        let(:people) do
          Person.create!([{ title: "sir" }, { title: "madam" }]) do |person|
            person.age = 36
          end
        end

        it "creates the first document" do
          expect(people.first.title).to eq("sir")
        end

        it "persists the first document" do
          expect(people.first).to be_persisted
        end

        it "passes the block to the first document" do
          expect(people.first.age).to eq(36)
        end

        it "creates the second document" do
          expect(people.last.title).to eq("madam")
        end

        it "persists the second document" do
          expect(people.last).to be_persisted
        end

        it "passes the block to the second document" do
          expect(people.last.age).to eq(36)
        end
      end
    end

    context "inserting with a field that is not unique" do

      context "when a unique index exists" do

        before do
          Person.index({ ssn: 1 }, { unique: true })
          Person.create_indexes
        end

        after do
          Person.collection.drop
        end

        it "raises an error" do
          expect {
            4.times { Person.create!(ssn: "555-55-1029") }
          }.to raise_error(Mongo::Error::OperationFailure)
        end
      end
    end

    context "when passing in a block" do

      let(:person) do
        Person.create! do |peep|
          peep.ssn = "666-66-6666"
        end
      end

      it "sets the attributes" do
        expect(person.ssn).to eq("666-66-6666")
      end

      it "persists the document" do
        expect(person).to be_persisted
      end
    end

    context "when setting the composite key" do

      let(:account) do
        Account.create!(name: "Hello")
      end

      it "saves the document" do
        expect(account).to be_persisted
      end
    end

    context "when a callback aborts the chain" do

      it "raises a callback error" do
        expect { Oscar.create! }.to raise_error(Mongoid::Errors::Callback)
      end
    end
  end
end
