"""
Async client.
"""
from abc import ABCMeta, abstractmethod
from json import dumps as serialize
from json import loads as deserialize
from typing import Any, Dict, Iterator, List, Optional, Union

from apply_defaults import apply_self  # type: ignore

from .client import Client
from .exceptions import ReceivedErrorResponseError
from .parse import parse
from .requests import Notification, Request
from .response import ErrorResponse, Response


class AsyncClient(Client, metaclass=ABCMeta):
    """
    Abstract base class for the asynchronous clients.

    Has async versions of the Client class's public methods.
    """

    @abstractmethod
    async def send_message(
        self, request: str, response_expected: bool, **kwargs: Any
    ) -> Response:
        """Override to transport the request"""

    @apply_self
    async def send(
        self,
        request: Union[str, Dict, List],
        trim_log_values: bool = False,
        validate_against_schema: bool = True,
        **kwargs: Any
    ) -> Response:
        """
        Async version of Client.send.
        """
        # We need both the serialized and deserialized version of the request
        if isinstance(request, str):
            request_text = request
            request_deserialized = deserialize(request)
        else:
            request_text = serialize(request)
            request_deserialized = request
        batch = isinstance(request_deserialized, list)
        response_expected = batch or "id" in request_deserialized
        self.log_request(request_text, trim_log_values=trim_log_values)
        response = await self.send_message(
            request_text, response_expected=response_expected, **kwargs
        )
        self.log_response(response, trim_log_values=trim_log_values)
        self.validate_response(response)
        response.data = parse(
            response.text, batch=batch, validate_against_schema=validate_against_schema
        )
        # If received a single error response, raise
        if isinstance(response.data, ErrorResponse):
            raise ReceivedErrorResponseError(response.data)
        return response

    @apply_self
    async def notify(
        self,
        method_name: str,
        *args: Any,
        trim_log_values: Optional[bool] = None,
        validate_against_schema: Optional[bool] = None,
        **kwargs: Any
    ) -> Response:
        """
        Async version of Client.notify.
        """
        return await self.send(
            Notification(method_name, *args, **kwargs),
            trim_log_values=trim_log_values,
            validate_against_schema=validate_against_schema,
        )

    @apply_self
    async def request(
        self,
        method_name: str,
        *args: Any,
        trim_log_values: bool = False,
        validate_against_schema: bool = True,
        id_generator: Optional[Iterator] = None,
        **kwargs: Any
    ) -> Response:
        """
        Async version of Client.request.
        """
        return await self.send(
            Request(method_name, id_generator=id_generator, *args, **kwargs),
            trim_log_values=trim_log_values,
            validate_against_schema=validate_against_schema,
        )