#  Copyright 2016-2022. Couchbase, Inc.
#  All Rights Reserved.
#
#  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.

from __future__ import annotations

from asyncio import AbstractEventLoop
from datetime import timedelta
from typing import (TYPE_CHECKING,
                    Any,
                    Dict,
                    Union)

from acouchbase import get_event_loop  # noqa: F401
from acouchbase.bucket import AsyncBucket
from acouchbase.logic.cluster_impl import AsyncClusterImpl
from acouchbase.management.analytics import AnalyticsIndexManager
from acouchbase.management.buckets import BucketManager
from acouchbase.management.eventing import EventingFunctionManager
from acouchbase.management.queries import QueryIndexManager
from acouchbase.management.search import SearchIndexManager
from acouchbase.management.users import UserManager
from acouchbase.transactions import Transactions
from couchbase.auth import (CertificateAuthenticator,
                            JwtAuthenticator,
                            PasswordAuthenticator)
from couchbase.logic.observability import ObservableRequestHandler
from couchbase.logic.operation_types import StreamingOperationType
from couchbase.result import (AnalyticsResult,
                              ClusterInfoResult,
                              DiagnosticsResult,
                              PingResult,
                              QueryResult,
                              SearchResult)

if TYPE_CHECKING:
    from couchbase.options import (AnalyticsOptions,
                                   ClusterOptions,
                                   DiagnosticsOptions,
                                   PingOptions,
                                   QueryOptions,
                                   SearchOptions,
                                   WaitUntilReadyOptions)
    from couchbase.search import SearchQuery, SearchRequest


class AsyncCluster:
    """Create a Couchbase Cluster instance.

    The cluster instance exposes the operations which are available to be performed against a cluster.

    .. note::
        Although creating an instance of :class:`.AsyncCluster` is allowed, it is recommended to
        use the AsyncCluster's static :meth:`.AsyncCluster.connect` method. See :meth:`.AsyncCluster.connect` for
        connect for examples.

    Args:
        connstr (str):
            The connection string to use for connecting to the cluster.
            This is a URI-like string allowing specifying multiple hosts.

            The format of the connection string is the *scheme*
            (``couchbase`` for normal connections, ``couchbases`` for
            SSL enabled connections); a list of one or more *hostnames*
            delimited by commas
        options (:class:`~couchbase.options.ClusterOptions`): Global options to set for the cluster.
            Some operations allow the global options to be overriden by passing in options to the
            operation.
        **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
            overrride provided :class:`~couchbase.options.ClusterOptions`

    Raises:
        :class:`~couchbase.exceptions.InvalidArgumentException`: If no :class:`~couchbase.auth.Authenticator`
            is provided.  Also raised if an invalid `ClusterOption` is provided.
        :class:`~couchbase.exceptions.AuthenticationException`: If provided :class:`~couchbase.auth.Authenticator`
            has incorrect credentials.

    """

    def __init__(self,
                 connstr,  # type: str
                 *options,  # type: ClusterOptions
                 **kwargs,  # type: Dict[str, Any]
                 ) -> None:
        self._impl = AsyncClusterImpl(connstr, *options, **kwargs)

    @property
    def loop(self) -> AbstractEventLoop:
        """
        **INTERNAL**
        """
        return self._impl.loop

    @property
    def transactions(self) -> Transactions:
        """
            :class:`~acouchbase.transactions.Transactions`: A Transactions instance which can be used to
                perform transactions on this cluster.
        """
        return self._impl.transactions

    async def on_connect(self) -> None:
        """Waits until the AsyncCluster connection has been established.

        .. note::
            It is recommended to use the AsyncCluster's static :meth:`.AsyncCluster.connect` method.
            See :meth:`.AsyncCluster.connect` for connect for examples.

        Raises:
            :class:`~couchbase.exceptions.UnAmbiguousTimeoutException`: If an error occured while trying to connect.
        """
        await self._impl.wait_until_connected()

    async def close(self) -> None:
        """Shuts down this cluster instance. Cleaning up all resources associated with it.

        .. warning::
            Use of this method is almost *always* unnecessary.  Cluster resources should be cleaned
            up once the cluster instance falls out of scope.  However, in some applications tuning resources
            is necessary and in those types of applications, this method might be beneficial.

        """
        await self._impl.close_connection()

    def set_authenticator(
            self, authenticator: Union[CertificateAuthenticator, JwtAuthenticator, PasswordAuthenticator]
    ) -> None:
        """Replace the authenticator used by this Cluster.

        Allows updating credentials without restarting the application.
        The effect on existing connections depends on the authenticator type:

        - JwtAuthenticator: Live re-auth on existing KV connections via OAUTHBEARER SASL.
          HTTP requests use the new Bearer token immediately.
        - PasswordAuthenticator: No effect on existing KV connections (they keep old
          credentials). New HTTP requests use the new Basic auth header.
        - CertificateAuthenticator: No effect on existing connections (TLS handshake
          already completed). Only new connections use the new certificate.

        Args:
            authenticator: New authenticator to use. Must be the same type as the
                current authenticator.

        Raises:
            RuntimeError: If cluster is not connected.
        """
        req = self._impl.request_builder.build_update_credential_request(authenticator)
        self._impl.update_credentials(req)

    def bucket(self, bucket_name: str) -> AsyncBucket:
        """Creates a Bucket instance to a specific bucket.

        .. seealso::
            :class:`.bucket.AsyncBucket`

        Args:
            bucket_name (str): Name of the bucket to reference

        Returns:
            :class:`~acouchbase.bucket.AsyncBucket`: A bucket instance

        Raises:
            :class:`~couchbase.exceptions.BucketNotFoundException`: If provided `bucket_name` cannot
                be found.

        """
        return AsyncBucket(self, bucket_name)

    async def cluster_info(self) -> ClusterInfoResult:
        """Retrieve the Couchbase cluster information

        .. note::
            If using Couchbase Server version < 6.6, a bucket *must* be opened prior to calling
            `cluster.cluster_info()`.  If a bucket is not opened a
            :class:`~couchbase.exceptions.ServiceUnavailableException` will be raised.


        Returns:
            Awaitable[:class:`~couchbase.result.ClusterInfoResult`]: Information about the connected cluster.

        Raises:
            RuntimeError:  If called prior to the cluster being connected.
            :class:`~couchbase.exceptions.ServiceUnavailableException`: If called prior to connecting
                to a bucket if using server version < 6.6.

        """
        req = self._impl.request_builder.build_cluster_info_request()
        return await self._impl.get_cluster_info(req)

    async def ping(self,
                   *opts,  # type: PingOptions
                   **kwargs  # type: Any
                   ) -> PingResult:
        """Performs a ping operation against the cluster.

        The ping operation pings the services which are specified
        (or all services if none are specified). Returns a report which describes the outcome of
        the ping operations which were performed.

        Args:
            opts (:class:`~couchbase.options.PingOptions`): Optional parameters for this operation.

        Returns:
            Awaitable[:class:`~couchbase.result.PingResult`]: A report which describes the outcome of the
            ping operations which were performed.

        """
        req = self._impl.request_builder.build_ping_request(*opts, **kwargs)
        return await self._impl.ping(req)

    async def diagnostics(self,
                          *opts,  # type: DiagnosticsOptions
                          **kwargs  # type: Dict[str, Any]
                          ) -> DiagnosticsResult:
        """Performs a diagnostic operation against the cluster.

        The diagnostic operations returns a report about the current active connections with the cluster.
        Includes information about remote and local addresses, last activity, and other diagnostics information.

        Args:
            opts (:class:`~couchbase.options.DiagnosticsOptions`): Optional parameters for this operation.

        Returns:
            Awaitable[:class:`~couchbase.result.DiagnosticsResult`]: A report which describes current active
            connections with the cluster.

        """
        req = self._impl.request_builder.build_diagnostics_request(*opts, **kwargs)
        return await self._impl.diagnostics(req)

    async def wait_until_ready(self,
                               timeout,  # type: timedelta
                               *opts,  # type: WaitUntilReadyOptions
                               **kwargs  # type: Dict[str, Any]
                               ) -> None:
        """Wait until the cluster is ready for use.

            Check the current connections to see if the desired state has been reached.  If not,
            perform a ping against the specified services. The ping operation will be performed
            repeatedly with a slight delay in between until the specified timeout has been reached
            or the cluster is ready for use, whichever comes first.

            .. seealso::
                * :class:`~couchbase.diagnostics.ServiceType`
                * :class:`~couchbase.diagnostics.ClusterState`

        Args:
            timeout (timedelta): Amount of time to wait for cluster to be ready before a
                :class:`~couchbase.exceptions.UnAmbiguousTimeoutException` is raised.
            opts (:class:`~couchbase.options.WaitUntilReadyOptions`): Optional parameters for this operation.
            **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
                override provided :class:`~couchbase.options.WaitUntilReadyOptions`

        Raises:
            :class:`~couchbase.exceptions.UnAmbiguousTimeoutException`: If the specified timeout is reached prior to
                the cluster being ready for use.

        Example:

            Wait until the cluster is ready to use KV and query services::

                from acouchbase.cluster import Cluster
                from couchbase.auth import PasswordAuthenticator
                from couchbase.diagnostics import ServiceType
                from couchbase.options import WaitUntilReadyOptions

                auth = PasswordAuthenticator('username', 'password')
                cluster = Cluster.connect('couchbase://localhost', ClusterOptions(auth))

                await cluster.wait_until_ready(timedelta(seconds=3),
                         WaitUntilReadyOptions(service_types=[ServiceType.KeyValue, ServiceType.Query]))

        """
        req = self._impl.request_builder.build_wait_until_ready_request(timeout, *opts, **kwargs)
        await self._impl.wait_until_ready(req)

    def query(self,
              statement,  # type: str
              *options,  # type: QueryOptions
              **kwargs  # type: Dict[str, Any]
              ) -> QueryResult:
        """Executes a N1QL query against the cluster.

        .. note::
            The query is executed lazily in that it is executed once iteration over the
            :class:`~couchbase.result.QueryResult` begins.

        .. seealso::
            * :class:`~acouchbase.management.queries.QueryIndexManager`: for how to manage query indexes
            * :meth:`~acouchbase.scope.AsyncScope.query`: For how to execute scope-level queries.

        Args:
            statement (str): The N1QL statement to execute.
            options (:class:`~couchbase.options.QueryOptions`): Optional parameters for the query operation.
            **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
                override provided :class:`~couchbase.options.QueryOptions`

        Returns:
            :class:`~couchbase.result.QueryResult`: An instance of a :class:`~couchbase.result.QueryResult` which
            provides access to iterate over the query results and access metadata and metrics about the query.

        Examples:
            Simple query::

                q_res = cluster.query('SELECT * FROM `travel-sample` WHERE country LIKE 'United%' LIMIT 2;')
                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Simple query with positional parameters::

                from couchbase.options import QueryOptions

                # ... other code ...

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $1 LIMIT $2;'
                q_res = cluster.query(q_str, QueryOptions(positional_parameters=['United%', 5]))
                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Simple query with named parameters::

                from couchbase.options import QueryOptions

                # ... other code ...

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $country LIMIT $lim;'
                q_res = cluster.query(q_str, QueryOptions(named_parameters={'country': 'United%', 'lim':2}))
                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Retrieve metadata and/or metrics from query::

                from couchbase.options import QueryOptions

                # ... other code ...

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $country LIMIT $lim;'
                q_res = cluster.query(q_str, QueryOptions(metrics=True))
                async for row in q_res.rows():
                    print(f'Found row: {row}')

                print(f'Query metadata: {q_res.metadata()}')
                print(f'Query metrics: {q_res.metadata().metrics()}')

        """
        op_type = StreamingOperationType.Query
        obs_handler = ObservableRequestHandler(op_type, self._impl.observability_instruments)
        req = self._impl.request_builder.build_query_request(statement, obs_handler, *options, **kwargs)
        return self._impl.query(req)

    def analytics_query(self,  # type: Cluster
                        statement,  # type: str
                        *options,  # type: AnalyticsOptions
                        **kwargs,  # type: Dict[str, Any]
                        ) -> AnalyticsResult:
        """Executes an analaytics query against the cluster.

        .. note::
            The analytics query is executed lazily in that it is executed once iteration over the
            :class:`~couchbase.result.AnalyticsResult` begins.

        .. seealso::
            * :class:`~acouchbase.management.analytics.AnalyticsIndexManager`: for how to manage analytics dataverses, datasets, indexes and links.
            * :meth:`~acouchbase.scope.AsyncScope.analytics_query`: for how to execute scope-level analytics queries

        Args:
            statement (str): The analytics SQL++ statement to execute.
            options (:class:`~couchbase.options.AnalyticsOptions`): Optional parameters for the analytics query
                operation.
            **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
                override provided :class:`~couchbase.options.AnalyticsOptions`

        Returns:
            :class:`~couchbase.result.AnalyticsResult`: An instance of a
            :class:`~couchbase.result.AnalyticsResult` which provides access to iterate over the analytics
            query results and access metadata and metrics about the analytics query.

        Examples:
            .. note::
                Be sure to setup the necessary dataverse(s), dataset(s) for your analytics queries.
                See :analytics_intro:`Analytics Introduction <>` in Couchbase Server docs.

            Simple analytics query::

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $1 LIMIT $2;'
                q_res = cluster.analytics_query(q_str)
                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Simple analytics query with positional parameters::

                from couchbase.options import AnalyticsOptions

                # ... other code ...

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $1 LIMIT $2;'
                q_res = cluster.analytics_query(q_str, AnalyticsOptions(positional_parameters=['United%', 5]))
                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Simple analytics query with named parameters::

                from couchbase.options import AnalyticsOptions

                # ... other code ...

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $country LIMIT $lim;'
                q_res = cluster.analytics_query(q_str,
                                                AnalyticsOptions(named_parameters={'country': 'United%', 'lim':2}))
                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Retrieve metadata and/or metrics from analytics query::

                q_str = 'SELECT * FROM `travel-sample` WHERE country LIKE $country LIMIT $lim;'
                q_res = cluster.analytics_query(q_str)
                async for row in q_res.rows():
                    print(f'Found row: {row}')

                print(f'Analytics query metadata: {q_res.metadata()}')
                print(f'Analytics query metrics: {q_res.metadata().metrics()}')

        """  # noqa: E501
        op_type = StreamingOperationType.AnalyticsQuery
        obs_handler = ObservableRequestHandler(op_type, self._impl.observability_instruments)
        req = self._impl.request_builder.build_analytics_query_request(statement, obs_handler, *options, **kwargs)
        return self._impl.analytics_query(req)

    def search_query(self,
                     index,  # type: str
                     query,  # type: SearchQuery
                     *options,  # type: SearchOptions
                     **kwargs   # type: Dict[str, Any]
                     ) -> SearchResult:
        """Executes an search query against the cluster.

        .. note::
            The search query is executed lazily in that it is executed once iteration over the
            :class:`~couchbase.result.SearchResult` begins.

        .. seealso::
            * :class:`~acouchbase.management.search.SearchIndexManager`: for how to manage search indexes.
            * :meth:`~acouchbase.scope.AsyncScope.search_query`: for how to execute scope-level search queries

        Args:
            index (str): Name of the search query to use.
            query (:class:`~couchbase.search.SearchQuery`): Type of search query to perform.
            options (:class:`~couchbase.options.SearchOptions`): Optional parameters for the search query
                operation.
            **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
                override provided :class:`~couchbase.options.SearchOptions`

        Returns:
            :class:`~couchbase.result.SearchResult`: An instance of a
            :class:`~couchbase.result.SearchResult` which provides access to iterate over the search
            query results and access metadata and metrics about the search query.

        Examples:

            .. note::
                Be sure to create a search index prior to executing search queries.  Also, if an application
                desires to utilize search row locations, highlighting, etc. make sure the search index is
                setup appropriately.  See :search_create_idx:`Creating Indexes <>` in Couchbase Server docs.

            Simple search query::

                import couchbase.search as search
                from couchbase.options import SearchOptions

                # ... other code ...

                query = search.TermQuery('home')
                q_res = cluster.search_query('travel-sample-index',
                                            query,
                                            SearchOptions(limit=10))

                async for row in q_res.rows():
                    print(f'Found row: {row}')


            Simple search query with facets::

                import couchbase.search as search
                from couchbase.options import SearchOptions

                # ... other code ...

                facet_name = 'activity'
                facet = search.TermFacet('activity')
                query = search.TermQuery('home')
                q_res = cluster.search_query('travel-sample-index',
                                            query,
                                            SearchOptions(limit=10, facets={facet_name: facet}))

                async for row in q_res.rows():
                    print(f'Found row: {row}')

                print(f'facets: {q_res.facets()}')


            Simple search query with fields and locations::

                import couchbase.search as search
                from couchbase.options import SearchOptions

                # ... other code ...

                search_fields = ['name', 'activity']
                query = search.TermQuery('home')
                q_res = cluster.search_query('travel-sample-index',
                                            query,
                                            SearchOptions(limit=10,
                                                        include_locations=True,
                                                        fields=search_fields))

                async for row in q_res.rows():
                    print(f'Found row: {row}')
                    print(f'Fields: {row.fields}')
                    print(f'Locations: {row.locations}')

        """
        op_type = StreamingOperationType.SearchQuery
        obs_handler = ObservableRequestHandler(op_type, self._impl.observability_instruments)
        req = self._impl.request_builder.build_search_request(index, query, obs_handler, *options, **kwargs)
        return self._impl.search(req)

    def search(self,
               index,  # type: str
               request,  # type: SearchRequest
               *options,  # type: SearchOptions
               **kwargs,  # type: Dict[str, Any]
               ) -> SearchResult:
        """Executes an search against the cluster.

        .. note::
            The search is executed lazily in that it is executed once iteration over the
            :class:`~couchbase.result.SearchResult` begins.

        .. seealso::
            * :class:`~couchbase.management.search.SearchIndexManager`: for how to manage search indexes.
            * :meth:`~acouchbase.scope.AsyncScope.search`: for how to execute scope-level search

        Args:
            index (str): Name of the search index to use.
            request (:class:`~couchbase.search.SearchRequest`): Type of search request to perform.
            options (:class:`~couchbase.options.SearchOptions`): Optional parameters for the search query operation.
            **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
                override provided :class:`~couchbase.options.SearchOptions`

        Returns:
            :class:`~couchbase.result.SearchResult`: An instance of a
            :class:`~couchbase.result.SearchResult` which provides access to iterate over the search
            query results and access metadata and metrics about the search query.

        Examples:

            .. note::
                Be sure to create a search index prior to executing a search.  Also, if an application
                desires to utilize search row locations, highlighting, etc. make sure the search index is
                setup appropriately.  See :search_create_idx:`Creating Indexes <>` in Couchbase Server docs.

            Simple search::

                import couchbase.search as search
                from couchbase.options import SearchOptions

                # ... other code ...

                request = search.SearchRequest.create(search.TermQuery('home'))
                q_res = cluster.search('travel-sample-index',
                                       request,
                                       SearchOptions(limit=10))

                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Simple vector search::

                import couchbase.search as search
                from couchbase.options import SearchOptions
                from couchbase.vector_search import VectorQuery, VectorSearch

                # ... other code ...

                # NOTE:  the vector is expected to be type List[float], set the vector to the appropriate value, this is an example.
                vector = [-0.014653487130999565, -0.008658270351588726, 0.017129190266132355, -0.015563474968075752]
                request = search.SearchRequest.create(VectorSearch.from_vector_query(VectorQuery('vector_field', vector)))
                q_res = cluster.search('travel-sample-vector-index',
                                       request,
                                       SearchOptions(limit=10))

                async for row in q_res.rows():
                    print(f'Found row: {row}')

            Combine search and vector search::

                import couchbase.search as search
                from couchbase.options import SearchOptions
                from couchbase.vector_search import VectorQuery, VectorSearch

                # ... other code ...

                # NOTE:  the vector is expected to be type List[float], set the vector to the appropriate value, this is an example.
                vector_search = VectorSearch.from_vector_query(VectorQuery('vector_field', [-0.014653487130999565,
                                                                                            -0.008658270351588726,
                                                                                            0.017129190266132355,
                                                                                            -0.015563474968075752]))
                request = search.SearchRequest.create(search.MatchAllQuery()).with_vector_search(vector_search)
                q_res = cluster.search('travel-sample-vector-index',
                                       request,
                                       SearchOptions(limit=10))

                async for row in q_res.rows():
                    print(f'Found row: {row}')
        """  # noqa: E501
        op_type = StreamingOperationType.SearchQuery
        obs_handler = ObservableRequestHandler(op_type, self._impl.observability_instruments)
        req = self._impl.request_builder.build_search_request(index, request, obs_handler, *options, **kwargs)
        return self._impl.search(req)

    def buckets(self) -> BucketManager:
        """
        Get a :class:`~acouchbase.management.buckets.BucketManager` which can be used to manage the buckets
        of this cluster.

        Returns:
            :class:`~acouchbase.management.buckets.BucketManager`: A :class:`~acouchbase.management.buckets.BucketManager` instance.
        """  # noqa: E501
        return BucketManager(self._impl._client_adapter, self._impl.observability_instruments)

    def users(self) -> UserManager:
        """
        Get a :class:`~acouchbase.management.users.UserManager` which can be used to manage the users
        of this cluster.

        Returns:
            :class:`~acouchbase.management.users.UserManager`: A :class:`~couchbase.management.users.UserManager` instance.
        """  # noqa: E501
        return UserManager(self._impl._client_adapter, self._impl.observability_instruments)

    def query_indexes(self) -> QueryIndexManager:
        """
        Get a :class:`~acouchbase.management.queries.QueryIndexManager` which can be used to manage the query
        indexes of this cluster.

        Returns:
            :class:`~acouchbase.management.queries.QueryIndexManager`: A :class:`~acouchbase.management.queries.QueryIndexManager` instance.
        """  # noqa: E501
        return QueryIndexManager(self._impl._client_adapter, self._impl.observability_instruments)

    def analytics_indexes(self) -> AnalyticsIndexManager:
        """
        Get a :class:`~acouchbase.management.analytics.AnalyticsIndexManager` which can be used to manage the analytics
        dataverses, dataset, indexes and links of this cluster.

        Returns:
            :class:`~acouchbase.management.analytics.AnalyticsIndexManager`: An :class:`~acouchbase.management.analytics.AnalyticsIndexManager` instance.
        """  # noqa: E501
        return AnalyticsIndexManager(self._impl._client_adapter, self._impl.observability_instruments)

    def search_indexes(self) -> SearchIndexManager:
        """
        Get a :class:`~acouchbase.management.search.SearchIndexManager` which can be used to manage the search
        indexes of this cluster.

        Returns:
            :class:`~acouchbase.management.search.SearchIndexManager`: A :class:`~acouchbase.management.search.SearchIndexManager` instance.

        """  # noqa: E501
        return SearchIndexManager(self._impl._client_adapter, self._impl.observability_instruments)

    def eventing_functions(self) -> EventingFunctionManager:
        """
        Get a :class:`~acouchbase.management.eventing.EventingFunctionManager` which can be used to manage the
        eventing functions of this cluster.

        .. note::
            Eventing function management is an **uncommitted** API that is unlikely to change,
            but may still change as final consensus on its behavior has not yet been reached.

        Returns:
            :class:`~acouchbase.management.eventing.EventingFunctionManager`: An :class:`~acouchbase.management.eventing.EventingFunctionManager` instance.

        """  # noqa: E501
        return EventingFunctionManager(self._impl._client_adapter, self._impl.observability_instruments)

    @staticmethod
    async def connect(connstr,  # type: str
                      *options,  # type: ClusterOptions
                      **kwargs,  # type: Dict[str, Any]
                      ) -> AsyncCluster:
        """Create a Couchbase Cluster and connect

        Args:
            connstr (str):
                The connection string to use for connecting to the cluster.
                This is a URI-like string allowing specifying multiple hosts.

                The format of the connection string is the *scheme*
                (``couchbase`` for normal connections, ``couchbases`` for
                SSL enabled connections); a list of one or more *hostnames*
                delimited by commas
            options (:class:`~couchbase.options.ClusterOptions`): Global options to set for the cluster.
                Some operations allow the global options to be overriden by passing in options to the
                operation.
            **kwargs (Dict[str, Any]): keyword arguments that can be used in place or to
                overrride provided :class:`~couchbase.options.ClusterOptions`

        Returns:
            :class:`.AsyncCluster`: If successful, a connect Couchbase Cluster instance.

        Raises:
            :class:`~couchbase.exceptions.InvalidArgumentException`: If no :class:`~couchbase.auth.Authenticator`
                is provided.  Also raised if an invalid `ClusterOption` is provided.
            :class:`~couchbase.exceptions.AuthenticationException`: If provided :class:`~couchbase.auth.Authenticator`
                has incorrect credentials.


        Examples:
            Initialize cluster using default options::

                from acouchbase.cluster import Cluster
                from couchbase.auth import PasswordAuthenticator
                from couchbase.options import ClusterOptions

                auth = PasswordAuthenticator('username', 'password')
                cluster = await Cluster.connect('couchbase://localhost', ClusterOptions(auth))

            Connect using SSL::

                from acouchbase.cluster import Cluster
                from couchbase.auth import PasswordAuthenticator
                from couchbase.options import ClusterOptions

                auth = PasswordAuthenticator('username', 'password', cert_path='/path/to/cert')
                cluster = await Cluster.connect('couchbases://localhost', ClusterOptions(auth))

            Initialize cluster using with global timeout options::

                from datetime import timedelta

                from acouchbase.cluster import Cluster
                from couchbase.auth import PasswordAuthenticator
                from couchbase.options import ClusterOptions, ClusterTimeoutOptions

                auth = PasswordAuthenticator('username', 'password')
                timeout_opts = ClusterTimeoutOptions(kv_timeout=timedelta(seconds=10),
                                                    query_timeout=timedelta(seconds=120))
                cluster = await Cluster.connect('couchbase://localhost',
                                                ClusterOptions(auth, timeout_options=timeout_opts))

        """
        cluster = AsyncCluster(connstr, *options, **kwargs)
        await cluster.on_connect()
        return cluster


Cluster = AsyncCluster
