require 'rails_helper'

require 'support/models'
require 'support/pluck_polyfill'
require 'support/active_admin_helpers'

RSpec.describe 'searchable_select_options dsl', type: :request do
  describe 'with text_attribute option' do
    before(:each) do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(scope: Post, text_attribute: :title)
        end
      end
    end

    describe 'creates JSON endpoint that' do
      it 'returns options for searchable select' do
        Post.create!(title: 'A post')

        get '/admin/posts/all_options'

        expect(json_response).to match(results: [a_hash_including(text: 'A post',
                                                                  id: kind_of(Numeric))],
                                       pagination: { more: false })
      end

      it 'supports filtering via term parameter' do
        Post.create!(title: 'A post')
        Post.create!(title: 'Other post')
        Post.create!(title: 'Not matched')

        get '/admin/posts/all_options?term=post'
        titles = json_response[:results].pluck(:text)

        expect(titles).to eq(['A post', 'Other post'])
      end
    end
  end

  describe 'with separate filter option' do
    before(:each) do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(scope: Post,
                                    filter: ->(term, scope) { scope.where(title: term) },
                                    text_attribute: :title)
        end
      end
    end

    describe 'creates JSON endpoint that' do
      it 'returns options for searchable select' do
        Post.create!(title: 'A post')

        get '/admin/posts/all_options'

        expect(json_response).to match(results: [a_hash_including(text: 'A post',
                                                                  id: kind_of(Numeric))],
                                       pagination: { more: false })
      end

      it 'supports filtering via term parameter' do
        Post.create!(title: 'Post')
        Post.create!(title: 'Not matched')

        get '/admin/posts/all_options?term=Post'
        titles = json_response[:results].pluck(:text)

        expect(titles).to eq(['Post'])
      end
    end
  end

  describe 'with separate display_text option' do
    before(:each) do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(scope: Post,
                                    display_text: ->(record) { record.title.upcase },
                                    text_attribute: :title)
        end
      end
    end

    describe 'creates JSON endpoint that' do
      it 'returns options for searchable select' do
        Post.create!(title: 'A post')

        get '/admin/posts/all_options'

        expect(json_response).to match(results: [a_hash_including(text: 'A POST',
                                                                  id: kind_of(Numeric))],
                                       pagination: { more: false })
      end

      it 'supports filtering via term parameter' do
        Post.create!(title: 'A post')
        Post.create!(title: 'Not matched')

        get '/admin/posts/all_options?term=post'
        titles = json_response[:results].pluck(:text)

        expect(titles).to eq(['A POST'])
      end
    end
  end

  describe 'pagination' do
    before(:each) do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(scope: Post,
                                    text_attribute: :title,
                                    per_page: 2)
        end
      end
    end

    it 'limits results and indicates that more results are available' do
      Post.create!(title: 'A post')
      Post.create!(title: 'Other post')
      Post.create!(title: 'Yet another post')

      get '/admin/posts/all_options'

      expect(json_response[:results].size).to eq(2)
      expect(json_response[:pagination][:more]).to eq(true)
    end

    it 'allows passing page param' do
      Post.create!(title: 'A post')
      Post.create!(title: 'Other post')
      Post.create!(title: 'Yet another post')

      get '/admin/posts/all_options?page=1'

      expect(json_response[:results].size).to eq(1)
      expect(json_response[:pagination][:more]).to eq(false)
    end
  end

  describe 'with additional_payload' do
    context 'as lambda' do
      before(:each) do
        ActiveAdminHelpers.setup do
          ActiveAdmin.register(Post) do
            searchable_select_options(
              scope: Post,
              text_attribute: :title,
              additional_payload: lambda do |record|
                { published: record.published }
              end
            )
          end
        end
      end
      let!(:post) { Post.create!(title: 'A post', published: false) }

      subject { get '/admin/posts/all_options' }

      it 'returns options with our additional attribute' do
        subject
        expect(json_response).to match(
          results: [{ text: 'A post', id: post.id, published: false }],
          pagination: { more: false }
        )
      end
    end

    context 'as Proc' do
      before(:each) do
        ActiveAdminHelpers.setup do
          ActiveAdmin.register(Post) do
            searchable_select_options(
              scope: Post,
              text_attribute: :title,
              additional_payload: proc { |record| { published: record.published } }
            )
          end
        end
      end
      let!(:post) { Post.create!(title: 'A post', published: false) }

      subject { get '/admin/posts/all_options' }

      it 'returns options with our additional attribute' do
        subject
        expect(json_response).to match(
          results: [{ text: 'A post', id: post.id, published: false }],
          pagination: { more: false }
        )
      end
    end
  end

  it 'allows passing lambda as scope' do
    ActiveAdminHelpers.setup do
      ActiveAdmin.register(Post) do
        searchable_select_options(scope: -> { Post.published },
                                  text_attribute: :title)
      end
    end

    Post.create!(title: 'Draft')
    Post.create!(title: 'Published post', published: true)

    get '/admin/posts/all_options'
    titles = json_response[:results].pluck(:text)

    expect(titles).to eq(['Published post'])
  end

  it 'allows passing lambda as scope that uses view helpers' do
    ActiveAdminHelpers.setup do
      ActiveAdmin.register(Post) do
        searchable_select_options(scope: -> { Post.where(user: current_user) },
                                  text_attribute: :title)
      end
    end

    user = User.create!
    Post.create!(title: 'By current user', user: user)
    Post.create!(title: 'By other user', user: User.create!)

    ApplicationController.current_user = user
    get '/admin/posts/all_options'
    titles = json_response[:results].pluck(:text)

    expect(titles).to eq(['By current user'])
  end

  it 'allows passing lambda that takes params argument' do
    ActiveAdminHelpers.setup do
      ActiveAdmin.register(Post) do
        searchable_select_options(scope: ->(params) { Post.where(user_id: params[:user]) },
                                  text_attribute: :title)
      end
    end

    user = User.create!
    Post.create!(title: 'By given user', user: user)
    Post.create!(title: 'By other user', user: User.create!)

    get "/admin/posts/all_options?user=#{user.id}"
    titles = json_response[:results].pluck(:text)

    expect(titles).to eq(['By given user'])
  end

  it 'allows passing name prefix for collection action' do
    ActiveAdminHelpers.setup do
      ActiveAdmin.register(Post) do
        searchable_select_options(name: :some,
                                  scope: Post,
                                  text_attribute: :title)
      end
    end

    Post.create!(title: 'A post')

    get '/admin/posts/some_options'

    expect(json_response).to include(results: array_including(a_hash_including(text: 'A post')))
  end

  it 'fails with helpful message if scope option is missing' do
    expect do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(text_attribute: :title)
        end
      end
    end.to raise_error(/Missing option: scope/)
  end

  it 'fails with helpful message if display_text are missing' do
    expect do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(scope: Post,
                                    filter: ->(_term, scope) { scope })
        end
      end
    end.to raise_error(/Missing option: display_text/)
  end

  it 'fails with helpful message if filter option is missing' do
    expect do
      ActiveAdminHelpers.setup do
        ActiveAdmin.register(Post) do
          searchable_select_options(scope: Post,
                                    display_text: ->(_term, scope) { scope })
        end
      end
    end.to raise_error(/Missing option: filter/)
  end

  def json_response
    JSON.parse(response.body).with_indifferent_access
  end
end
