# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::Presence, :config do
  it 'registers an offense and corrects when `a.present? ? a : nil`' do
    expect_offense(<<~RUBY)
      a.present? ? a : nil
      ^^^^^^^^^^^^^^^^^^^^ Use `a.presence` instead of `a.present? ? a : nil`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when `!a.present? ? nil: a`' do
    expect_offense(<<~RUBY)
      !a.present? ? nil: a
      ^^^^^^^^^^^^^^^^^^^^ Use `a.presence` instead of `!a.present? ? nil: a`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when `a.blank? ? nil : a`' do
    expect_offense(<<~RUBY)
      a.blank? ? nil : a
      ^^^^^^^^^^^^^^^^^^ Use `a.presence` instead of `a.blank? ? nil : a`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when `!a.blank? ? a : nil`' do
    expect_offense(<<~RUBY)
      !a.blank? ? a : nil
      ^^^^^^^^^^^^^^^^^^^ Use `a.presence` instead of `!a.blank? ? a : nil`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when `a.present? ? a : b`' do
    expect_offense(<<~RUBY)
      a.present? ? a : b
      ^^^^^^^^^^^^^^^^^^ Use `a.presence || b` instead of `a.present? ? a : b`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b
    RUBY
  end

  it 'registers an offense and corrects when `!a.present? ? b : a`' do
    expect_offense(<<~RUBY)
      !a.present? ? b : a
      ^^^^^^^^^^^^^^^^^^^ Use `a.presence || b` instead of `!a.present? ? b : a`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b
    RUBY
  end

  it 'registers an offense and corrects when `a.blank? ? b : a`' do
    expect_offense(<<~RUBY)
      a.blank? ? b : a
      ^^^^^^^^^^^^^^^^ Use `a.presence || b` instead of `a.blank? ? b : a`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b
    RUBY
  end

  it 'registers an offense and corrects when `!a.blank? ? a : b`' do
    expect_offense(<<~RUBY)
      !a.blank? ? a : b
      ^^^^^^^^^^^^^^^^^ Use `a.presence || b` instead of `!a.blank? ? a : b`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b
    RUBY
  end

  it 'registers an offense and corrects when `a.present? ? a : 1`' do
    expect_offense(<<~RUBY)
      a.present? ? a : 1
      ^^^^^^^^^^^^^^^^^^ Use `a.presence || 1` instead of `a.present? ? a : 1`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || 1
    RUBY
  end

  it 'registers an offense and corrects when `a.blank? ? 1 : a`' do
    expect_offense(<<~RUBY)
      a.blank? ? 1 : a
      ^^^^^^^^^^^^^^^^ Use `a.presence || 1` instead of `a.blank? ? 1 : a`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || 1
    RUBY
  end

  it 'registers an offense and corrects when `a(:bar).map(&:baz).present? ? a(:bar).map(&:baz) : nil`' do
    expect_offense(<<~RUBY)
      a(:bar).map(&:baz).present? ? a(:bar).map(&:baz) : nil
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `a(:bar).map(&:baz).presence` instead of `a(:bar).map(&:baz).present? ? a(:bar).map(&:baz) : nil`.
    RUBY

    expect_correction(<<~RUBY)
      a(:bar).map(&:baz).presence
    RUBY
  end

  it 'registers an offense and corrects when `a.present? ? a : b[:c]`' do
    expect_offense(<<~RUBY)
      a.present? ? a : b[:c]
      ^^^^^^^^^^^^^^^^^^^^^^ Use `a.presence || b[:c]` instead of `a.present? ? a : b[:c]`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b[:c]
    RUBY
  end

  it 'registers an offense and corrects when multi-line if node' do
    expect_offense(<<~RUBY)
      if a.present?
      ^^^^^^^^^^^^^ Use `a.presence` instead of `if a.present? ... end`.
        a
      else
        nil
      end
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when multi-line unless node' do
    expect_offense(<<~RUBY)
      unless a.present?
      ^^^^^^^^^^^^^^^^^ Use `a.presence` instead of `unless a.present? ... end`.
        nil
      else
        a
      end
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when multi-line if node with `+` operators in the else branch' do
    expect_offense(<<~RUBY)
      if a.present?
      ^^^^^^^^^^^^^ Use `a.presence || b.to_f + 12.0` instead of `if a.present? ... end`.
        a
      else
        b.to_f + 12.0
      end
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b.to_f + 12.0
    RUBY
  end

  it 'registers an offense and corrects when multi-line if `*` operators in the else branch' do
    expect_offense(<<~RUBY)
      if a.present?
      ^^^^^^^^^^^^^ Use `a.presence || b.to_f * 12.0` instead of `if a.present? ... end`.
        a
      else
        b.to_f * 12.0
      end
    RUBY

    expect_correction(<<~RUBY)
      a.presence || b.to_f * 12.0
    RUBY
  end

  it 'registers an offense and corrects when `a if a.present?`' do
    expect_offense(<<~RUBY)
      a if a.present?
      ^^^^^^^^^^^^^^^ Use `a.presence` instead of `a if a.present?`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when `a unless a.blank?`' do
    expect_offense(<<~RUBY)
      a unless a.blank?
      ^^^^^^^^^^^^^^^^^ Use `a.presence` instead of `a unless a.blank?`.
    RUBY

    expect_correction(<<~RUBY)
      a.presence
    RUBY
  end

  it 'registers an offense and corrects when `.present?` with method chain' do
    expect_offense(<<~RUBY)
      if [1, 2, 3].map { |num| num + 1 }
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `[1, 2, 3].map { |num| num + 1 }.map { |num| num + 2 }.presence || b` instead of `if [1, 2, 3].map { |num| num + 1 }.map { |num| num + 2 }.present? ... end`.
                  .map { |num| num + 2 }
                  .present?
                  [1, 2, 3].map { |num| num + 1 }.map { |num| num + 2 }
      else
        b
      end
    RUBY

    expect_correction(<<~RUBY)
      [1, 2, 3].map { |num| num + 1 }
                  .map { |num| num + 2 }.presence || b
    RUBY
  end

  context 'when a method is called on the receiver' do
    it 'registers an offense and corrects when `a.present? ? a.foo : nil' do
      expect_offense(<<~RUBY)
        a.present? ? a.foo : nil
        ^^^^^^^^^^^^^^^^^^^^^^^^ Use `a.presence&.foo` instead of `a.present? ? a.foo : nil`.
      RUBY

      expect_correction(<<~RUBY)
        a.presence&.foo
      RUBY
    end

    it 'registers an offense and corrects when `a.blank? ? nil : a.foo' do
      expect_offense(<<~RUBY)
        a.blank? ? nil : a.foo
        ^^^^^^^^^^^^^^^^^^^^^^ Use `a.presence&.foo` instead of `a.blank? ? nil : a.foo`.
      RUBY

      expect_correction(<<~RUBY)
        a.presence&.foo
      RUBY
    end

    it 'registers an offense and corrects when `a.foo if a.present?`' do
      expect_offense(<<~RUBY)
        a.foo if a.present?
        ^^^^^^^^^^^^^^^^^^^ Use `a.presence&.foo` instead of `a.foo if a.present?`.
      RUBY

      expect_correction(<<~RUBY)
        a.presence&.foo
      RUBY
    end

    it 'registers an offense and corrects when `a.foo unless a.blank?`' do
      expect_offense(<<~RUBY)
        a.foo unless a.blank?
        ^^^^^^^^^^^^^^^^^^^^^ Use `a.presence&.foo` instead of `a.foo unless a.blank?`.
      RUBY

      expect_correction(<<~RUBY)
        a.presence&.foo
      RUBY
    end

    it 'registers an offense and corrects when chained method takes parameters' do
      expect_offense(<<~RUBY)
        a.present? ? a.foo(42, key: :value) : nil
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `a.presence&.foo(42, key: :value)` instead of `a.present? ? a.foo(42, key: :value) : nil`.
      RUBY

      expect_correction(<<~RUBY)
        a.presence&.foo(42, key: :value)
      RUBY
    end

    it 'does not register an offense when chained method is `[]`' do
      expect_no_offenses(<<~RUBY)
        a.present? ? a[1] : nil
      RUBY
    end

    it 'does not register an offense when chained method is `[]=`' do
      expect_no_offenses(<<~RUBY)
        a[1] = 1 if a.present?
      RUBY
    end

    it 'does not register an offense when chained method is attribute assignment' do
      expect_no_offenses(<<~RUBY)
        a.attribute = 42 if a.present?
      RUBY
    end

    it 'does not register an offense when chained method is an arithmetic operation' do
      expect_no_offenses(<<~RUBY)
        a.present? ? a + 42 : nil
      RUBY
    end

    it 'does not register an offense when using comparison operation in modifier if' do
      expect_no_offenses(<<~RUBY)
        a <= 0 if a.present?
      RUBY
    end

    it 'does not register an offense when chained method is a comparison operation in ternary' do
      expect_no_offenses(<<~RUBY)
        a.present? ? a > 42 : nil
      RUBY
    end

    it 'does not register an offense when chained method is `<<`' do
      expect_no_offenses(<<~RUBY)
        foo << "bar" if foo.present?
      RUBY
    end

    it 'does not register an offense when chained method is `=~`' do
      expect_no_offenses(<<~RUBY)
        foo =~ /pattern/ if foo.present?
      RUBY
    end

    it 'does not register an offense when multiple methods are chained' do
      expect_no_offenses(<<~RUBY)
        a.present? ? a.foo.bar : nil
      RUBY
    end

    it 'registers an offense and corrects without parentheses when assigned to a variable' do
      expect_offense(<<~RUBY)
        x = a.present? ? a.foo : nil
            ^^^^^^^^^^^^^^^^^^^^^^^^ Use `a.presence&.foo` instead of `a.present? ? a.foo : nil`.
      RUBY

      expect_correction(<<~RUBY)
        x = a.presence&.foo
      RUBY
    end
  end

  context 'when multiline ternary can be replaced' do
    it 'registers an offense and corrects' do
      expect_offense(<<~RUBY)
        a.present? ?
        ^^^^^^^^^^^^ Use `a.presence` instead of `a.present? ? a : nil`.
          a :
          nil
      RUBY

      expect_correction(<<~RUBY)
        a.presence
      RUBY
    end
  end

  context 'when a method argument of `else` branch is enclosed in parentheses' do
    it 'registers an offense and corrects' do
      expect_offense(<<~RUBY)
        if value.present?
        ^^^^^^^^^^^^^^^^^ Use `value.presence || do_something(value)` instead of `if value.present? ... end`.
          value
        else
          do_something(value)
        end
      RUBY

      expect_correction(<<~RUBY)
        value.presence || do_something(value)
      RUBY
    end
  end

  context 'when a method argument of `else` branch is not enclosed in parentheses' do
    it 'registers an offense and corrects' do
      expect_offense(<<~RUBY)
        if value.present?
        ^^^^^^^^^^^^^^^^^ Use `value.presence || do_something(value)` instead of `if value.present? ... end`.
          value
        else
          do_something value
        end
      RUBY

      expect_correction(<<~RUBY)
        value.presence || do_something(value)
      RUBY
    end
  end

  context 'when multiple method arguments of `else` branch is not enclosed in parentheses' do
    it 'registers an offense and corrects' do
      expect_offense(<<~RUBY)
        if value.present?
        ^^^^^^^^^^^^^^^^^ Use `value.presence || do_something(arg1, arg2)` instead of `if value.present? ... end`.
          value
        else
          do_something arg1, arg2
        end
      RUBY

      expect_correction(<<~RUBY)
        value.presence || do_something(arg1, arg2)
      RUBY
    end
  end

  context 'when a method argument with a receiver of `else` branch is not enclosed in parentheses' do
    it 'registers an offense and corrects' do
      expect_offense(<<~RUBY)
        if value.present?
        ^^^^^^^^^^^^^^^^^ Use `value.presence || foo.do_something(value)` instead of `if value.present? ... end`.
          value
        else
          foo.do_something value
        end
      RUBY

      expect_correction(<<~RUBY)
        value.presence || foo.do_something(value)
      RUBY
    end
  end

  context 'when a right-hand side of the relational operator' do
    %w[< > <= >= == !=].each do |operator|
      it "registers an offense and corrects when `#{operator}`" do
        expect_offense(<<~RUBY, operator: operator)
          a #{operator} if b.present?
            _{operator} ^^^^^^^^^^^^^ Use `(b.presence || c)` instead of `if b.present? ... end`.
            b
          else
            c
          end
        RUBY

        expect_correction(<<~RUBY)
          a #{operator} (b.presence || c)
        RUBY
      end
    end
  end

  context 'when a right-hand side of `&&` operator' do
    it 'registers an offense and corrects with parentheses' do
      expect_offense(<<~RUBY)
        a && if b.present?
             ^^^^^^^^^^^^^ Use `(b.presence || c)` instead of `if b.present? ... end`.
               b
             else
               c
             end
      RUBY

      expect_correction(<<~RUBY)
        a && (b.presence || c)
      RUBY
    end
  end

  context 'when a parenthesized method argument' do
    it 'registers an offense and corrects without parentheses' do
      expect_offense(<<~RUBY)
        foo(a.present? ? a : b)
            ^^^^^^^^^^^^^^^^^^ Use `a.presence || b` instead of `a.present? ? a : b`.
      RUBY

      expect_correction(<<~RUBY)
        foo(a.presence || b)
      RUBY
    end
  end

  context 'when an unparenthesized method argument' do
    it 'registers an offense and corrects with parentheses' do
      expect_offense(<<~RUBY)
        foo bar, a.present? ? a : b
                 ^^^^^^^^^^^^^^^^^^ Use `(a.presence || b)` instead of `a.present? ? a : b`.
      RUBY

      expect_correction(<<~RUBY)
        foo bar, (a.presence || b)
      RUBY
    end
  end

  context 'when a right-hand side of `||` operator' do
    it 'registers an offense and corrects without parentheses' do
      expect_offense(<<~RUBY)
        a || if b.present?
             ^^^^^^^^^^^^^ Use `b.presence || c` instead of `if b.present? ... end`.
               b
             else
               c
             end
      RUBY

      expect_correction(<<~RUBY)
        a || b.presence || c
      RUBY
    end
  end

  context 'when assigned to a variable' do
    it 'registers an offense and corrects without parentheses' do
      expect_offense(<<~RUBY)
        x = a.present? ? a : b
            ^^^^^^^^^^^^^^^^^^ Use `a.presence || b` instead of `a.present? ? a : b`.
      RUBY

      expect_correction(<<~RUBY)
        x = a.presence || b
      RUBY
    end
  end

  it 'does not register an offense when using `#presence`' do
    expect_no_offenses(<<~RUBY)
      a.presence
    RUBY
  end

  it 'does not register an offense when the expression does not return the receiver of `#present?`' do
    expect_no_offenses(<<~RUBY)
      a.present? ? b : nil
    RUBY

    expect_no_offenses(<<~RUBY)
      puts foo if present?
      puts foo if !present?
    RUBY
  end

  it 'does not register an offense when the expression does not return the receiver of `#blank?`' do
    expect_no_offenses(<<~RUBY)
      a.blank? ? nil : b
    RUBY

    expect_no_offenses(<<~RUBY)
      puts foo if blank?
      puts foo if !blank?
    RUBY
  end

  it 'does not register an offense when if or unless modifier is used' do
    [
      'a if a.blank?',
      'a unless a.present?'
    ].each { |source| expect_no_offenses(source) }
  end

  it 'does not register an offense when the else block is multiline' do
    expect_no_offenses(<<~RUBY)
      if a.present?
        a
      else
        something
        something
        something
      end
    RUBY
  end

  it 'does not register an offense when the else block has multiple statements' do
    expect_no_offenses(<<~RUBY)
      if a.present?
        a
      else
        something; something; something
      end
    RUBY
  end

  it 'does not register an offense when including the elsif block' do
    expect_no_offenses(<<~RUBY)
      if a.present?
        a
      elsif b
        b
      end
    RUBY
  end

  it 'does not register an offense when the else block has `if` node' do
    expect_no_offenses(<<~RUBY)
      if a.present?
        a
      else
        b if c
      end
    RUBY
  end

  it 'does not register an offense when the else block has `rescue` node' do
    expect_no_offenses(<<~RUBY)
      if something_method.present?
        something_method
      else
        invalid_method rescue StandardError
      end
    RUBY
  end

  it 'does not register an offense when the else block has `while` node' do
    expect_no_offenses(<<~RUBY)
      if a.present?
        a
      else
        fetch_state while waiting?
      end
    RUBY
  end

  it 'does not register an offense when using #present? with elsif block' do
    expect_no_offenses(<<~RUBY)
      if something?
        a
      elsif b.present?
        b
      end
    RUBY
  end

  context 'when inspection file that have already been migrated' do
    let(:config) do
      RuboCop::Config.new('AllCops' => { 'MigratedSchemaVersion' => '20240101010101' })
    end

    it 'does not register an offense when `a.present? ? a : nil`' do
      expect_no_offenses(<<~RUBY, '20190101010101_add_column_to_table.rb')
        a.present? ? a : nil
      RUBY
    end
  end
end
