# frozen_string_literal: true

# Copyright (C) 2014-2020 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Mongo
  module Auth
    class User
      # Defines behavior for user related operation on databases.
      #
      # @since 2.0.0
      class View
        extend Forwardable

        # @return [ Database ] database The view's database.
        attr_reader :database

        def_delegators :database, :cluster, :read_preference, :client
        def_delegators :cluster, :next_primary

        # Create a new user in the database.
        #
        # @example Create a new read/write user.
        #   view.create('user', password: 'password', roles: [ 'readWrite' ])
        #
        # @param [ Auth::User, String ] user_or_name The user object or user name.
        # @param [ Hash ] options The user options.
        #
        # @option options [ Session ] :session The session to use for the operation.
        # @option options [ Hash ] :write_concern The write concern options.
        #
        # @return [ Result ] The command response.
        #
        # @since 2.0.0
        def create(user_or_name, options = {})
          user = generate(user_or_name, options)
          execute_operation(options) do |session|
            Operation::CreateUser.new(
              user: user,
              db_name: database.name,
              session: session,
              write_concern: options[:write_concern] && WriteConcern.get(options[:write_concern])
            )
          end
        end

        # Initialize the new user view.
        #
        # @example Initialize the user view.
        #   View::User.new(database)
        #
        # @param [ Mongo::Database ] database The database the view is for.
        #
        # @since 2.0.0
        def initialize(database)
          @database = database
        end

        # Remove a user from the database.
        #
        # @example Remove the user from the database.
        #   view.remove('user')
        #
        # @param [ String ] name The user name.
        # @param [ Hash ] options The options for the remove operation.
        #
        # @option options [ Session ] :session The session to use for the operation.
        # @option options [ Hash ] :write_concern The write concern options.
        #
        # @return [ Result ] The command response.
        #
        # @since 2.0.0
        def remove(name, options = {})
          execute_operation(options) do |session|
            Operation::RemoveUser.new(
              user_name: name,
              db_name: database.name,
              session: session,
              write_concern: options[:write_concern] && WriteConcern.get(options[:write_concern])
            )
          end
        end

        # Update a user in the database.
        #
        # @example Update a user.
        #   view.update('name', password: 'testpwd')
        #
        # @param [ Auth::User, String ] user_or_name The user object or user name.
        # @param [ Hash ] options The user options.
        #
        # @option options [ Session ] :session The session to use for the operation.
        # @option options [ Hash ] :write_concern The write concern options.
        #
        # @return [ Result ] The response.
        #
        # @since 2.0.0
        def update(user_or_name, options = {})
          user = generate(user_or_name, options)
          execute_operation(options) do |session|
            Operation::UpdateUser.new(
              user: user,
              db_name: database.name,
              session: session,
              write_concern: options[:write_concern] && WriteConcern.get(options[:write_concern])
            )
          end
        end

        # Get info for a particular user in the database.
        #
        # @example Get a particular user's info.
        #   view.info('emily')
        #
        # @param [ String ] name The user name.
        # @param [ Hash ] options The options for the info operation.
        #
        # @option options [ Session ] :session The session to use for the operation.
        #
        # @return [ Array ] An array wrapping a document containing information on a particular user.
        #
        # @since 2.1.0
        def info(name, options = {})
          user_query(name, options).documents
        end

        private

        def user_query(name, options = {})
          execute_operation(options) do |session|
            Operation::UsersInfo.new(
              user_name: name,
              db_name: database.name,
              session: session
            )
          end
        end

        def generate(user, options)
          user.is_a?(String) ? Auth::User.new({ user: user }.merge(options)) : user
        end

        def execute_operation(options)
          client.send(:with_session, options) do |session|
            op = yield session
            op.execute(next_primary(nil, session), context: Operation::Context.new(client: client, session: session))
          end
        end
      end
    end
  end
end
