# encoding: utf-8
module Moped

  # Provides behaviour to nodes around authentication.
  #
  # @since 2.0.0
  module Authenticatable

    # Apply authentication credentials.
    #
    # @example Apply the authentication credentials.
    #   node.apply_credentials({ "moped_test" => [ "user", "pass" ]})
    #
    # @param [ Hash ] credentials The authentication credentials in the form:
    #   { database_name: [ user, password ]}
    #
    # @return [ Object ] The authenticated object.
    #
    # @since 2.0.0
    def apply_credentials(logins)
      unless credentials == logins
        logouts = credentials.keys - logins.keys
        logouts.each do |database|
          logout(database)
        end
        logins.each do |database, (username, password)|
          unless credentials[database] == [ username, password ]
            login(database, username, password)
          end
        end
        @original_credentials = credentials.dup
      end
      self
    end

    # Get the applied credentials.
    #
    # @example Get the applied credentials.
    #   node.credentials
    #
    # @return [ Hash ] The credentials.
    #
    # @since 2.0.0
    def credentials
      @credentials ||= {}
    end

    # Login the user to the provided database with the supplied password.
    #
    # @example Login the user to the database.
    #   node.login("moped_test", "user", "pass")
    #
    # @param [ String ] database The database name.
    # @param [ String ] username The username.
    # @param [ String ] password The password.
    #
    # @raise [ Errors::AuthenticationFailure ] If the login failed.
    #
    # @return [ Array ] The username and password.
    #
    # @since 2.0.0
    def login(database, username, password)
      getnonce = Protocol::Command.new(database, getnonce: 1)
      self.write([getnonce])
      reply = self.receive_replies([getnonce]).first
      if getnonce.failure?(reply)
        return
      end
      result = getnonce.results(reply)

      authenticate = Protocol::Commands::Authenticate.new(database, username, password, result["nonce"])
      self.write([ authenticate ])
      document = self.read.documents.first

      unless result["ok"] == 1
        # See if we had connectivity issues so we can retry
        e = Errors::PotentialReconfiguration.new(authenticate, document)
        if e.reconfiguring_replica_set?
          raise Errors::ReplicaSetReconfigured.new(e.command, e.details)
        elsif e.connection_failure?
          raise Errors::ConnectionFailure.new(e.inspect)
        end

        raise Errors::AuthenticationFailure.new(authenticate, document)
      end
      credentials[database] = [username, password]
    end

    # Logout the user from the provided database.
    #
    # @example Logout from the provided database.
    #   node.logout("moped_test")
    #
    # @param [ String ] database The database name.
    #
    # @return [ Array ] The username and password.
    #
    # @since 2.0.0
    def logout(database)
      command = Protocol::Command.new(database, logout: 1)
      self.write([command])
      reply = self.receive_replies([command]).first
      if command.failure?(reply)
        return
      end
      credentials.delete(database)
    end
  end
end
