# frozen_string_literal: true
require "rails_helper"

RSpec.describe ActiveAdmin::ResourceController::DataAccess do
  around do |example|
    with_resources_during(example) do
      config
    end
  end

  let(:config) do
    ActiveAdmin.register Post do
    end
  end

  let(:http_params) do
    {}
  end

  let(:params) do
    ActionController::Parameters.new(http_params)
  end

  let(:controller) do
    rc = Admin::PostsController.new
    allow(rc).to receive(:params) do
      params
    end
    rc
  end

  describe "searching" do
    let(:ransack_options) { { auth_object: controller.send(:active_admin_authorization) } }
    let(:http_params) { { q: {} } }
    it "should call the search method" do
      chain = double "ChainObj"
      expect(chain).to receive(:ransack).with(params[:q], ransack_options).once.and_return(Post.ransack)
      controller.send :apply_filtering, chain
    end

    context "params includes empty values" do
      let(:http_params) do
        { q: { id_eq: 1, position_eq: "" } }
      end
      it "should return relation without empty filters" do
        expect(Post).to receive(:ransack).with(params[:q], ransack_options).once.and_wrap_original do |original, *args|
          chain = original.call(*args)
          expect(chain.conditions.size).to eq(1)
          chain
        end
        controller.send :apply_filtering, Post
      end
    end
  end

  describe "sorting" do
    context "valid clause" do
      let(:http_params) { { order: "id_asc" } }

      it "reorders chain" do
        chain = double "ChainObj"
        expect(chain).to receive(:reorder).with('"posts"."id" asc').once.and_return(Post.ransack)
        controller.send :apply_sorting, chain
      end
    end

    context "invalid clause" do
      let(:http_params) { { order: "_asc" } }

      it "returns chain untouched" do
        chain = double "ChainObj"
        expect(chain).not_to receive(:reorder)
        expect(controller.send(:apply_sorting, chain)).to eq chain
      end
    end

    context "custom strategy" do
      before do
        expect(controller.send(:active_admin_config)).to receive(:ordering).twice.and_return(
          {
            published_date: proc do |order_clause|
              [order_clause.to_sql, "NULLS LAST"].join(" ") if order_clause.order == "desc"
            end
          }.with_indifferent_access
        )
      end

      context "when params applicable" do
        let(:http_params) { { order: "published_date_desc" } }
        it "reorders chain" do
          chain = double "ChainObj"
          expect(chain).to receive(:reorder).with('"posts"."published_date" desc NULLS LAST').once.and_return(Post.ransack)
          controller.send :apply_sorting, chain
        end
      end

      context "when params not applicable" do
        let(:http_params) { { order: "published_date_asc" } }
        it "reorders chain" do
          chain = double "ChainObj"
          expect(chain).to receive(:reorder).with('"posts"."published_date" asc').once.and_return(Post.ransack)
          controller.send :apply_sorting, chain
        end
      end
    end
  end

  describe "scoping" do
    context "when no current scope" do
      it "should set collection_before_scope to the chain and return the chain" do
        chain = double "ChainObj"
        expect(controller.send(:apply_scoping, chain)).to eq chain
        expect(controller.send(:collection_before_scope)).to eq chain
      end
    end

    context "when current scope" do
      it "should set collection_before_scope to the chain and return the scoped chain" do
        chain = double "ChainObj"
        scoped_chain = double "ScopedChain"
        current_scope = double "CurrentScope"
        allow(controller).to receive(:current_scope) { current_scope }

        expect(controller).to receive(:scope_chain).with(current_scope, chain) { scoped_chain }
        expect(controller.send(:apply_scoping, chain)).to eq scoped_chain
        expect(controller.send(:collection_before_scope)).to eq chain
      end
    end
  end

  describe "includes" do
    context "with no includes" do
      it "should return the chain" do
        chain = double "ChainObj"
        expect(controller.send(:apply_includes, chain)).to eq chain
      end
    end

    context "with includes" do
      it "should return the chain with the includes" do
        chain = double "ChainObj"
        chain_with_includes = double "ChainObjWithIncludes"
        expect(chain).to receive(:includes).with(:taggings, :author).and_return(chain_with_includes)
        expect(controller.send(:active_admin_config)).to receive(:includes).twice.and_return([:taggings, :author])
        expect(controller.send(:apply_includes, chain)).to eq chain_with_includes
      end
    end
  end

  describe "pagination" do
    let(:collection) do
      Post.create!
      Post.all
    end

    let(:config) do
      ActiveAdmin.register Post do
        config.per_page = 1
      end
    end

    context "when CSV format requested" do
      let(:params) do
        ActionController::Parameters.new(page: 2, format: "csv")
      end

      it "does not apply it" do
        expect(controller.send(:apply_pagination, collection).size).to eq(1)
      end
    end

    context "when CSV format not requested" do
      let(:params) do
        ActionController::Parameters.new(page: 2)
      end

      it "applies it" do
        expect(controller.send(:apply_pagination, collection).size).to eq(0)
      end
    end
  end

  describe "find_collection" do
    let(:appliers) do
      ActiveAdmin::ResourceController::DataAccess::COLLECTION_APPLIES
    end
    let(:scoped_collection) do
      double "ScopedCollectionChain"
    end
    before do
      allow(controller).to receive(:scoped_collection).
        and_return(scoped_collection)
    end

    it "should return chain with all appliers " do
      appliers.each do |applier|
        expect(controller).to receive("apply_#{applier}").
          with(scoped_collection).
          once.
          and_return(scoped_collection)
      end
      expect(controller).to receive(:collection_applies).
        with({}).and_call_original.once
      controller.send :find_collection
    end

    describe "collection_applies" do
      context "excepting appliers" do
        let(:options) do
          { except:
              [:authorization_scope, :filtering, :scoping, :collection_decorator]
          }
        end

        it "should except appliers" do
          expect(controller.send :collection_applies, options).
            to eq([:sorting, :includes, :pagination])
        end
      end

      context "specifying only needed appliers" do
        let(:options) do
          { only: [:filtering, :scoping] }
        end
        it "should except appliers" do
          expect(controller.send :collection_applies, options).to eq(options[:only])
        end
      end
    end
  end

  describe "build_resource" do
    let(:config) do
      ActiveAdmin.register User do
        permit_params :type, posts_attributes: :custom_category_id
      end
    end

    let!(:category) { Category.create!(name: "Category") }

    let(:params) do
      ActionController::Parameters.new(user: { type: "User::VIP", posts_attributes: [custom_category_id: category.id] })
    end

    subject do
      controller.send :build_resource
    end

    let(:controller) do
      rc = Admin::UsersController.new
      allow(rc).to receive(:params) do
        params
      end
      rc
    end

    it "should return post with assigned attributes" do
      expect(subject).to be_a(User::VIP)
    end

    # see issue 4548
    it "should assign nested attributes once" do
      expect(subject.posts.size).to eq(1)
    end

    context "given authorization scope" do
      let(:authorization) { controller.send(:active_admin_authorization) }

      it "should apply authorization scope" do
        expect(authorization).to receive(:scope_collection) do |collection|
          collection.where(age: "42")
        end
        expect(subject.age).to eq(42)
      end
    end
  end

  describe "in_paginated_batches" do
    it "calls find_collection just once and disables the ActiveRecord query cache" do
      expect(controller).to receive(:find_collection).once do
        expect(ActiveRecord::Base.connection.query_cache_enabled).to be_falsy
        Post.none
      end
      ActiveRecord::Base.cache do
        controller.send(:in_paginated_batches, &Proc.new {})
      end
    end
  end
end
