# The MIT License (MIT)
# Copyright (c) 2021 Microsoft Corporation

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Create, read, and delete databases in the Azure Cosmos DB SQL API service.
"""

from typing import Any, Optional, Union, cast, Mapping, Iterable, Callable, overload, Literal
import warnings
from azure.core.async_paging import AsyncItemPaged
from azure.core.credentials import TokenCredential
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.pipeline.policies import RetryMode

from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.tracing.decorator import distributed_trace
from azure.cosmos.offer import ThroughputProperties

from ..cosmos_client import _parse_connection_str
from .._constants import _Constants as Constants
from ._cosmos_client_connection_async import CosmosClientConnection, CredentialDict
from .._base import build_options as _build_options, _set_throughput_options
from ._retry_utility_async import _ConnectionRetryPolicy
from ._database import DatabaseProxy, _get_database_link
from ..documents import ConnectionPolicy, DatabaseAccount
from ..exceptions import CosmosResourceNotFoundError
from .._cosmos_responses import CosmosDict

# pylint: disable=docstring-keyword-should-match-keyword-only

__all__ = ("CosmosClient",)

CredentialType = Union[
    AsyncTokenCredential, CredentialDict, str, Mapping[str, Any], Iterable[Mapping[str, Any]]
]

def _build_auth(credential: CredentialType) -> CredentialDict:
    auth: CredentialDict = {}
    if isinstance(credential, str):
        auth['masterKey'] = credential
    elif isinstance(credential, Mapping):
        if any(k for k in credential.keys() if k in ['masterKey', 'resourceTokens', 'permissionFeed']):
            return cast(CredentialDict, credential)  # Backwards compatible
        auth['resourceTokens'] = credential
    elif isinstance(credential, Iterable):
        auth['permissionFeed'] = cast(Iterable[Mapping[str, Any]], credential)
    elif isinstance(credential, (TokenCredential, AsyncTokenCredential)):
        auth['clientSecretCredential'] = credential
    else:
        raise TypeError(
            "Unrecognized credential type. Please supply the master key as a string "
            "or a dictionary, or resource tokens, or a list of permissions, or any instance of a class implementing"
            " AsyncTokenCredential (see azure.identity module for specific implementations "
            "such as ClientSecretCredential).")
    return auth


def _build_connection_policy(kwargs: dict[str, Any]) -> ConnectionPolicy:
    # pylint: disable=protected-access
    policy = kwargs.pop('connection_policy', None) or ConnectionPolicy()

    # Connection config
    # `request_timeout` is supported as a legacy parameter later replaced by `connection_timeout`
    if 'request_timeout' in kwargs:
        policy.RequestTimeout = kwargs.pop('request_timeout') / 1000.0
    else:
        policy.RequestTimeout = kwargs.pop('connection_timeout', policy.RequestTimeout)
    policy.ConnectionMode = kwargs.pop('connection_mode', policy.ConnectionMode)
    policy.ProxyConfiguration = kwargs.pop('proxy_config', policy.ProxyConfiguration)
    policy.EnableEndpointDiscovery = kwargs.pop('enable_endpoint_discovery', policy.EnableEndpointDiscovery)
    policy.PreferredLocations = kwargs.pop('preferred_locations', policy.PreferredLocations)
    policy.ExcludedLocations = kwargs.pop('excluded_locations', policy.ExcludedLocations)
    policy.UseMultipleWriteLocations = kwargs.pop('multiple_write_locations', policy.UseMultipleWriteLocations)

    # SSL config
    verify = kwargs.pop('connection_verify', None)
    policy.DisableSSLVerification = not bool(verify if verify is not None else True)
    ssl = kwargs.pop('ssl_config', None) or policy.SSLConfiguration
    if ssl:
        ssl.SSLCertFile = kwargs.pop('connection_cert', ssl.SSLCertFile)
        ssl.SSLCaCerts = verify or ssl.SSLCaCerts
        policy.SSLConfiguration = ssl

    # Retry config
    retry_options = policy.RetryOptions
    total_retries = kwargs.pop('retry_total', None)
    total_throttle_retries = kwargs.pop('retry_throttle_total', None)
    retry_options._max_retry_attempt_count = \
        total_throttle_retries or total_retries or retry_options._max_retry_attempt_count
    retry_options._fixed_retry_interval_in_milliseconds = \
        kwargs.pop('retry_fixed_interval', retry_options._fixed_retry_interval_in_milliseconds)
    max_backoff = kwargs.pop('retry_backoff_max', None)
    max_throttle_backoff = kwargs.pop('retry_throttle_backoff_max', None)
    retry_options._max_wait_time_in_seconds = \
        max_throttle_backoff or max_backoff or retry_options._max_wait_time_in_seconds
    policy.RetryOptions = retry_options
    connection_retry = policy.ConnectionRetryConfiguration
    if not connection_retry:
        connection_retry = _ConnectionRetryPolicy(
            retry_total=total_retries,
            retry_connect=kwargs.pop('retry_connect', None),
            retry_read=kwargs.pop('retry_read', None),
            retry_status=kwargs.pop('retry_status', None),
            retry_backoff_max=max_backoff or retry_options._max_wait_time_in_seconds,
            retry_mode=kwargs.pop('retry_mode', RetryMode.Fixed),
            retry_on_status_codes=kwargs.pop('retry_on_status_codes', []),
            retry_backoff_factor=kwargs.pop('retry_backoff_factor', 1),
        )
    policy.ConnectionRetryConfiguration = connection_retry
    policy.ResponsePayloadOnWriteDisabled = kwargs.pop('no_response_on_write', False)
    policy.RetryNonIdempotentWrites = kwargs.pop(Constants.Kwargs.RETRY_WRITE, False)
    return policy


class CosmosClient:  # pylint: disable=client-accepts-api-version-keyword
    """A client-side logical representation of an Azure Cosmos DB account.

    Use this client to configure and execute requests to the Azure Cosmos DB service.

    It's recommended to maintain a single instance of CosmosClient per lifetime of the application which enables
        efficient connection management and performance.

    CosmosClient initialization is a heavy operation - don't use initialization CosmosClient instances as
        credentials or network connectivity validations.

    :param str url: The URL of the Cosmos DB account.
    :param credential: Can be the account key, or a dictionary of resource tokens.
    :type credential: Union[str, dict[str, str], ~azure.core.credentials_async.AsyncTokenCredential]
    :keyword str consistency_level: Consistency level to use for the session. Default value is None (account-level).
        More on consistency levels and possible values: https://aka.ms/cosmos-consistency-levels
    :keyword int timeout: An absolute timeout in seconds, for the combined HTTP request and response processing.
    :keyword int connection_timeout: The HTTP request timeout in seconds.
    :keyword str connection_mode: The connection mode for the client - currently only supports 'Gateway'.
    :keyword proxy_config: Connection proxy configuration.
    :paramtype proxy_config: ~azure.cosmos.ProxyConfiguration
    :keyword ssl_config: Connection SSL configuration.
    :paramtype ssl_config: ~azure.cosmos.SSLConfiguration
    :keyword bool connection_verify: Whether to verify the connection, default value is True.
    :keyword str connection_cert: An alternative certificate to verify the connection.
    :keyword int retry_total: Maximum retry attempts.
    :keyword int retry_backoff_max: Maximum retry wait time in seconds.
    :keyword int retry_fixed_interval: Fixed retry interval in milliseconds.
    :keyword int retry_read: Maximum number of socket read retry attempts.
    :keyword int retry_connect: Maximum number of connection error retry attempts.
    :keyword int retry_status: Maximum number of retry attempts on error status codes.
    :keyword list[int] retry_on_status_codes: A list of specific status codes to retry on.
    :keyword float retry_backoff_factor: Factor to calculate wait time between retry attempts.
    :keyword bool retry_write: Indicates whether the SDK should automatically retry write operations for items, even if
        the operation is not guaranteed to be idempotent. This should only be enabled if the application can
        tolerate such risks or has logic to safely detect and handle duplicate operations.
    :keyword bool enable_endpoint_discovery: Enable endpoint discovery for
        geo-replicated database accounts. (Default: True)
    :keyword list[str] preferred_locations: The preferred locations for geo-replicated database accounts.
    :keyword list[str] excluded_locations: The excluded locations to be skipped from preferred locations. The locations
        in this list are specified as the names of the azure Cosmos locations like, 'West US', 'East US' and so on.
        If all preferred locations were excluded, primary/hub location will be used.
    :keyword bool enable_diagnostics_logging: Enable the CosmosHttpLogging policy.
        Must be used along with a logger to work.
    :keyword ~logging.Logger logger: Logger to be used for collecting request diagnostics. Can be passed in at client
        level (to log all requests) or at a single request level. Requests will be logged at INFO level.
    :keyword bool no_response_on_write: Indicates whether service should be instructed to skip sending 
        response payloads for write operations on items by default unless specified differently per operation.
    :keyword int throughput_bucket: The desired throughput bucket for the client
    :keyword str user_agent_suffix: Allows user agent suffix to be specified when creating client

    .. admonition:: Example:

        .. literalinclude:: ../samples/examples_async.py
            :start-after: [START create_client]
            :end-before: [END create_client]
            :language: python
            :dedent: 0
            :caption: Create a new instance of the Cosmos DB client:
            :name: create_client
    """

    def __init__(
            self,
            url: str,
            credential: Union[str, dict[str, str], AsyncTokenCredential],
            *,
            consistency_level: Optional[str] = None,
            **kwargs: Any
    ) -> None:
        """Instantiate a new CosmosClient."""
        auth = _build_auth(credential)
        connection_policy = _build_connection_policy(kwargs)
        self.client_connection = CosmosClientConnection(
            url,
            auth=auth,
            consistency_level=consistency_level,
            connection_policy=connection_policy,
            **kwargs
        )

    def __repr__(self) -> str:
        return "<CosmosClient [{}]>".format(self.client_connection.url_connection)[:1024]

    async def __aenter__(self) -> "CosmosClient":
        await self.client_connection.pipeline_client.__aenter__()
        await self.client_connection._setup()
        return self

    async def __aexit__(self, *args) -> None:
        await self.client_connection._global_endpoint_manager.close() # pylint: disable=protected-access
        return await self.client_connection.pipeline_client.__aexit__(*args)

    async def close(self) -> None:
        """Close this instance of CosmosClient."""
        await self.__aexit__()

    @classmethod
    def from_connection_string(
        cls,
        conn_str: str,
        *,
        credential: Optional[Union[str, dict[str, str]]] = None,
        consistency_level: Optional[str] = None,
        **kwargs: Any
    ) -> "CosmosClient":
        """Create a CosmosClient instance from a connection string.

        This can be retrieved from the Azure portal.For full list of optional
        keyword arguments, see the CosmosClient constructor.

        :param str conn_str: The connection string.
        :keyword credential: Alternative credentials to use instead of the key provided in the connection string.
        :paramtype credential: Union[str, dict[str, str]]
        :keyword str consistency_level: Consistency level to use for the session. Default value is None (account-level).
            More on consistency levels and possible values: https://aka.ms/cosmos-consistency-levels
        :returns: a CosmosClient instance
        :rtype: ~azure.cosmos.aio.CosmosClient
        """
        settings = _parse_connection_str(conn_str, credential)
        return cls(
            url=settings['AccountEndpoint'],
            credential=settings['AccountKey'],
            consistency_level=consistency_level,
            **kwargs
        )

    @overload
    async def create_database(
        self,
        id: str,
        *,
        offer_throughput: Optional[Union[int, ThroughputProperties]] = None,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        return_properties: Literal[False] = False,
        **kwargs: Any
    ) -> DatabaseProxy:
        """
        Create a new database with the given ID (name).

        :param str id: ID (name) of the database to create.
        :keyword Union[int, ~azure.cosmos.ThroughputProperties] offer_throughput: The provisioned throughput
            for this database.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword Callable[[dict[str, str], dict[str, Any]], None] response_hook: A callable invoked with
            the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :keyword bool return_properties: Specifies whether to return either a DatabaseProxy
            or a Tuple containing a DatabaseProxy and the associated database properties.
        :raises ~azure.cosmos.exceptions.CosmosResourceExistsError: Database with the given ID already exists.
        :returns: A `DatabaseProxy` instance representing the database.
        :rtype: ~azure.cosmos.aio.DatabaseProxy

        .. admonition:: Example:

            .. literalinclude:: ../samples/examples_async.py
                :start-after: [START create_database]
                :end-before: [END create_database]
                :language: python
                :dedent: 0
                :caption: Create a database in the Cosmos DB account:
                :name: create_database
        """
        ...

    @overload
    async def create_database(
        self,
        id: str,
        *,
        offer_throughput: Optional[Union[int, ThroughputProperties]] = None,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        return_properties: Literal[True],
        **kwargs: Any
    ) -> tuple[DatabaseProxy, CosmosDict]:
        """
        Create a new database with the given ID (name).

        :param str id: ID (name) of the database to create.
        :keyword Union[int, ~azure.cosmos.ThroughputProperties] offer_throughput: The provisioned throughput
            for this database.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword Callable[[dict[str, str], dict[str, Any]], None] response_hook: A callable invoked with
            the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :keyword bool return_properties: Specifies whether to return either a DatabaseProxy
            or a Tuple containing a DatabaseProxy and the associated database properties.
        :raises ~azure.cosmos.exceptions.CosmosResourceExistsError: Database with the given ID already exists.
        :returns: A tuple of `DatabaseProxy` and CosmosDict with the database properties.
        :rtype: tuple [~azure.cosmos.aio.DatabaseProxy, ~azure.cosmos.CosmosDict]

        .. admonition:: Example:

            .. literalinclude:: ../samples/examples_async.py
                :start-after: [START create_database]
                :end-before: [END create_database]
                :language: python
                :dedent: 0
                :caption: Create a database in the Cosmos DB account:
                :name: create_database
        """
        ...

    @distributed_trace_async
    async def create_database( # pylint:disable=docstring-should-be-keyword
        self,
        *args: Any,
        **kwargs: Any
    ) -> Union[DatabaseProxy, tuple[DatabaseProxy, CosmosDict]]:
        """
        Create a new database with the given ID (name).

        :param Any args: args
        :param str id: ID (name) of the database to read or create.
        :keyword Union[int, ~azure.cosmos.ThroughputProperties] offer_throughput: The provisioned throughput
            for this database.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword Callable[[dict[str, str], dict[str, Any]], None] response_hook: A callable invoked with
            the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :keyword bool return_properties: Specifies whether to return either a DatabaseProxy
            or a Tuple containing a DatabaseProxy and the associated database properties.
        :raises ~azure.cosmos.exceptions.CosmosResourceExistsError: Database with the given ID already exists.
        :returns: A DatabaseProxy instance representing the database or a tuple of DatabaseProxy
            and CosmosDict with the database properties.
        :rtype: ~azure.cosmos.aio.DatabaseProxy or tuple [~azure.cosmos.aio.DatabaseProxy, ~azure.cosmos.CosmosDict]

        .. admonition:: Example:

            .. literalinclude:: ../samples/examples_async.py
                :start-after: [START create_database]
                :end-before: [END create_database]
                :language: python
                :dedent: 0
                :caption: Create a database in the Cosmos DB account:
                :name: create_database
        """
        id = args[0] if args else kwargs.pop("id")
        if len(args) > 1:
            raise TypeError(f"Unexpected positional parameters: {args[1:]}")
        session_token = kwargs.get('session_token')
        if session_token is not None:
            warnings.warn(
                "The 'session_token' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        etag = kwargs.get('etag')
        if etag is not None:
            warnings.warn(
                "The 'etag' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        match_condition = kwargs.get('match_condition')
        if match_condition is not None:
            warnings.warn(
                "The 'match_condition' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)

        offer_throughput = kwargs.pop("offer_throughput", None)
        return_properties = kwargs.pop("return_properties", False)
        response_hook = kwargs.pop("response_hook", None)

        request_options = _build_options(kwargs)
        _set_throughput_options(offer=offer_throughput, request_options=request_options)

        result = await self.client_connection.CreateDatabase(database={"id": id}, options=request_options, **kwargs)
        if response_hook:
            response_hook(self.client_connection.last_response_headers)
        if not return_properties:
            return DatabaseProxy(self.client_connection, id=result["id"], properties=result)
        return  DatabaseProxy(self.client_connection, id=result["id"], properties=result), result

    @overload
    async def create_database_if_not_exists(  # pylint: disable=redefined-builtin
        self,
        id: str,
        *,
        offer_throughput: Optional[Union[int, ThroughputProperties]] = None,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        return_properties: Literal[False] = False,
        **kwargs: Any
    ) -> DatabaseProxy:
        """
        Create the database if it does not exist already.

        If the database already exists, the existing settings are returned.

        ..note::
            This function does not check or update existing database settings or
            offer throughput if they differ from what is passed in.

        :param str id: ID (name) of the database to read or create.
        :keyword Union[int, ~azure.cosmos.ThroughputProperties] offer_throughput: The provisioned throughput
            for this database.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword Callable[[dict[str, str], dict[str, Any]], None] response_hook: A callable invoked with
            the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :keyword bool return_properties: Specifies whether to return either a DatabaseProxy
            or a Tuple containing a DatabaseProxy and the associated database properties.
        :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The database read or creation failed.
        :returns: A DatabaseProxy instance representing the database.
        :rtype: ~azure.cosmos.aio.DatabaseProxy
        """
        ...

    @overload
    async def create_database_if_not_exists(  # pylint: disable=redefined-builtin
        self,
        id: str,
        *,
        offer_throughput: Optional[Union[int, ThroughputProperties]] = None,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        return_properties: Literal[True],
        **kwargs: Any
    ) -> tuple[DatabaseProxy, CosmosDict]:
        """
        Create the database if it does not exist already.

        If the database already exists, the existing settings are returned.

        ..note::
            This function does not check or update existing database settings or
            offer throughput if they differ from what is passed in.

        :param str id: ID (name) of the database to read or create.
        :keyword Union[int, ~azure.cosmos.ThroughputProperties] offer_throughput: The provisioned throughput
            for this database.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword Callable[[dict[str, str], dict[str, Any]], None] response_hook: A callable invoked with
            the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :keyword bool return_properties: Specifies whether to return either a DatabaseProxy
            or a Tuple containing a DatabaseProxy and the associated database properties.
        :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The database read or creation failed.
        :returns: A tuple of DatabaseProxy and CosmosDict with the database properties.
        :rtype: tuple [~azure.cosmos.aio.DatabaseProxy, ~azure.cosmos.CosmosDict]
        """
        ...

    @distributed_trace_async
    async def create_database_if_not_exists( # pylint:disable=docstring-should-be-keyword
        self,
        *args: Any,
        **kwargs: Any
    ) -> Union[DatabaseProxy, tuple[DatabaseProxy, CosmosDict]]:
        """
        Create the database if it does not exist already.

        If the database already exists, the existing settings are returned.

        ..note::
            This function does not check or update existing database settings or
            offer throughput if they differ from what is passed in.

        :param Any args: args
        :param str id: ID (name) of the database to read or create.
        :keyword Union[int, ~azure.cosmos.ThroughputProperties] offer_throughput: The provisioned throughput
            for this database.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword Callable[[dict[str, str], dict[str, Any]], None] response_hook: A callable invoked with
            the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :keyword bool return_properties: Specifies whether to return either a DatabaseProxy
            or a Tuple containing a DatabaseProxy and the associated database properties.
        :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The database read or creation failed.
        :returns: A DatabaseProxy instance representing the database or a tuple of DatabaseProxy
            and CosmosDict with the database properties.
        :rtype: ~azure.cosmos.aio.DatabaseProxy or tuple [~azure.cosmos.aio.DatabaseProxy, ~azure.cosmos.CosmosDict]
        """

        id = args[0] if args else kwargs.pop("id")
        if len(args) > 1:
            raise TypeError(f"Unexpected positional parameters: {args[1:]}")

        session_token = kwargs.get('session_token')
        if session_token is not None:
            warnings.warn(
                "The 'session_token' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        etag = kwargs.get('etag')
        if etag is not None:
            warnings.warn(
                "The 'etag' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        match_condition = kwargs.get('match_condition')
        if match_condition is not None:
            warnings.warn(
                "The 'match_condition' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)

        offer_throughput = kwargs.pop("offer_throughput", None)
        return_properties = kwargs.pop("return_properties", False)

        try:
            database_proxy = self.get_database_client(id)
            result = await database_proxy.read(**kwargs)
            if not return_properties:
                return database_proxy
            return database_proxy, result
        except CosmosResourceNotFoundError:
            return await self.create_database(
                id,
                offer_throughput=offer_throughput,
                return_properties=return_properties,
                **kwargs
            )

    def get_database_client(self, database: Union[str, DatabaseProxy, dict[str, Any]]) -> DatabaseProxy:
        """Retrieve an existing database with the ID (name) `id`.

        :param database: The ID (name), dict representing the properties, or :class:`DatabaseProxy`
            instance of the database to get.
        :type database: Union[str, ~azure.cosmos.DatabaseProxy, dict[str, Any]]
        :returns: A `DatabaseProxy` instance representing the retrieved database.
        :rtype: ~azure.cosmos.DatabaseProxy
        """
        if isinstance(database, str):
            id_value = database
        elif isinstance(database, DatabaseProxy):
            id_value = database.id
        else:
            id_value = str(database['id'])
        return DatabaseProxy(self.client_connection, id_value)

    @distributed_trace
    def list_databases(
        self,
        *,
        max_item_count: Optional[int] = None,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        **kwargs: Any
    ) -> AsyncItemPaged[dict[str, Any]]:
        """List the databases in a Cosmos DB SQL database account.

        :keyword int max_item_count: Max number of items to be returned in the enumeration operation.
        :keyword str session_token: Token for use with Session consistency.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword response_hook: A callable invoked with the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :paramtype response_hook: Callable[[Mapping[str, Any]], None]
        :returns: An AsyncItemPaged of database properties (dicts).
        :rtype: AsyncItemPaged[dict[str, str]]
        """
        session_token = kwargs.pop('session_token', None)
        if session_token is not None:
            warnings.warn(
                "The 'session_token' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        if initial_headers is not None:
            kwargs["initial_headers"] = initial_headers
        if throughput_bucket is not None:
            kwargs["throughput_bucket"] = throughput_bucket
        feed_options = _build_options(kwargs)
        if max_item_count is not None:
            feed_options["maxItemCount"] = max_item_count

        result = self.client_connection.ReadDatabases(options=feed_options, **kwargs)
        if response_hook:
            response_hook(self.client_connection.last_response_headers)
        return result

    @distributed_trace
    def query_databases(
        self,
        query: str,
        *,
        parameters: Optional[list[dict[str, Any]]] = None,
        max_item_count: Optional[int] = None,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        **kwargs: Any
    ) -> AsyncItemPaged[dict[str, Any]]:
        """Query the databases in a Cosmos DB SQL database account.

        :param Union[str, dict[str, Any]] query: The Azure Cosmos DB SQL query to execute.
        :keyword parameters: Optional array of parameters to the query.
            Each parameter is a dict() with 'name' and 'value' keys.
        :paramtype parameters: list[dict[str, Any]]
        :keyword int max_item_count: Max number of items to be returned in the enumeration operation.
        :keyword str session_token: Token for use with Session consistency.
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword response_hook: A callable invoked with the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :paramtype response_hook: Callable[[Mapping[str, Any]], None]
        :returns: An AsyncItemPaged of database properties (dicts).
        :rtype: AsyncItemPaged[dict[str, str]]
        """
        session_token = kwargs.get('session_token')
        if session_token is not None:
            warnings.warn(
                "The 'session_token' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        if initial_headers is not None:
            kwargs["initial_headers"] = initial_headers
        if throughput_bucket is not None:
            kwargs['throughput_bucket'] = throughput_bucket
        feed_options = _build_options(kwargs)
        if max_item_count is not None:
            feed_options["maxItemCount"] = max_item_count

        result = self.client_connection.QueryDatabases(
            query=query if parameters is None else {"query": query, "parameters": parameters},
            options=feed_options,
            **kwargs)
        if response_hook:
            response_hook(self.client_connection.last_response_headers)
        return result

    @distributed_trace_async
    async def delete_database(
        self,
        database: Union[str, DatabaseProxy, dict[str, Any]],
        *,
        initial_headers: Optional[dict[str, str]] = None,
        response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
        throughput_bucket: Optional[int] = None,
        **kwargs: Any
    ) -> None:
        """Delete the database with the given ID (name).

        :param database: The ID (name), dict representing the properties, or :class:`DatabaseProxy`
            instance of the database to delete.
        :type database: Union[str, ~azure.cosmos.DatabaseProxy, dict[str, Any]]
        :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
        :keyword response_hook: A callable invoked with the response metadata.
        :keyword int throughput_bucket: The desired throughput bucket for the client
        :paramtype response_hook: Callable[[Mapping[str, Any]], None]
        :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the database couldn't be deleted.
        :rtype: None
        """
        session_token = kwargs.get('session_token')
        if session_token is not None:
            warnings.warn(
                "The 'session_token' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        etag = kwargs.get('etag')
        if etag is not None:
            warnings.warn(
                "The 'etag' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        match_condition = kwargs.get('match_condition')
        if match_condition is not None:
            warnings.warn(
                "The 'match_condition' flag does not apply to this method and is always ignored even if passed."
                " It will now be removed in the future.",
                DeprecationWarning)
        if throughput_bucket is not None:
            kwargs['throughput_bucket'] = throughput_bucket
        if initial_headers is not None:
            kwargs["initial_headers"] = initial_headers
        request_options = _build_options(kwargs)
        database_link = _get_database_link(database)
        await self.client_connection.DeleteDatabase(database_link, options=request_options, **kwargs)
        if response_hook:
            response_hook(self.client_connection.last_response_headers)

    @distributed_trace_async
    async def _get_database_account(
            self,
            *,
            response_hook: Optional[Callable[[Mapping[str, Any]], None]] = None,
            **kwargs: Any
    ) -> DatabaseAccount:
        """Retrieve the database account information.

        :keyword response_hook: A callable invoked with the response metadata.
        :paramtype response_hook: Callable[[Mapping[str, Any]], None]
        :returns: A `DatabaseAccount` instance representing the Cosmos DB Database Account.
        :rtype: ~azure.cosmos.DatabaseAccount
        """
        result = await self.client_connection.GetDatabaseAccount(**kwargs)
        if response_hook:
            response_hook(self.client_connection.last_response_headers)
        return result
