require 'test_helper'

module Workarea
  class ReleasableTest < TestCase
    class Foo
      include Mongoid::Document
      include Mongoid::Timestamps
      include Releasable
      field :name, type: String
      field :blank_field, type: String
      field :slug, type: String
      validates :name, presence: true

      embeds_many :bars
      embeds_one :baz
    end

    class Bar
      include Mongoid::Document
      include Releasable
      field :name, type: String
      embedded_in :foo
    end

    class Baz
      include Mongoid::Document
      include Releasable
      field :name, type: String
      embedded_in :foo
    end

    setup :set_release

    def set_release
      @release = create_release
    end

    def test_find
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!

        model.baz.name = 'Baz Changed'
        model.baz.save!
      end

      assert_equal('Test', model.name)
      assert_equal('Bar', model.bars.first.name)
      assert_equal('Baz', model.baz.name)

      Release.with_current(@release.id) do
        new_instance = Foo.find(model.id)
        assert_equal('Changed', new_instance.name)
        assert_equal('Bar Changed', new_instance.bars.first.name)
        assert_equal('Baz Changed', new_instance.baz.name)
      end

      new_instance = Foo.find(model.id)
      assert_equal('Test', new_instance.name)
      assert_equal('Bar', new_instance.bars.first.name)
      assert_equal('Baz', new_instance.baz.name)

      Release.with_current(@release.id) do
        new_instance = Foo.where(id: model.id).first
        assert_equal('Changed', new_instance.name)
        assert_equal('Bar Changed', new_instance.bars.first.name)
        assert_equal('Baz Changed', new_instance.baz.name)
      end

      new_instance = Foo.find(model.id)
      assert_equal('Test', new_instance.name)
      assert_equal('Bar', new_instance.bars.first.name)
      assert_equal('Baz', new_instance.baz.name)

      Release.with_current(@release.id) do
        new_instance = Foo.find_or_create_by(name: 'Test')
        assert_equal('Changed', new_instance.name)
        assert_equal('Bar Changed', new_instance.bars.first.name)
        assert_equal('Baz Changed', new_instance.baz.name)
      end
    end

    def test_save_logs_release_changes_if_current_release
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!

        model.baz.name = 'Baz Changed'
        model.baz.save!
      end

      model.reload

      assert_equal('Test', model.name)
      assert_equal(1, model.changesets.length)
      assert_equal(@release.id, model.changesets.first.release_id)
      assert_equal('Changed', model.changesets.first.changeset['name'])

      assert_equal('Bar', model.bars.first.name)
      assert_equal(1, model.bars.first.changesets.length)
      assert_equal(@release.id, model.bars.first.changesets.first.release_id)
      assert_equal('Bar Changed', model.bars.first.changesets.first.changeset['name'])

      assert_equal('Baz', model.baz.name)
      assert_equal(1, model.baz.changesets.length)
      assert_equal(@release.id, model.baz.changesets.first.release_id)
      assert_equal('Baz Changed', model.baz.changesets.first.changeset['name'])
    end

    def test_save_does_not_log_undone_changes
      model = Foo.create!(
        name: 'Test',
        blank_field: 'test'
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.blank_field = 'changed'
        model.save!

        model.name = 'Test'
        model.blank_field = 'changed'
        model.save!
      end

      assert(model.changesets.first.present?)
      assert_equal('changed', model.changesets.first.changeset['blank_field'])
      assert_nil(model.changesets.first.changeset['name'])

      Release.with_current(@release.id) do
        model.blank_field = 'test'
        model.save!
      end

      assert(model.changesets.empty?)
    end

    def test_save_does_not_log_release_changes_if_invalid
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = ''
        model.bars.first.name = 'Bar Changed'
        model.baz.name = 'Baz Changed'
        refute(model.save)
      end

      assert_equal('', model.name)
      assert(model.changesets.empty?)
      assert(model.bars.first.changesets.empty?)
      assert(model.baz.changesets.empty?)

      model.reload

      assert_equal('Test', model.name)
      assert_equal('Bar', model.bars.first.name)
      assert_equal('Baz', model.baz.name)
      assert(model.changesets.empty?)
      assert(model.bars.first.changesets.empty?)
      assert(model.baz.changesets.empty?)
    end

    def test_save_does_not_make_more_than_one_release_change_per_release
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed 1'
        model.save!

        model.bars.first.name = 'Bar Changed 1'
        model.bars.first.save!

        model.baz.name = 'Baz Changed 1'
        model.baz.save!

        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!

        model.baz.name = 'Baz Changed'
        model.baz.save!
      end

      model.reload

      assert_equal('Test', model.name)
      assert_equal(1, model.changesets.length)
      assert_equal(@release.id, model.changesets.first.release_id)
      assert_equal('Changed', model.changesets.first.changeset['name'])

      assert_equal('Bar', model.bars.first.name)
      assert_equal(1, model.bars.first.changesets.length)
      assert_equal(@release.id, model.bars.first.changesets.first.release_id)
      assert_equal('Bar Changed', model.bars.first.changesets.first.changeset['name'])

      assert_equal('Baz', model.baz.name)
      assert_equal(1, model.baz.changesets.length)
      assert_equal(@release.id, model.baz.changesets.first.release_id)
      assert_equal('Baz Changed', model.baz.changesets.first.changeset['name'])
    end

    def test_save_does_not_save_timestamp_changes
      model = Foo.create!(name: 'Test')

      Release.with_current(@release.id) do
        model.update_attributes!(name: 'Changed')
      end

      fields_in_change = model.changesets.first.changeset.keys
      refute_includes(fields_in_change, 'updated_at')
    end

    def test_save_does_not_save_slug_changes
      model = Foo.create!(name: 'Test', slug: 'test')

      Release.with_current(@release.id) do
        model.update_attributes(slug: 'test-changed')

        refute(model.valid?)
        assert_includes(
          model.errors.full_messages,
          'Slug cannot be changed for releases'
        )
      end
    end

    def test_save_does_not_save_blank_to_blank_changes
      model = Foo.create!(name: 'Test', blank_field: nil)

      Release.with_current(@release.id) do
        model.update_attributes!(name: 'Changed', blank_field: '')
      end

      fields_in_change = model.changesets.first.changeset.keys
      refute_includes(fields_in_change, 'blank_field')
    end

    def test_save_can_schedule_activation
      model = Foo.create!(
        name: 'Test',
        active: true,
        activate_with: @release.id
      )

      model.reload

      refute(model.active)
      assert_equal(1, model.changesets.length)
      assert_equal(@release.id, model.changesets.first.release_id)
      assert(model.changesets.first.changeset['active'])
    end

    def test_save_ignores_illegal_values
      model = Foo.create!(
        name: 'Test',
        active: true,
        activate_with: 'now'
      )

      model.reload

      assert(model.active)
      assert_equal(0, model.changesets.length)
    end

    def test_save_can_schedule_activation_for_an_embedded_document
      model = Foo.create!(name: 'Test')

      embedded_1 = model.bars.create!(
        name: 'Bar',
        active: true,
        activate_with: @release.id
      )

      embedded_2 = model.bars.create!(
        name: 'Baz',
        active: true,
        activate_with: @release.id
      )

      model.reload

      refute(embedded_1.active)
      assert_equal(1, embedded_1.changesets.length)
      assert_equal(@release.id, embedded_1.changesets.first.release_id)
      assert(embedded_1.changesets.first.changeset['active'])

      refute(embedded_2.active)
      assert_equal(1, embedded_2.changesets.length)
      assert_equal(@release.id, embedded_2.changesets.first.release_id)
      assert(embedded_2.changesets.first.changeset['active'])
    end

    def test_destroys_related_changesets
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!
      end

      model.destroy

      assert_equal(0, Release::Changeset.count)
    end

    def test_destroy_cleans_its_parents_changesets_if_embedded
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!
      end

      model.bars.first.destroy
      model.reload

      assert_equal(1, Release::Changeset.count)
      assert_equal(1, model.changesets.length)
      assert_equal(@release.id, model.changesets.first.release_id)
      assert_equal('Changed', model.changesets.first.changeset['name'])
    end

    def test_destroys_a_changeset_where_it_is_the_only_change_if_embedded
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.bars.first.name = 'Bar Changed'
        model.save!
      end

      model.bars.first.destroy
      model.reload

      assert_equal(0, model.changesets.length)
    end

    def test_publish
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!

        model.baz.name = 'Baz Changed'
        model.baz.save!
      end

      @release.changesets.each(&:publish!)
      model.reload

      assert_equal('Changed', model.name)
      assert_equal('Bar Changed', model.bars.first.name)
      assert_equal('Baz Changed', model.baz.name)
    end

    def test_undo
      model = Foo.create!(
        name: 'Test',
        bars: [{ name: 'Bar' }],
        baz: { name: 'Baz' }
      )

      Release.with_current(@release.id) do
        model.name = 'Changed'
        model.save!

        model.bars.first.name = 'Bar Changed'
        model.bars.first.save!

        model.baz.name = 'Baz Changed'
        model.baz.save!
      end

      @release.changesets.each(&:publish!)
      model.reload

      assert_equal('Changed', model.name)
      assert_equal('Bar Changed', model.bars.first.name)
      assert_equal('Baz Changed', model.baz.name)

      @release.changesets.each(&:undo!)
      model.reload

      assert_equal('Test', model.name)
      assert_equal('Bar', model.bars.first.name)
      assert_equal('Baz', model.baz.name)
    end

    def test_activates_with_current_release
      model = Foo.create!(name: 'Test', active: false)
      refute(model.activates_with_current_release?)

      Release.with_current(@release.id) do
        model.active = true
        model.save!

        model.reload
        assert(model.activates_with_current_release?)
      end

      model.reload
      refute(model.activates_with_current_release?)
    end

    def test_creating_and_activating_embedded
      model = Foo.create!(name: 'Foo')

      embedded = Release.with_current(@release.id) do
        model.bars.create!(
          name: 'Test',
          active: true,
          activate_with: @release.id
        )
      end

      model.reload

      refute(embedded.active)
      assert_equal(1, embedded.changesets.length)
      assert_equal(@release.id, embedded.changesets.first.release_id)
      assert(embedded.changesets.first.changeset['active'])
      assert(embedded.changesets.first.document_path.present?)
    end
  end
end