# ------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -------------------------------------------------------------------------
import binascii
import functools
from datetime import datetime
from typing import Any, Dict, List, Optional, Union, cast, overload
from typing_extensions import Literal
from azure.core import MatchConditions
from azure.core.paging import ItemPaged
from azure.core.credentials import TokenCredential, AzureKeyCredential
from azure.core.pipeline.policies import BearerTokenCredentialPolicy
from azure.core.polling import LROPoller
from azure.core.tracing.decorator import distributed_trace
from azure.core.exceptions import (
    ResourceExistsError,
    ResourceNotFoundError,
    ResourceModifiedError,
    ResourceNotModifiedError,
)
from azure.core.rest import HttpRequest, HttpResponse
from ._azure_appconfiguration_error import ResourceReadOnlyError
from ._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy
from ._generated import AzureAppConfiguration
from ._generated.models import SnapshotUpdateParameters, SnapshotStatus
from ._models import (
    ConfigurationSetting,
    ConfigurationSettingsFilter,
    ConfigurationSnapshot,
    ConfigurationSettingPropertiesPaged,
)
from ._utils import (
    prep_if_match,
    prep_if_none_match,
    get_key_filter,
    get_label_filter,
    parse_connection_string,
)
from ._sync_token import SyncTokenPolicy


class AzureAppConfigurationClient:
    """Represents a client that calls restful API of Azure App Configuration service.

    :param str base_url: Base url of the service.
    :param credential: An object which can provide secrets for the app configuration service
    :type credential: ~azure.core.credentials.TokenCredential
    :keyword api_version: Api Version. Default value is "2023-10-01". Note that overriding this default
        value may result in unsupported behavior.
    :paramtype api_version: str

    """

    # pylint:disable=protected-access
    def __init__(self, base_url: str, credential: TokenCredential, **kwargs: Any) -> None:
        try:
            if not base_url.lower().startswith("http"):
                base_url = f"https://{base_url}"
        except AttributeError as exc:
            raise ValueError("Base URL must be a string.") from exc

        if not credential:
            raise ValueError("Missing credential")

        credential_scopes = [f"{base_url.strip('/')}/.default"]
        self._sync_token_policy = SyncTokenPolicy()

        if isinstance(credential, AzureKeyCredential):
            id_credential = kwargs.pop("id_credential")
            kwargs.update(
                {
                    "authentication_policy": AppConfigRequestsCredentialsPolicy(credential, base_url, id_credential),
                }
            )
        elif isinstance(credential, TokenCredential):
            kwargs.update(
                {
                    "authentication_policy": BearerTokenCredentialPolicy(credential, *credential_scopes, **kwargs),
                }
            )
        else:
            raise TypeError(
                f"Unsupported credential: {type(credential)}. Use an instance of token credential from azure.identity"
            )
        # mypy doesn't compare the credential type hint with the API surface in patch.py
        self._impl = AzureAppConfiguration(
            credential, base_url, per_call_policies=self._sync_token_policy, **kwargs  # type: ignore[arg-type]
        )

    @classmethod
    def from_connection_string(cls, connection_string: str, **kwargs: Any) -> "AzureAppConfigurationClient":
        """Create AzureAppConfigurationClient from a Connection String.

        :param str connection_string: Connection String
            (one of the access keys of the Azure App Configuration resource)
            used to access the Azure App Configuration.
        :return: An AzureAppConfigurationClient authenticated with the connection string
        :rtype: ~azure.appconfiguration.AzureAppConfigurationClient

        Example

        .. code-block:: python

            from azure.appconfiguration import AzureAppConfigurationClient
            connection_str = "<my connection string>"
            client = AzureAppConfigurationClient.from_connection_string(connection_str)
        """
        endpoint, id_credential, secret = parse_connection_string(connection_string)
        # AzureKeyCredential type is for internal use, it's not exposed in public API.
        return cls(
            credential=AzureKeyCredential(secret),  # type: ignore[arg-type]
            base_url=endpoint,
            id_credential=id_credential,
            **kwargs,
        )

    @distributed_trace
    def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs) -> HttpResponse:
        """Runs a network request using the client's existing pipeline.

        The request URL can be relative to the vault URL. The service API version used for the request is the same as
        the client's unless otherwise specified. This method does not raise if the response is an error; to raise an
        exception, call `raise_for_status()` on the returned response object. For more information about how to send
        custom requests with this method, see https://aka.ms/azsdk/dpcodegen/python/send_request.

        :param request: The network request you want to make.
        :type request: ~azure.core.rest.HttpRequest
        :keyword bool stream: Whether the response payload will be streamed. Defaults to False.
        :return: The response of your network call. Does not do error handling on your response.
        :rtype: ~azure.core.rest.HttpResponse
        """
        return self._impl._send_request(request, stream=stream, **kwargs)

    @overload
    def list_configuration_settings(
        self,
        *,
        key_filter: Optional[str] = None,
        label_filter: Optional[str] = None,
        accept_datetime: Optional[Union[datetime, str]] = None,
        fields: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> ItemPaged[ConfigurationSetting]:
        """List the configuration settings stored in the configuration service, optionally filtered by
        key, label and accept_datetime.

        :keyword key_filter: filter results based on their keys. '*' can be
            used as wildcard in the beginning or end of the filter
        :paramtype key_filter: str or None
        :keyword label_filter: filter results based on their label. '*' can be
            used as wildcard in the beginning or end of the filter
        :paramtype label_filter: str or None
        :keyword accept_datetime: retrieve ConfigurationSetting existed at this datetime
        :paramtype accept_datetime: ~datetime.datetime or str or None
        :keyword list[str] fields: specify which fields to include in the results. Leave None to include all fields
        :return: An iterator of :class:`~azure.appconfiguration.ConfigurationSetting`
        :rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.ConfigurationSetting]
        :raises: :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`

        Example

        .. code-block:: python

            from datetime import datetime, timedelta

            accept_datetime = datetime.utcnow() + timedelta(days=-1)

            all_listed = client.list_configuration_settings()
            for item in all_listed:
                pass  # do something

            filtered_listed = client.list_configuration_settings(
                label_filter="Labe*", key_filter="Ke*", accept_datetime=str(accept_datetime)
            )
            for item in filtered_listed:
                pass  # do something
        """

    @overload
    def list_configuration_settings(
        self, *, snapshot_name: str, fields: Optional[List[str]] = None, **kwargs: Any
    ) -> ItemPaged[ConfigurationSetting]:
        """List the configuration settings stored under a snapshot in the configuration service, optionally filtered by
        fields to present in return.

        :keyword str snapshot_name: The snapshot name.
        :keyword fields: Specify which fields to include in the results. Leave None to include all fields.
        :type fields: list[str] or None
        :return: An iterator of :class:`~azure.appconfiguration.ConfigurationSetting`
        :rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.ConfigurationSetting]
        :raises: :class:`~azure.core.exceptions.HttpResponseError`
        """

    @distributed_trace
    def list_configuration_settings(self, *args, **kwargs) -> ItemPaged[ConfigurationSetting]:
        accept_datetime = kwargs.pop("accept_datetime", None)
        if isinstance(accept_datetime, datetime):
            accept_datetime = str(accept_datetime)
        select = kwargs.pop("fields", None)
        if select:
            select = ["locked" if x == "read_only" else x for x in select]
        snapshot_name = kwargs.pop("snapshot_name", None)

        try:
            if snapshot_name is not None:
                return self._impl.get_key_values(  # type: ignore
                    snapshot=snapshot_name,
                    accept_datetime=accept_datetime,
                    select=select,
                    cls=lambda objs: [ConfigurationSetting._from_generated(x) for x in objs],
                    **kwargs,
                )
            key_filter, kwargs = get_key_filter(*args, **kwargs)
            label_filter, kwargs = get_label_filter(*args, **kwargs)
            command = functools.partial(self._impl.get_key_values_in_one_page, **kwargs)  # type: ignore[attr-defined]
            return ItemPaged(
                command,
                key=key_filter,
                label=label_filter,
                accept_datetime=accept_datetime,
                select=select,
                page_iterator_class=ConfigurationSettingPropertiesPaged,
            )

        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def get_configuration_setting(
        self,
        key: str,
        label: Optional[str] = None,
        etag: Optional[str] = "*",
        match_condition: MatchConditions = MatchConditions.Unconditionally,
        *,
        accept_datetime: Optional[Union[datetime, str]] = None,
        **kwargs,
    ) -> Union[None, ConfigurationSetting]:
        """Get the matched ConfigurationSetting from Azure App Configuration service

        :param key: key of the ConfigurationSetting
        :type key: str
        :param label: label used to identify the ConfigurationSetting. Default is `None`.
        :type label: str or None
        :param etag: check if the ConfigurationSetting is changed. Set None to skip checking etag
        :type etag: str or None
        :param match_condition: The match condition to use upon the etag
        :type match_condition: ~azure.core.MatchConditions
        :keyword accept_datetime: retrieve ConfigurationSetting existed at this datetime
        :paramtype accept_datetime: ~datetime.datetime or str or None
        :return: The matched ConfigurationSetting object
        :rtype: ~azure.appconfiguration.ConfigurationSetting or None
        :raises: :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`, \
            :class:`~azure.core.exceptions.ResourceNotFoundError`, \
            :class:`~azure.core.exceptions.ResourceModifiedError`, \
            :class:`~azure.core.exceptions.ResourceExistsError`

        Example

        .. code-block:: python

            fetched_config_setting = client.get_configuration_setting(
                key="MyKey", label="MyLabel"
            )
        """
        if isinstance(accept_datetime, datetime):
            accept_datetime = str(accept_datetime)

        error_map: Dict[int, Any] = {}
        if match_condition == MatchConditions.IfNotModified:
            error_map.update({412: ResourceModifiedError})
        if match_condition == MatchConditions.IfPresent:
            error_map.update({412: ResourceNotFoundError})
        if match_condition == MatchConditions.IfMissing:
            error_map.update({412: ResourceExistsError})

        try:
            key_value = self._impl.get_key_value(
                key=key,
                label=label,
                accept_datetime=accept_datetime,
                if_match=prep_if_match(etag, match_condition),
                if_none_match=prep_if_none_match(etag, match_condition),
                error_map=error_map,
                **kwargs,
            )
            return ConfigurationSetting._from_generated(key_value)
        except ResourceNotModifiedError:
            return None
        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def add_configuration_setting(self, configuration_setting: ConfigurationSetting, **kwargs) -> ConfigurationSetting:
        """Add a ConfigurationSetting instance into the Azure App Configuration service.

        :param configuration_setting: the ConfigurationSetting object to be added
        :type configuration_setting: ~azure.appconfiguration.ConfigurationSetting
        :return: The ConfigurationSetting object returned from the App Configuration service
        :rtype: ~azure.appconfiguration.ConfigurationSetting
        :raises: :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`, \
            :class:`~azure.core.exceptions.ResourceExistsError`

        Example

        .. code-block:: python

            config_setting = ConfigurationSetting(
                key="MyKey",
                label="MyLabel",
                value="my value",
                content_type="my content type",
                tags={"my tag": "my tag value"}
            )
            added_config_setting = client.add_configuration_setting(config_setting)
        """
        key_value = configuration_setting._to_generated()
        error_map = {412: ResourceExistsError}
        try:
            key_value_added = self._impl.put_key_value(
                entity=key_value,
                key=key_value.key,  # type: ignore
                label=key_value.label,
                if_none_match="*",
                error_map=error_map,
                **kwargs,
            )
            return ConfigurationSetting._from_generated(key_value_added)
        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def set_configuration_setting(
        self,
        configuration_setting: ConfigurationSetting,
        match_condition: MatchConditions = MatchConditions.Unconditionally,
        *,
        etag: Optional[str] = None,
        **kwargs,
    ) -> ConfigurationSetting:
        """Add or update a ConfigurationSetting.
        If the configuration setting identified by key and label does not exist, this is a create.
        Otherwise this is an update.

        :param configuration_setting: the ConfigurationSetting to be added (if not exists) \
            or updated (if exists) to the service
        :type configuration_setting: ~azure.appconfiguration.ConfigurationSetting
        :param match_condition: The match condition to use upon the etag
        :type match_condition: ~azure.core.MatchConditions
        :keyword str etag: check if the ConfigurationSetting is changed. \
            Will use the value from param configuration_setting if not set.
        :return: The ConfigurationSetting returned from the service
        :rtype: ~azure.appconfiguration.ConfigurationSetting
        :raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
            :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`, \
            :class:`~azure.core.exceptions.ResourceModifiedError`, \
            :class:`~azure.core.exceptions.ResourceNotModifiedError`, \
            :class:`~azure.core.exceptions.ResourceNotFoundError`, \
            :class:`~azure.core.exceptions.ResourceExistsError`

        Example

        .. code-block:: python

            config_setting = ConfigurationSetting(
                key="MyKey",
                label="MyLabel",
                value="my set value",
                content_type="my set content type",
                tags={"my set tag": "my set tag value"}
            )
            returned_config_setting = client.set_configuration_setting(config_setting)
        """
        key_value = configuration_setting._to_generated()
        error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
        if match_condition == MatchConditions.IfNotModified:
            error_map.update({412: ResourceModifiedError})
        if match_condition == MatchConditions.IfModified:
            error_map.update({412: ResourceNotModifiedError})
        if match_condition == MatchConditions.IfPresent:
            error_map.update({412: ResourceNotFoundError})
        if match_condition == MatchConditions.IfMissing:
            error_map.update({412: ResourceExistsError})

        try:
            key_value_set = self._impl.put_key_value(
                entity=key_value,
                key=key_value.key,  # type: ignore
                label=key_value.label,
                if_match=prep_if_match(configuration_setting.etag, match_condition),
                if_none_match=prep_if_none_match(etag or configuration_setting.etag, match_condition),
                error_map=error_map,
                **kwargs,
            )
            return ConfigurationSetting._from_generated(key_value_set)
        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def delete_configuration_setting(  # pylint:disable=delete-operation-wrong-return-type
        self,
        key: str,
        label: Optional[str] = None,
        *,
        etag: Optional[str] = None,
        match_condition: MatchConditions = MatchConditions.Unconditionally,
        **kwargs,
    ) -> Union[None, ConfigurationSetting]:
        """Delete a ConfigurationSetting if it exists

        :param key: key used to identify the ConfigurationSetting
        :type key: str
        :param label: label used to identify the ConfigurationSetting. Default is `None`.
        :type label: str
        :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag
        :keyword match_condition: The match condition to use upon the etag
        :paramtype match_condition: ~azure.core.MatchConditions
        :return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist.
        :rtype: ~azure.appconfiguration.ConfigurationSetting
        :raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
            :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`, \
            :class:`~azure.core.exceptions.ResourceModifiedError`, \
            :class:`~azure.core.exceptions.ResourceNotModifiedError`, \
            :class:`~azure.core.exceptions.ResourceNotFoundError`, \
            :class:`~azure.core.exceptions.ResourceExistsError`

        Example

        .. code-block:: python

            deleted_config_setting = client.delete_configuration_setting(
                key="MyKey", label="MyLabel"
            )
        """
        error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
        if match_condition == MatchConditions.IfNotModified:
            error_map.update({412: ResourceModifiedError})
        if match_condition == MatchConditions.IfModified:
            error_map.update({412: ResourceNotModifiedError})
        if match_condition == MatchConditions.IfPresent:
            error_map.update({412: ResourceNotFoundError})
        if match_condition == MatchConditions.IfMissing:
            error_map.update({412: ResourceExistsError})

        try:
            key_value_deleted = self._impl.delete_key_value(
                key=key,
                label=label,
                if_match=prep_if_match(etag, match_condition),
                error_map=error_map,
                **kwargs,
            )
            if key_value_deleted:
                return ConfigurationSetting._from_generated(key_value_deleted)
            return None
        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def list_revisions(
        self,
        key_filter: Optional[str] = None,
        label_filter: Optional[str] = None,
        *,
        accept_datetime: Optional[Union[datetime, str]] = None,
        fields: Optional[List[str]] = None,
        **kwargs,
    ) -> ItemPaged[ConfigurationSetting]:
        """
        Find the ConfigurationSetting revision history, optionally filtered by key, label and accept_datetime.

        :param key_filter: filter results based on their keys. '*' can be
            used as wildcard in the beginning or end of the filter
        :type key_filter: str or None
        :param label_filter: filter results based on their label. '*' can be
            used as wildcard in the beginning or end of the filter
        :type label_filter: str or None
        :keyword accept_datetime: retrieve ConfigurationSetting existed at this datetime
        :paramtype accept_datetime: ~datetime.datetime or str or None
        :keyword list[str] fields: specify which fields to include in the results. Leave None to include all fields
        :return: An iterator of :class:`~azure.appconfiguration.ConfigurationSetting`
        :rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.ConfigurationSetting]
        :raises: :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`

        Example

        .. code-block:: python

            from datetime import datetime, timedelta

            accept_datetime = datetime.utcnow() + timedelta(days=-1)

            all_revisions = client.list_revisions()
            for item in all_revisions:
                pass  # do something

            filtered_revisions = client.list_revisions(
                label_filter="Labe*", key_filter="Ke*", accept_datetime=str(accept_datetime)
            )
            for item in filtered_revisions:
                pass  # do something
        """
        if isinstance(accept_datetime, datetime):
            accept_datetime = str(accept_datetime)
        if fields:
            fields = ["locked" if x == "read_only" else x for x in fields]

        try:
            return self._impl.get_revisions(  # type: ignore
                label=label_filter,
                key=key_filter,
                accept_datetime=accept_datetime,
                select=fields,
                cls=lambda objs: [ConfigurationSetting._from_generated(x) for x in objs],
                **kwargs,
            )
        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def set_read_only(
        self,
        configuration_setting: ConfigurationSetting,
        read_only: bool = True,
        *,
        match_condition: MatchConditions = MatchConditions.Unconditionally,
        **kwargs,
    ) -> ConfigurationSetting:
        """Set a configuration setting read only

        :param configuration_setting: the ConfigurationSetting to be set read only
        :type configuration_setting: ~azure.appconfiguration.ConfigurationSetting
        :param read_only: set the read only setting if true, else clear the read only setting
        :type read_only: bool
        :keyword match_condition: The match condition to use upon the etag
        :paramtype match_condition: ~azure.core.MatchConditions
        :return: The ConfigurationSetting returned from the service
        :rtype: ~azure.appconfiguration.ConfigurationSetting
        :raises: :class:`~azure.core.exceptions.HttpResponseError`, \
            :class:`~azure.core.exceptions.ClientAuthenticationError`, \
            :class:`~azure.core.exceptions.ResourceNotFoundError`

        Example

        .. code-block:: python

            config_setting = client.get_configuration_setting(
                key="MyKey", label="MyLabel"
            )

            read_only_config_setting = client.set_read_only(config_setting)
            read_only_config_setting = client.set_read_only(config_setting, read_only=False)
        """
        error_map: Dict[int, Any] = {}
        if match_condition == MatchConditions.IfNotModified:
            error_map.update({412: ResourceModifiedError})
        if match_condition == MatchConditions.IfModified:
            error_map.update({412: ResourceNotModifiedError})
        if match_condition == MatchConditions.IfPresent:
            error_map.update({412: ResourceNotFoundError})
        if match_condition == MatchConditions.IfMissing:
            error_map.update({412: ResourceExistsError})

        try:
            if read_only:
                key_value = self._impl.put_lock(
                    key=configuration_setting.key,
                    label=configuration_setting.label,
                    if_match=prep_if_match(configuration_setting.etag, match_condition),
                    if_none_match=prep_if_none_match(configuration_setting.etag, match_condition),
                    error_map=error_map,
                    **kwargs,
                )
            else:
                key_value = self._impl.delete_lock(
                    key=configuration_setting.key,
                    label=configuration_setting.label,
                    if_match=prep_if_match(configuration_setting.etag, match_condition),
                    if_none_match=prep_if_none_match(configuration_setting.etag, match_condition),
                    error_map=error_map,
                    **kwargs,
                )
            return ConfigurationSetting._from_generated(key_value)
        except binascii.Error as exc:
            raise binascii.Error("Connection string secret has incorrect padding") from exc

    @distributed_trace
    def begin_create_snapshot(
        self,
        name: str,
        filters: List[ConfigurationSettingsFilter],
        *,
        composition_type: Optional[Literal["key", "key_label"]] = None,
        retention_period: Optional[int] = None,
        tags: Optional[Dict[str, str]] = None,
        **kwargs,
    ) -> LROPoller[ConfigurationSnapshot]:
        """Create a snapshot of the configuration settings.

        :param name: The name of the configuration snapshot to create.
        :type name: str
        :param filters: A list of filters used to filter the configuration settings by key field and label field
            included in the configuration snapshot.
        :type filters: list[~azure.appconfiguration.ConfigurationSettingsFilter]
        :keyword composition_type: The composition type describes how the key-values within the configuration
            snapshot are composed. Known values are: "key" and "key_label". The "key" composition type
            ensures there are no two key-values containing the same key. The 'key_label' composition type ensures
            there are no two key-values containing the same key and label.
        :type composition_type: str or None
        :keyword retention_period: The amount of time, in seconds, that a configuration snapshot will remain in the
            archived state before expiring. This property is only writable during the creation of a configuration
            snapshot. If not specified, will set to 2592000(30 days). If specified, should be
            in range 3600(1 hour) to 7776000(90 days).
        :type retention_period: int or None
        :keyword tags: The tags of the configuration snapshot.
        :type tags: dict[str, str] or None
        :return: A poller for create configuration snapshot operation. Call `result()` on this object to wait for the
            operation to complete and get the created snapshot.
        :rtype: ~azure.core.polling.LROPoller[~azure.appconfiguration.ConfigurationSnapshot]
        :raises: :class:`~azure.core.exceptions.HttpResponseError`
        """
        snapshot = ConfigurationSnapshot(
            filters=filters, composition_type=composition_type, retention_period=retention_period, tags=tags
        )
        try:
            return cast(
                LROPoller[ConfigurationSnapshot],
                self._impl.begin_create_snapshot(
                    name=name, entity=snapshot._to_generated(), cls=ConfigurationSnapshot._from_deserialized, **kwargs
                ),
            )
        except binascii.Error:
            raise binascii.Error("Connection string secret has incorrect padding")  # pylint: disable=raise-missing-from

    @distributed_trace
    def archive_snapshot(
        self,
        name: str,
        *,
        match_condition: MatchConditions = MatchConditions.Unconditionally,
        etag: Optional[str] = None,
        **kwargs,
    ) -> ConfigurationSnapshot:
        """Archive a configuration setting snapshot. It will update the status of a snapshot from "ready" to "archived".
        The retention period will start to count, the snapshot will expire when the entire retention period elapses.

        :param name: The name of the configuration setting snapshot to archive.
        :type name: str
        :keyword match_condition: The match condition to use upon the etag.
        :type match_condition: ~azure.core.MatchConditions
        :keyword etag: Check if the ConfigurationSnapshot is changed. Set None to skip checking etag.
        :type etag: str or None
        :return: The ConfigurationSnapshot returned from the service.
        :rtype: ~azure.appconfiguration.ConfigurationSnapshot
        :raises: :class:`~azure.core.exceptions.HttpResponseError`
        """
        error_map: Dict[int, Any] = {}
        if match_condition == MatchConditions.IfNotModified:
            error_map.update({412: ResourceModifiedError})
        if match_condition == MatchConditions.IfModified:
            error_map.update({412: ResourceNotModifiedError})
        if match_condition == MatchConditions.IfPresent:
            error_map.update({412: ResourceNotFoundError})
        if match_condition == MatchConditions.IfMissing:
            error_map.update({412: ResourceExistsError})
        try:
            generated_snapshot = self._impl.update_snapshot(
                name=name,
                entity=SnapshotUpdateParameters(status=SnapshotStatus.ARCHIVED),
                if_match=prep_if_match(etag, match_condition),
                if_none_match=prep_if_none_match(etag, match_condition),
                error_map=error_map,
                **kwargs,
            )
            return ConfigurationSnapshot._from_generated(generated_snapshot)
        except binascii.Error:
            raise binascii.Error("Connection string secret has incorrect padding")  # pylint: disable=raise-missing-from

    @distributed_trace
    def recover_snapshot(
        self,
        name: str,
        *,
        match_condition: MatchConditions = MatchConditions.Unconditionally,
        etag: Optional[str] = None,
        **kwargs,
    ) -> ConfigurationSnapshot:
        """Recover a configuration setting snapshot. It will update the status of a snapshot from "archived" to "ready".

        :param name: The name of the configuration setting snapshot to recover.
        :type name: str
        :keyword match_condition: The match condition to use upon the etag.
        :type match_condition: ~azure.core.MatchConditions
        :keyword etag: Check if the ConfigurationSnapshot is changed. Set None to skip checking etag.
        :type etag: str or None
        :return: The ConfigurationSnapshot returned from the service.
        :rtype: ~azure.appconfiguration.ConfigurationSnapshot
        :raises: :class:`~azure.core.exceptions.HttpResponseError`
        """
        error_map: Dict[int, Any] = {}
        if match_condition == MatchConditions.IfNotModified:
            error_map.update({412: ResourceModifiedError})
        if match_condition == MatchConditions.IfModified:
            error_map.update({412: ResourceNotModifiedError})
        if match_condition == MatchConditions.IfPresent:
            error_map.update({412: ResourceNotFoundError})
        if match_condition == MatchConditions.IfMissing:
            error_map.update({412: ResourceExistsError})
        try:
            generated_snapshot = self._impl.update_snapshot(
                name=name,
                entity=SnapshotUpdateParameters(status=SnapshotStatus.READY),
                if_match=prep_if_match(etag, match_condition),
                if_none_match=prep_if_none_match(etag, match_condition),
                error_map=error_map,
                **kwargs,
            )
            return ConfigurationSnapshot._from_generated(generated_snapshot)
        except binascii.Error:
            raise binascii.Error("Connection string secret has incorrect padding")  # pylint: disable=raise-missing-from

    @distributed_trace
    def get_snapshot(self, name: str, *, fields: Optional[List[str]] = None, **kwargs) -> ConfigurationSnapshot:
        """Get a configuration setting snapshot.

        :param name: The name of the configuration setting snapshot to retrieve.
        :type name: str
        :keyword fields: Specify which fields to include in the results. Leave None to include all fields.
        :type fields: list[str] or None
        :return: The ConfigurationSnapshot returned from the service.
        :rtype: ~azure.appconfiguration.ConfigurationSnapshot
        :raises: :class:`~azure.core.exceptions.HttpResponseError`
        """
        try:
            generated_snapshot = self._impl.get_snapshot(
                name=name, if_match=None, if_none_match=None, select=fields, **kwargs
            )
            return ConfigurationSnapshot._from_generated(generated_snapshot)
        except binascii.Error:
            raise binascii.Error("Connection string secret has incorrect padding")  # pylint: disable=raise-missing-from

    @distributed_trace
    def list_snapshots(
        self,
        *,
        name: Optional[str] = None,
        fields: Optional[List[str]] = None,
        status: Optional[List[Union[str, SnapshotStatus]]] = None,
        **kwargs,
    ) -> ItemPaged[ConfigurationSnapshot]:
        """List the configuration setting snapshots stored in the configuration service, optionally filtered by
        snapshot name, snapshot status and fields to present in return.

        :keyword name: Filter results based on snapshot name.
        :type name: str or None
        :keyword fields: Specify which fields to include in the results. Leave None to include all fields.
        :type fields: list[str] or None
        :keyword status: Filter results based on snapshot keys.
        :type status: list[str] or list[~azure.appconfiguration.SnapshotStatus] or None
        :return: An iterator of :class:`~azure.appconfiguration.ConfigurationSnapshot`
        :rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.ConfigurationSnapshot]
        :raises: :class:`~azure.core.exceptions.HttpResponseError`
        """
        try:
            return self._impl.get_snapshots(  # type: ignore
                name=name,
                select=fields,
                status=status,
                cls=lambda objs: [ConfigurationSnapshot._from_generated(x) for x in objs],
                **kwargs,
            )
        except binascii.Error:
            raise binascii.Error("Connection string secret has incorrect padding")  # pylint: disable=raise-missing-from

    def update_sync_token(self, token: str) -> None:
        """Add a sync token to the internal list of tokens.

        :param str token: The sync token to be added to the internal list of tokens
        """
        if not self._sync_token_policy:
            raise AttributeError(
                "Client has no sync token policy, possibly because it was not provided during instantiation."
            )
        self._sync_token_policy.add_token(token)

    def close(self) -> None:
        """Close all connections made by the client"""
        self._impl._client.close()

    def __enter__(self) -> "AzureAppConfigurationClient":
        self._impl.__enter__()
        return self

    def __exit__(self, *args: Any) -> None:
        self._impl.__exit__(*args)
