# frozen_string_literal: true
# rubocop:todo all

require "spec_helper"

describe Mongoid::Criteria::Queryable::Aggregable do

  let(:query) do
    Mongoid::Query.new("id" => "_id", "alias" => "a")
  end

  shared_examples_for "an aggregable object" do

    it "clones the queryable" do
      expect(aggregation).to_not equal(query)
    end

    it "sets the aggregating flag" do
      expect(aggregation).to be_aggregating
    end
  end

  describe "#project" do

    context "when no selection or options exist" do

      let(:aggregation) do
        query.project(author: 1, title: 0)
      end

      let!(:pipeline) do
        aggregation.pipeline
      end

      it "sets the projection" do
        expect(pipeline).to eq([
          { "$project" => { "author" => 1, "title" => 0 }}
        ])
      end

      it_behaves_like "an aggregable object"
    end

    context "when the field is aliased" do

      let(:aggregation) do
        query.project(id: 1, title: "$docTitle")
      end

      let!(:pipeline) do
        aggregation.pipeline
      end

      it "sets the aliased projection" do
        expect(pipeline).to eq([
          { "$project" => { "_id" => 1, "title" => "$docTitle" }}
        ])
      end

      it_behaves_like "an aggregable object"
    end

    context "when selection exists" do

      let(:aggregation) do
        query.where(name: "test").project(author: 1, title: 0)
      end

      let!(:pipeline) do
        aggregation.pipeline
      end

      it "converts the selection to a $match" do
        expect(pipeline).to eq([
          { "$match" => { "name" => "test" }},
          { "$project" => { "author" => 1, "title" => 0 }}
        ])
      end

      it_behaves_like "an aggregable object"
    end

    context "when options exist" do

      context "when the option is a sort" do

        let(:aggregation) do
          query.asc(:name).project(author: 1, title: 0)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "converts the option to a $sort" do
          expect(pipeline).to eq([
            { "$sort" => { "name" => 1 }},
            { "$project" => { "author" => 1, "title" => 0 }}
          ])
        end

        it_behaves_like "an aggregable object"
      end

      context "when the option is a limit" do

        let(:aggregation) do
          query.limit(10).project(author: 1, title: 0)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "converts the option to a $sort" do
          expect(pipeline).to eq([
            { "$limit" => 10 },
            { "$project" => { "author" => 1, "title" => 0 }}
          ])
        end

        it_behaves_like "an aggregable object"
      end

      context "when the option is a skip" do

        let(:aggregation) do
          query.skip(10).project(author: 1, title: 0)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "converts the option to a $sort" do
          expect(pipeline).to eq([
            { "$skip" => 10 },
            { "$project" => { "author" => 1, "title" => 0 }}
          ])
        end

        it_behaves_like "an aggregable object"
      end
    end

    context "when selection and options exist" do

      let(:aggregation) do
        query.skip(10).where(name: "test").project(author: 1, title: 0)
      end

      let!(:pipeline) do
        aggregation.pipeline
      end

      it "converts the option to a $sort" do
        expect(pipeline).to eq([
          { "$match" => { "name" => "test" }},
          { "$skip" => 10 },
          { "$project" => { "author" => 1, "title" => 0 }}
        ])
      end

      it_behaves_like "an aggregable object"
    end
  end

  describe "#unwind" do

    context "when another pipeline operation exists" do

      let(:aggregation) do
        query.project(name: 1).unwind(:author)
      end

      let!(:pipeline) do
        aggregation.pipeline
      end

      it "adds the unwind to the pipeline" do
        expect(pipeline).to eq([
          { "$project" => { "name" => 1 }},
          { "$unwind" => "$author" }
        ])
      end

      it_behaves_like "an aggregable object"
    end

    context "when provided a symbol" do

      context "when the symbol begins with $" do

        let(:aggregation) do
          query.unwind(:$author)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "converts the symbol to a string" do
          expect(pipeline).to eq([{ "$unwind" => "$author" }])
        end

        it_behaves_like "an aggregable object"
      end

      context "when the symbol does not begin with $" do

        let(:aggregation) do
          query.unwind(:author)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "converts the symbol to a string and prepends $" do
          expect(pipeline).to eq([{ "$unwind" => "$author" }])
        end

        it_behaves_like "an aggregable object"
      end
    end

    context "when provided a string" do

      context "when the string begins with $" do

        let(:aggregation) do
          query.unwind("$author")
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "sets the string" do
          expect(pipeline).to eq([{ "$unwind" => "$author" }])
        end

        it_behaves_like "an aggregable object"
      end

      context "when the string does not begin with $" do

        let(:aggregation) do
          query.unwind(:author)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "prepends $ to the string" do
          expect(pipeline).to eq([{ "$unwind" => "$author" }])
        end

        it_behaves_like "an aggregable object"
      end
    end

    context "when provided a string alias" do

      context "when the string does not begin with $" do

        let(:aggregation) do
          query.unwind(:alias)
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "prepends $ to the string" do
          expect(pipeline).to eq([{ "$unwind" => "$a" }])
        end

        it_behaves_like "an aggregable object"
      end
    end
  end

  describe "#group" do

    context "when the expression fields are not aliased" do

      context "when using full notation" do

        let(:aggregation) do
          query.group(count: { "$sum" => 1 }, max: { "$max" => "likes" })
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "adds the group operation to the pipeline" do
          expect(pipeline).to eq([
            { "$group" => { "count" => { "$sum" => 1 }, "max" => { "$max" => "likes" }}}
          ])
        end

        it_behaves_like "an aggregable object"
      end

      context "when using symbol shortcuts" do

        let(:aggregation) do
          query.group(:count.sum => 1, :max.max => "likes")
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "adds the group operation to the pipeline" do
          expect(pipeline).to eq([
            { "$group" => { "count" => { "$sum" => 1 }, "max" => { "$max" => "likes" }}}
          ])
        end

        it_behaves_like "an aggregable object"
      end
    end

    context "when the expression fields are aliased" do

      context "when using full notation" do

        let(:aggregation) do
          query.group(count: { "$sum" => 1 }, max: { "$max" => "alias" })
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "adds the group operation to the pipeline" do
          pending
          fail
          expect(pipeline).to eq([
            { "$group" => { "count" => { "$sum" => 1 }, "max" => { "$max" => "a" }}}
          ])
        end

        it_behaves_like "an aggregable object"
      end

      context "when using symbol shortcuts" do

        let(:aggregation) do
          query.group(:count.sum => 1, :max.max => "alias")
        end

        let!(:pipeline) do
          aggregation.pipeline
        end

        it "adds the group operation to the pipeline" do
          pending
          fail
          expect(pipeline).to eq([
            { "$group" => { "count" => { "$sum" => 1 }, "max" => { "$max" => "a" }}}
          ])
        end

        it_behaves_like "an aggregable object"
      end
    end
  end
end
