# frozen_string_literal: true

require "spec_helper"
require "rails/generators"

RSpec.describe Pet, :versioning do
  it "baseline test setup" do
    expect(described_class.new).to be_versioned
  end

  it "can be reified" do
    person = Person.create(name: "Frank")
    dog = Dog.create(name: "Snoopy")
    cat = Cat.create(name: "Garfield")

    person.pets << described_class.create(animal: dog)
    person.pets << described_class.create(animal: cat)
    person.update(name: "Steve")

    dog.update(name: "Beethoven")
    cat.update(name: "Sylvester")
    person.update(name: "Peter")

    expect(person.reload.versions.length).to(eq(3))
  end

  context "when an older version entry's item_type refers to the base_class" do
    let(:cat) { Cat.create(name: "Garfield") }   # Index 0
    let(:animal) { Animal.create }               # Index 4

    before do
      # This line runs the `let` for :cat, creating two entries
      cat.update(name: "Sylvester")   # Index 1 - second
      cat.update(name: "Cheshire")    # Index 2 - third
      cat.destroy # Index 3 - fourth

      # Prior to PR#1143 a subclassed version's item_subtype would be nil.  In order to simulate
      # an entry having been made in the old way, set one of the item_subtype entries to be nil
      # instead of "Cat".
      versions = PaperTrail::Version.order(:id)
      versions.second.update(item_subtype: nil)

      # This line runs the `let` for :animal, creating two entries
      animal.update(name: "Muppets Drummer")    # Index 5
      animal.destroy                            # Index 6
    end

    it "can reify a subclassed item" do
      versions = PaperTrail::Version.order(:id)
      # Still the reification process correctly brings back Cat since `species` is
      # properly set to this sub-classed name.
      expect(versions.second.reify).to be_a(Cat) # Sylvester
      expect(versions.third.reify).to be_a(Cat) # Cheshire
      expect(versions.fourth.reify).to be_a(Cat) # Cheshire that was destroyed
      # Creating an object from the base class is correctly identified as "Animal"
      expect(versions[5].reify).to be_an(Animal) # Muppets Drummer
      expect(versions[6].reify).to be_an(Animal) # Animal that was destroyed
    end

    it "has a generator that builds migrations to upgrade older entries" do
      # Only newer versions have item_subtype that refers directly to the subclass name.
      expect(PaperTrail::Version.where(item_subtype: "Cat").count).to eq(3)

      # To have has_many :versions work properly, you can generate and run a migration
      # that examines all existing models to identify use of STI, then updates all older
      # version entries that may refer to the base_class so they refer to the subclass.
      # (This is the same as running:  rails g paper_trail:update_sti; rails db:migrate)
      migrator = PaperTrailSpecMigrator.new
      expect {
        migrator.generate_and_migrate("paper_trail:update_item_subtype", [])
      }.to output(/Associated 1 record to Cat/).to_stdout

      # And now it finds all four changes
      cat_versions = PaperTrail::Version.where(item_subtype: "Cat").order(:id).to_a
      expect(cat_versions.length).to eq(4)
      expect(cat_versions.map(&:event)).to eq(%w[create update update destroy])

      # And Animal is unaffected
      animal_versions = PaperTrail::Version.where(item_subtype: "Animal").order(:id).to_a
      expect(animal_versions.length).to eq(3)
      expect(animal_versions.map(&:event)).to eq(%w[create update destroy])
    end

    # After creating a bunch of records above, we change the inheritance_column
    # so that we can demonstrate passing hints to the migration generator.
    context "when there was a historical change to inheritance_column" do
      before do
        Animal.inheritance_column = "species_xyz"
      end

      after do
        # Clean up the temporary switch-up
        Animal.inheritance_column = "species"
      end

      it "no hints given to generator, does not generate the correct migration" do
        # Because of the change to inheritance_column, the generator `rails g
        # paper_trail:update_sti` would be unable to determine the previous
        # inheritance_column, so a generated migration *with no hints* would
        # accomplish nothing.
        migrator = PaperTrailSpecMigrator.new
        hints = []
        expect {
          migrator.generate_and_migrate("paper_trail:update_item_subtype", hints)
        }.not_to output(/Associated 1 record to Cat/).to_stdout

        expect(PaperTrail::Version.where(item_subtype: "Cat").count).to eq(3)
        # And older Cat changes remain as nil.
        expect(PaperTrail::Version.where(item_subtype: nil, item_id: cat.id).count).to eq(1)
      end

      it "giving hints to the generator, updates older entries in a custom way" do
        # Pick up all version IDs regarding our single cat Garfield / Sylvester / Cheshire
        cat_ids = PaperTrail::Version.where(item_type: "Animal", item_id: cat.id).
          order(:id).pluck(:id)
        # This time (as opposed to above example) we are going to provide hints
        # to the generator.
        #
        # You can specify custom inheritance_column settings over a range of
        # IDs so that the generated migration will properly update all your historic versions,
        # having them now to refer to the proper subclass.

        # This is the same as running:
        #   rails g paper_trail:update_sti Animal(species):1..4; rails db:migrate
        migrator = PaperTrailSpecMigrator.new
        hints = ["Animal(species):#{cat_ids.first}..#{cat_ids.last}"]
        expect {
          migrator.generate_and_migrate("paper_trail:update_item_subtype", hints)
        }.to output(/Associated 1 record to Cat/).to_stdout

        # And now the has_many :versions properly finds all four changes
        cat_versions = cat.versions.order(:id).to_a
        expect(cat_versions.length).to eq(4)
        expect(cat_versions.map(&:event)).to eq(%w[create update update destroy])

        # And Animal is still unaffected
        animal_versions = animal.versions.order(:id).to_a
        expect(animal_versions.length).to eq(3)
        expect(animal_versions.map(&:event)).to eq(%w[create update destroy])
      end
    end
  end
end
