# frozen_string_literal: true

require 'spec_helper'
require_relative '../shared/csot/examples'

describe Mongo::Operation::Create::OpMsg do
  include CSOT::Examples

  let(:context) { Mongo::Operation::Context.new }

  let(:write_concern) do
    Mongo::WriteConcern.get(w: :majority)
  end

  let(:session) { nil }
  let(:spec) do
    { selector: { create: authorized_collection.name },
      db_name: authorized_collection.database.name,
      write_concern: write_concern,
      session: session }
  end

  let(:op) { described_class.new(spec) }

  let(:connection) do
    double('connection').tap do |connection|
      allow(connection).to receive(:server).and_return(authorized_primary)
      allow(connection).to receive(:features).and_return(authorized_primary.features)
      allow(connection).to receive(:description).and_return(authorized_primary.description)
      allow(connection).to receive(:cluster_time).and_return(authorized_primary.cluster_time)
    end
  end

  describe '#initialize' do
    context 'spec' do
      it 'sets the spec' do
        expect(op.spec).to eq(spec)
      end
    end
  end

  describe '#==' do
    context 'spec' do
      context 'when two ops have the same specs' do
        let(:other) { described_class.new(spec) }

        it 'returns true' do
          expect(op).to eq(other)
        end
      end

      context 'when two ops have different specs' do
        let(:other_selector) do
          { create: 'other_collection_name' }
        end

        let(:other_spec) do
          { selector: other_selector,
            db_name: authorized_collection.database.name,
            write_concern: write_concern,
            ordered: true }
        end
        let(:other) { described_class.new(other_spec) }

        it 'returns false' do
          expect(op).not_to eq(other)
        end
      end
    end
  end

  describe '#selector' do
    it 'does not mutate user input' do
      user_input = IceNine.deep_freeze(spec.dup)
      expect do
        described_class.new(user_input).send(:selector, connection)
      end.not_to raise_error
    end
  end

  describe '#message' do
    # https://jira.mongodb.org/browse/RUBY-2224
    require_no_linting

    let(:global_args) do
      {
        create: TEST_COLL,
        writeConcern: write_concern.options,
        '$db' => SpecConfig.instance.test_db,
        lsid: session.session_id
      }
    end

    let(:session) do
      authorized_client.start_session
    end

    context 'when the topology is replica set or sharded' do
      require_topology :replica_set, :sharded

      let(:expected_global_args) do
        global_args.merge(Mongo::Operation::CLUSTER_TIME => authorized_client.cluster.cluster_time)
      end

      it 'creates the correct OP_MSG message' do
        authorized_client.command(ping: 1)
        expect(Mongo::Protocol::Msg).to receive(:new).with([], {}, expected_global_args)
        op.send(:message, connection)
      end
    end

    context 'when the topology is standalone' do
      require_topology :single

      let(:expected_global_args) do
        global_args
      end

      it 'creates the correct OP_MSG message' do
        authorized_client.command(ping: 1)
        expect(Mongo::Protocol::Msg).to receive(:new).with([], {}, expected_global_args)
        op.send(:message, connection)
      end

      context 'when an implicit session is created and the topology is then updated and the server does not support sessions' do
        # Mocks on features are incompatible with linting
        require_no_linting

        let(:expected_global_args) do
          global_args.dup.tap do |args|
            args.delete(:lsid)
          end
        end

        let(:session) do
          Mongo::Session.new(nil, authorized_client, implicit: true).tap do |session|
            allow(session).to receive(:session_id).and_return(42)
            session.should be_implicit
          end
        end

        it 'creates the correct OP_MSG message' do
          RSpec::Mocks.with_temporary_scope do
            # mimic lack of session support
            allow(authorized_primary.description).to receive(:logical_session_timeout).and_return(nil)

            expect(expected_global_args[:session]).to be_nil
            expect(Mongo::Protocol::Msg).to receive(:new).with([], {}, expected_global_args)
            op.send(:message, connection)
          end
        end
      end
    end

    context 'when the write concern is 0' do
      let(:write_concern) do
        Mongo::WriteConcern.get(w: 0)
      end

      context 'when the session is implicit' do
        let(:session) do
          Mongo::Session.new(nil, authorized_client, implicit: true).tap do |session|
            allow(session).to receive(:session_id).and_return(42)
            session.should be_implicit
          end
        end

        context 'when the topology is replica set or sharded' do
          require_topology :replica_set, :sharded

          let(:expected_global_args) do
            global_args.dup.tap do |args|
              args.delete(:lsid)
              args.merge!(Mongo::Operation::CLUSTER_TIME => authorized_client.cluster.cluster_time)
            end
          end

          it 'does not send a session id in the command' do
            authorized_client.command(ping: 1)
            expect(Mongo::Protocol::Msg).to receive(:new).with([ :more_to_come ], {}, expected_global_args)
            op.send(:message, connection)
          end
        end

        context 'when the topology is standalone' do
          require_topology :single

          let(:expected_global_args) do
            global_args.dup.tap do |args|
              args.delete(:lsid)
            end
          end

          it 'creates the correct OP_MSG message' do
            authorized_client.command(ping: 1)
            expect(Mongo::Protocol::Msg).to receive(:new).with([ :more_to_come ], {}, expected_global_args)
            op.send(:message, connection)
          end
        end
      end

      context 'when the session is explicit' do
        require_topology :replica_set, :sharded

        let(:session) do
          authorized_client.start_session
        end

        before do
          session.should_not be_implicit
        end

        let(:expected_global_args) do
          global_args.dup.tap do |args|
            args.delete(:lsid)
            args.merge!(Mongo::Operation::CLUSTER_TIME => authorized_client.cluster.cluster_time)
          end
        end

        it 'does not send a session id in the command' do
          authorized_client.command(ping: 1)
          RSpec::Mocks.with_temporary_scope do
            expect(Mongo::Protocol::Msg).to receive(:new).with([ :more_to_come ], {}, expected_global_args)
            op.send(:message, connection)
          end
        end
      end
    end
  end

  it_behaves_like 'a CSOT-compliant OpMsg subclass'
end
