import base64
import json
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

from moto.core.base_backend import BackendDict, BaseBackend
from moto.core.common_models import BaseModel
from moto.core.utils import unix_time
from moto.moto_api._internal import mock_random
from moto.utilities.tagging_service import TaggingService
from moto.utilities.utils import get_partition

from .exceptions import (
    BadRequestException,
    EventsAPINotFound,
    GraphqlAPICacheNotFound,
    GraphqlAPINotFound,
    GraphQLSchemaException,
)

# AWS custom scalars and directives
# https://github.com/dotansimha/graphql-code-generator/discussions/4311#discussioncomment-2921796
AWS_CUSTOM_GRAPHQL = """scalar AWSTime
scalar AWSDateTime
scalar AWSTimestamp
scalar AWSEmail
scalar AWSJSON
scalar AWSURL
scalar AWSPhone
scalar AWSIPAddress
scalar BigInt
scalar Double

directive @aws_subscribe(mutations: [String!]!) on FIELD_DEFINITION

# Allows transformer libraries to deprecate directive arguments.
directive @deprecated(reason: String!) on INPUT_FIELD_DEFINITION | ENUM

directive @aws_auth(cognito_groups: [String!]!) on FIELD_DEFINITION
directive @aws_api_key on FIELD_DEFINITION | OBJECT
directive @aws_iam on FIELD_DEFINITION | OBJECT
directive @aws_oidc on FIELD_DEFINITION | OBJECT
directive @aws_cognito_user_pools(
  cognito_groups: [String!]
) on FIELD_DEFINITION | OBJECT
"""


# region: APICache
class APICache(BaseModel):
    def __init__(
        self,
        ttl: int,
        api_caching_behavior: str,
        type_: str,
        transit_encryption_enabled: Optional[bool] = None,
        at_rest_encryption_enabled: Optional[bool] = None,
        health_metrics_config: Optional[str] = None,
    ):
        self.ttl = ttl
        self.api_caching_behavior = api_caching_behavior
        self.type = type_
        self.transit_encryption_enabled = transit_encryption_enabled or False
        self.at_rest_encryption_enabled = at_rest_encryption_enabled or False
        self.health_metrics_config = health_metrics_config or "DISABLED"
        self.status = "AVAILABLE"

    def update(
        self,
        ttl: int,
        api_caching_behavior: str,
        type: str,
        health_metrics_config: Optional[str] = None,
    ) -> None:
        self.ttl = ttl
        self.api_caching_behavior = api_caching_behavior
        self.type = type
        if health_metrics_config is not None:
            self.health_metrics_config = health_metrics_config

    def to_json(self) -> Dict[str, Any]:
        return {
            "ttl": self.ttl,
            "transitEncryptionEnabled": self.transit_encryption_enabled,
            "atRestEncryptionEnabled": self.at_rest_encryption_enabled,
            "apiCachingBehavior": self.api_caching_behavior,
            "type": self.type,
            "healthMetricsConfig": self.health_metrics_config,
            "status": self.status,
        }


# endregion


# region: GraphqlAPI
class GraphqlSchema(BaseModel):
    def __init__(self, definition: Any, region_name: str):
        self.definition = definition
        self.region_name = region_name
        # [graphql.language.ast.ObjectTypeDefinitionNode, ..]
        self.types: List[Any] = []

        self.status = "PROCESSING"
        self.parse_error: Optional[str] = None
        self._parse_graphql_definition()

    def get_type(self, name: str) -> Optional[Dict[str, Any]]:  # type: ignore[return]
        for graphql_type in self.types:
            if graphql_type.name.value == name:
                return {
                    "name": name,
                    "description": graphql_type.description.value
                    if graphql_type.description
                    else None,
                    "arn": f"arn:{get_partition(self.region_name)}:appsync:graphql_type/{name}",
                    "definition": "NotYetImplemented",
                }

    def get_status(self) -> Tuple[str, Optional[str]]:
        return self.status, self.parse_error

    def _parse_graphql_definition(self) -> None:
        try:
            from graphql import parse
            from graphql.error.graphql_error import GraphQLError
            from graphql.language.ast import ObjectTypeDefinitionNode

            res = parse(self.definition)
            for definition in res.definitions:
                if isinstance(definition, ObjectTypeDefinitionNode):
                    self.types.append(definition)
            self.status = "SUCCESS"
        except GraphQLError as e:
            self.status = "FAILED"
            self.parse_error = str(e)

    def get_introspection_schema(self, format_: str, include_directives: bool) -> str:
        from graphql import (
            build_client_schema,
            build_schema,
            introspection_from_schema,
            print_schema,
        )

        schema = build_schema(self.definition + AWS_CUSTOM_GRAPHQL)
        introspection_data = introspection_from_schema(schema, descriptions=False)

        if not include_directives:
            introspection_data["__schema"]["directives"] = []

        if format_ == "SDL":
            return print_schema(build_client_schema(introspection_data))
        elif format_ == "JSON":
            return json.dumps(introspection_data)
        else:
            raise BadRequestException(message=f"Invalid format {format_} given")


class GraphqlAPIKey(BaseModel):
    def __init__(self, description: str, expires: Optional[int]):
        self.key_id = str(mock_random.uuid4())[0:6]
        self.description = description
        if not expires:
            default_expiry = datetime.now(timezone.utc)
            default_expiry = default_expiry.replace(
                minute=0, second=0, microsecond=0, tzinfo=None
            )
            default_expiry = default_expiry + timedelta(days=7)
            self.expires = unix_time(default_expiry)
        else:
            self.expires = expires

    def update(self, description: Optional[str], expires: Optional[int]) -> None:
        if description:
            self.description = description
        if expires:
            self.expires = expires

    def to_json(self) -> Dict[str, Any]:
        return {
            "id": self.key_id,
            "description": self.description,
            "expires": self.expires,
            "deletes": self.expires,
        }


class GraphqlAPI(BaseModel):
    def __init__(
        self,
        account_id: str,
        region: str,
        name: str,
        authentication_type: str,
        additional_authentication_providers: Optional[List[str]],
        log_config: str,
        xray_enabled: str,
        user_pool_config: str,
        open_id_connect_config: str,
        lambda_authorizer_config: str,
        visibility: str,
        backend: "AppSyncBackend",
    ) -> None:
        self.region = region
        self.name = name
        self.api_id = str(mock_random.uuid4())
        self.authentication_type = authentication_type
        self.additional_authentication_providers = additional_authentication_providers
        self.lambda_authorizer_config = lambda_authorizer_config
        self.log_config = log_config
        self.open_id_connect_config = open_id_connect_config
        self.user_pool_config = user_pool_config
        self.xray_enabled = xray_enabled
        self.visibility = visibility or "GLOBAL"  # Default to Global if not provided

        self.arn = f"arn:{get_partition(self.region)}:appsync:{self.region}:{account_id}:apis/{self.api_id}"
        self.graphql_schema: Optional[GraphqlSchema] = None

        self.api_keys: Dict[str, GraphqlAPIKey] = dict()

        self.api_cache: Optional[APICache] = None
        self.backend = backend

    def update(
        self,
        name: str,
        additional_authentication_providers: Optional[List[str]],
        authentication_type: str,
        lambda_authorizer_config: str,
        log_config: str,
        open_id_connect_config: str,
        user_pool_config: str,
        xray_enabled: str,
    ) -> None:
        if name:
            self.name = name
        if additional_authentication_providers:
            self.additional_authentication_providers = (
                additional_authentication_providers
            )
        if authentication_type:
            self.authentication_type = authentication_type
        if lambda_authorizer_config:
            self.lambda_authorizer_config = lambda_authorizer_config
        if log_config:
            self.log_config = log_config
        if open_id_connect_config:
            self.open_id_connect_config = open_id_connect_config
        if user_pool_config:
            self.user_pool_config = user_pool_config
        if xray_enabled is not None:
            self.xray_enabled = xray_enabled

    def create_api_key(self, description: str, expires: Optional[int]) -> GraphqlAPIKey:
        api_key = GraphqlAPIKey(description, expires)
        self.api_keys[api_key.key_id] = api_key
        return api_key

    def list_api_keys(self) -> Iterable[GraphqlAPIKey]:
        return self.api_keys.values()

    def delete_api_key(self, api_key_id: str) -> None:
        self.api_keys.pop(api_key_id)

    def update_api_key(
        self, api_key_id: str, description: str, expires: Optional[int]
    ) -> GraphqlAPIKey:
        api_key = self.api_keys[api_key_id]
        api_key.update(description, expires)
        return api_key

    def start_schema_creation(self, definition: str) -> None:
        graphql_definition = base64.b64decode(definition).decode("utf-8")

        self.graphql_schema = GraphqlSchema(graphql_definition, region_name=self.region)

    def get_schema_status(self) -> Any:
        return self.graphql_schema.get_status()  # type: ignore[union-attr]

    def get_type(self, type_name: str, type_format: str) -> Any:
        graphql_type = self.graphql_schema.get_type(type_name)  # type: ignore[union-attr]
        graphql_type["format"] = type_format  # type: ignore[index]
        return graphql_type

    def create_api_cache(
        self,
        ttl: int,
        api_caching_behavior: str,
        type: str,
        transit_encryption_enabled: Optional[bool] = None,
        at_rest_encryption_enabled: Optional[bool] = None,
        health_metrics_config: Optional[str] = None,
    ) -> APICache:
        self.api_cache = APICache(
            ttl,
            api_caching_behavior,
            type,
            transit_encryption_enabled,
            at_rest_encryption_enabled,
            health_metrics_config,
        )
        return self.api_cache

    def update_api_cache(
        self,
        ttl: int,
        api_caching_behavior: str,
        type: str,
        health_metrics_config: Optional[str] = None,
    ) -> APICache:
        self.api_cache.update(ttl, api_caching_behavior, type, health_metrics_config)  # type: ignore[union-attr]
        return self.api_cache  # type: ignore[return-value]

    def delete_api_cache(self) -> None:
        self.api_cache = None

    def to_json(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "apiId": self.api_id,
            "authenticationType": self.authentication_type,
            "arn": self.arn,
            "uris": {"GRAPHQL": "http://graphql.uri"},
            "additionalAuthenticationProviders": self.additional_authentication_providers,
            "lambdaAuthorizerConfig": self.lambda_authorizer_config,
            "logConfig": self.log_config,
            "openIDConnectConfig": self.open_id_connect_config,
            "userPoolConfig": self.user_pool_config,
            "xrayEnabled": self.xray_enabled,
            "visibility": self.visibility,
            "tags": self.backend.list_tags_for_resource(self.arn),
        }


# endregion


# region: EventsAPI
class EventsAPIKey(BaseModel):
    def __init__(self, description: str, expires: Optional[int]):
        self.key_id = str(mock_random.uuid4())[0:6]
        self.description = description
        if not expires:
            default_expiry = datetime.now(timezone.utc)
            default_expiry = default_expiry.replace(
                minute=0, second=0, microsecond=0, tzinfo=None
            )
            default_expiry = default_expiry + timedelta(days=7)
            self.expires = unix_time(default_expiry)
        else:
            self.expires = expires

    def update(self, description: Optional[str], expires: Optional[int]) -> None:
        if description:
            self.description = description
        if expires:
            self.expires = expires

    def to_json(self) -> Dict[str, Any]:
        return {
            "id": self.key_id,
            "description": self.description,
            "expires": self.expires,
            "deletes": self.expires,
        }


class ChannelNamespace(BaseModel):
    def __init__(
        self,
        api_id: str,
        name: str,
        subscribe_auth_modes: List[Dict[str, str]],
        publish_auth_modes: List[Dict[str, str]],
        code_handlers: Optional[List[Dict[str, Any]]] = None,
        handler_configs: Optional[Dict[str, Any]] = None,
        account_id: str = "",
        region: str = "",
        backend: Optional["AppSyncBackend"] = None,
    ) -> None:
        self.api_id = api_id
        self.name = name
        self.subscribe_auth_modes = subscribe_auth_modes
        self.publish_auth_modes = publish_auth_modes
        self.code_handlers = code_handlers or []
        self.handler_configs = handler_configs or {}

        self.channel_namespace_arn = f"arn:{get_partition(region)}:appsync:{region}:{account_id}:apis/{api_id}/channelNamespace/{name}"

        now = datetime.now(timezone.utc).isoformat()
        self.created = now
        self.last_modified = now

        self.backend = backend

    def to_json(self) -> Dict[str, Any]:
        response = {
            "apiId": self.api_id,
            "name": self.name,
            "subscribeAuthModes": self.subscribe_auth_modes,
            "publishAuthModes": self.publish_auth_modes,
            "channelNamespaceArn": self.channel_namespace_arn,
            "created": self.created,
            "lastModified": self.last_modified,
            "handlerConfigs": self.handler_configs,
        }

        if self.code_handlers:
            response["codeHandlers"] = self.code_handlers

        if self.backend:
            response["tags"] = self.backend.list_tags_for_resource(
                self.channel_namespace_arn
            )

        return response


class EventsAPI(BaseModel):
    def __init__(
        self,
        account_id: str,
        region: str,
        name: str,
        owner_contact: Optional[str],
        event_config: Optional[Dict[str, Any]],
        backend: "AppSyncBackend",
    ) -> None:
        self.region = region
        self.name = name
        self.api_id = str(mock_random.get_random_string(length=26))
        self.owner_contact = owner_contact
        self.event_config = event_config

        self.api_arn = f"arn:{get_partition(self.region)}:appsync:{self.region}:{account_id}:apis/{self.api_id}"

        self.api_keys: Dict[str, EventsAPIKey] = dict()
        self.channel_namespaces: List[ChannelNamespace] = list()

        dns_prefix = str(mock_random.get_random_string(length=26))
        self.dns = {
            "REALTIME": f"{dns_prefix}.appsync-realtime-api.{self.region}.amazonaws.com",
            "HTTP": f"{dns_prefix}.appsync-api.{self.region}.amazonaws.com",
        }

        self.created = datetime.now(timezone.utc).isoformat()

        self.backend = backend

    def to_json(self) -> Dict[str, Any]:
        response = {
            "apiId": self.api_id,
            "name": self.name,
            "tags": self.backend.list_tags_for_resource(self.api_arn),
            "dns": self.dns,
            "apiArn": self.api_arn,
            "created": self.created,
            "eventConfig": self.event_config or {},  # Default to empty dict if None
        }

        if self.owner_contact:
            response["ownerContact"] = self.owner_contact

        return response

    def create_api_key(self, description: str, expires: Optional[int]) -> EventsAPIKey:
        api_key = EventsAPIKey(description, expires)
        self.api_keys[api_key.key_id] = api_key
        return api_key

    def list_api_keys(self) -> Iterable[EventsAPIKey]:
        return self.api_keys.values()

    def delete_api_key(self, api_key_id: str) -> None:
        self.api_keys.pop(api_key_id)

    def update_api_key(
        self, api_key_id: str, description: str, expires: Optional[int]
    ) -> EventsAPIKey:
        api_key = self.api_keys[api_key_id]
        api_key.update(description, expires)
        return api_key


# endregion


# region: AppSyncBackend
class AppSyncBackend(BaseBackend):
    """Implementation of AppSync APIs."""

    def __init__(self, region_name: str, account_id: str) -> None:
        super().__init__(region_name, account_id)
        self.graphql_apis: Dict[str, GraphqlAPI] = dict()
        self.events_apis: Dict[str, EventsAPI] = dict()
        self.tagger = TaggingService()

    def create_graphql_api(
        self,
        name: str,
        log_config: str,
        authentication_type: str,
        user_pool_config: str,
        open_id_connect_config: str,
        additional_authentication_providers: Optional[List[str]],
        xray_enabled: str,
        lambda_authorizer_config: str,
        tags: Dict[str, str],
        visibility: str,
    ) -> GraphqlAPI:
        graphql_api = GraphqlAPI(
            account_id=self.account_id,
            region=self.region_name,
            name=name,
            authentication_type=authentication_type,
            additional_authentication_providers=additional_authentication_providers,
            log_config=log_config,
            xray_enabled=xray_enabled,
            user_pool_config=user_pool_config,
            open_id_connect_config=open_id_connect_config,
            lambda_authorizer_config=lambda_authorizer_config,
            visibility=visibility,
            backend=self,
        )
        self.graphql_apis[graphql_api.api_id] = graphql_api
        self.tagger.tag_resource(
            graphql_api.arn, TaggingService.convert_dict_to_tags_input(tags)
        )
        return graphql_api

    def update_graphql_api(
        self,
        api_id: str,
        name: str,
        log_config: str,
        authentication_type: str,
        user_pool_config: str,
        open_id_connect_config: str,
        additional_authentication_providers: Optional[List[str]],
        xray_enabled: str,
        lambda_authorizer_config: str,
    ) -> GraphqlAPI:
        graphql_api = self.graphql_apis[api_id]
        graphql_api.update(
            name,
            additional_authentication_providers,
            authentication_type,
            lambda_authorizer_config,
            log_config,
            open_id_connect_config,
            user_pool_config,
            xray_enabled,
        )
        return graphql_api

    def get_graphql_api(self, api_id: str) -> GraphqlAPI:
        if api_id not in self.graphql_apis:
            raise GraphqlAPINotFound(api_id)
        return self.graphql_apis[api_id]

    def get_graphql_schema(self, api_id: str) -> GraphqlSchema:
        graphql_api = self.get_graphql_api(api_id)
        if not graphql_api.graphql_schema:
            # When calling get_introspetion_schema without a graphql schema
            # the response GraphQLSchemaException exception includes InvalidSyntaxError
            # in the message. This might not be the case for other methods.
            raise GraphQLSchemaException(message="InvalidSyntaxError")
        return graphql_api.graphql_schema

    def delete_graphql_api(self, api_id: str) -> None:
        self.graphql_apis.pop(api_id)

    def list_graphql_apis(self) -> Iterable[GraphqlAPI]:
        """
        Pagination or the maxResults-parameter have not yet been implemented.
        """
        return self.graphql_apis.values()

    def create_api_key(
        self, api_id: str, description: str, expires: Optional[int]
    ) -> Union[GraphqlAPIKey, EventsAPIKey]:
        if api_id in self.graphql_apis:
            return self.graphql_apis[api_id].create_api_key(description, expires)
        else:
            return self.events_apis[api_id].create_api_key(description, expires)

    def delete_api_key(self, api_id: str, api_key_id: str) -> None:
        if api_id in self.graphql_apis:
            self.graphql_apis[api_id].delete_api_key(api_key_id)
        else:
            self.events_apis[api_id].delete_api_key(api_key_id)

    def list_api_keys(
        self, api_id: str
    ) -> Iterable[Union[GraphqlAPIKey, EventsAPIKey]]:
        """
        Pagination or the maxResults-parameter have not yet been implemented.
        """
        if api_id in self.graphql_apis:
            return self.graphql_apis[api_id].list_api_keys()
        elif api_id in self.events_apis:
            return self.events_apis[api_id].list_api_keys()
        else:
            return []

    def update_api_key(
        self,
        api_id: str,
        api_key_id: str,
        description: str,
        expires: Optional[int],
    ) -> Union[GraphqlAPIKey, EventsAPIKey]:
        if api_id in self.graphql_apis:
            return self.graphql_apis[api_id].update_api_key(
                api_key_id, description, expires
            )
        else:
            return self.events_apis[api_id].update_api_key(
                api_key_id, description, expires
            )

    def start_schema_creation(self, api_id: str, definition: str) -> str:
        self.graphql_apis[api_id].start_schema_creation(definition)
        return "PROCESSING"

    def get_schema_creation_status(self, api_id: str) -> Any:
        return self.graphql_apis[api_id].get_schema_status()

    def tag_resource(self, resource_arn: str, tags: Dict[str, str]) -> None:
        self.tagger.tag_resource(
            resource_arn, TaggingService.convert_dict_to_tags_input(tags)
        )

    def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
        self.tagger.untag_resource_using_names(resource_arn, tag_keys)

    def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
        return self.tagger.get_tag_dict_for_resource(resource_arn)

    def get_type(self, api_id: str, type_name: str, type_format: str) -> Any:
        return self.graphql_apis[api_id].get_type(type_name, type_format)

    def get_api_cache(self, api_id: str) -> APICache:
        if api_id not in self.graphql_apis:
            raise GraphqlAPINotFound(api_id)
        api_cache = self.graphql_apis[api_id].api_cache
        if api_cache is None:
            raise GraphqlAPICacheNotFound("get")
        return api_cache

    def delete_api_cache(self, api_id: str) -> None:
        if api_id not in self.graphql_apis:
            raise GraphqlAPINotFound(api_id)
        if self.graphql_apis[api_id].api_cache is None:
            raise GraphqlAPICacheNotFound("delete")
        self.graphql_apis[api_id].delete_api_cache()
        return

    def create_api_cache(
        self,
        api_id: str,
        ttl: int,
        api_caching_behavior: str,
        type: str,
        transit_encryption_enabled: Optional[bool] = None,
        at_rest_encryption_enabled: Optional[bool] = None,
        health_metrics_config: Optional[str] = None,
    ) -> APICache:
        if api_id not in self.graphql_apis:
            raise GraphqlAPINotFound(api_id)
        graphql_api = self.graphql_apis[api_id]
        if graphql_api.api_cache is not None:
            raise BadRequestException(message="The API has already enabled caching.")
        api_cache = graphql_api.create_api_cache(
            ttl,
            api_caching_behavior,
            type,
            transit_encryption_enabled,
            at_rest_encryption_enabled,
            health_metrics_config,
        )
        return api_cache

    def update_api_cache(
        self,
        api_id: str,
        ttl: int,
        api_caching_behavior: str,
        type: str,
        health_metrics_config: Optional[str] = None,
    ) -> APICache:
        if api_id not in self.graphql_apis:
            raise GraphqlAPINotFound(api_id)
        graphql_api = self.graphql_apis[api_id]
        if graphql_api.api_cache is None:
            raise GraphqlAPICacheNotFound("update")
        api_cache = graphql_api.update_api_cache(
            ttl, api_caching_behavior, type, health_metrics_config
        )
        return api_cache

    def flush_api_cache(self, api_id: str) -> None:
        if api_id not in self.graphql_apis:
            raise GraphqlAPINotFound(api_id)
        if self.graphql_apis[api_id].api_cache is None:
            raise GraphqlAPICacheNotFound("flush")
        return

    def create_api(
        self,
        name: str,
        owner_contact: Optional[str],
        tags: Optional[Dict[str, str]],
        event_config: Optional[Dict[str, Any]],
    ) -> EventsAPI:
        events_api = EventsAPI(
            account_id=self.account_id,
            region=self.region_name,
            name=name,
            owner_contact=owner_contact,
            event_config=event_config,
            backend=self,
        )

        self.events_apis[events_api.api_id] = events_api

        self.tagger.tag_resource(
            events_api.api_arn, TaggingService.convert_dict_to_tags_input(tags)
        )

        return events_api

    def list_apis(self) -> Iterable[EventsAPI]:
        """
        Pagination or the maxResults-parameter have not yet been implemented.
        """
        return self.events_apis.values()

    def delete_api(self, api_id: str) -> None:
        self.events_apis.pop(api_id)

    def create_channel_namespace(
        self,
        api_id: str,
        name: str,
        subscribe_auth_modes: List[Dict[str, str]],
        publish_auth_modes: List[Dict[str, str]],
        code_handlers: Optional[List[Dict[str, Any]]] = None,
        tags: Optional[Dict[str, str]] = None,
        handler_configs: Optional[Dict[str, Any]] = None,
    ) -> ChannelNamespace:
        # Check if API exists
        if api_id not in self.events_apis:
            raise EventsAPINotFound(api_id)

        channel_namespace = ChannelNamespace(
            api_id=api_id,
            name=name,
            subscribe_auth_modes=subscribe_auth_modes,
            publish_auth_modes=publish_auth_modes,
            code_handlers=code_handlers,
            handler_configs=handler_configs,
            account_id=self.account_id,
            region=self.region_name,
            backend=self,
        )

        for api in self.events_apis.values():
            if api.api_id == api_id:
                api.channel_namespaces.append(channel_namespace)

        if tags:
            self.tagger.tag_resource(
                channel_namespace.channel_namespace_arn,
                TaggingService.convert_dict_to_tags_input(tags),
            )

        return channel_namespace

    def list_channel_namespaces(self, api_id: str) -> Iterable[ChannelNamespace]:
        if api_id not in self.events_apis:
            raise EventsAPINotFound(api_id)
        return self.events_apis[api_id].channel_namespaces

    def delete_channel_namespace(self, api_id: str, name: str) -> None:
        if api_id not in self.events_apis:
            raise EventsAPINotFound(api_id)
        for channel_namespace in self.events_apis[api_id].channel_namespaces:
            if channel_namespace.name == name:
                self.events_apis[api_id].channel_namespaces.remove(channel_namespace)
                return

    def get_api(self, api_id: str) -> EventsAPI:
        if api_id not in self.events_apis:
            raise EventsAPINotFound(api_id)
        return self.events_apis[api_id]


# endregion


appsync_backends = BackendDict(AppSyncBackend, "appsync")
