from datetime import datetime
from typing import List, Optional

import aiohttp
from smb.common.http_client import (
    BadGateway,
    HttpClient,
    ServiceUnavailable,
    collect_errors,
)

from .entities import Data, Notification, Receiver, ThrottlePolicies
from .exceptions import (
    BalancerError,
    EmptyReceiverList,
    IncorrectPayload,
    IncorrectToken,
    InvalidData,
    InvalidNotification,
)


class SupClient(HttpClient):
    exceptions_to_retry = (
        BadGateway,
        ServiceUnavailable,
        BalancerError,
        aiohttp.ServerDisconnectedError,
        aiohttp.ServerTimeoutError,
    )

    def __init__(
        self,
        url: str,
        auth_token: str,
        project: str,
        throttle_policies: Optional[ThrottlePolicies] = None,
    ):
        self.auth_token = auth_token
        self.project = project
        self.throttle_policies = throttle_policies
        super().__init__(url=url)

    async def _handle_custom_errors(self, response: aiohttp.ClientResponse) -> None:
        if response.status == 504:
            raise BalancerError()
        elif response.status == 400:
            response_body = await response.read()
            raise IncorrectPayload(response_body)
        elif response.status == 401:
            raise IncorrectToken()
        else:
            await self._raise_unknown_response(response)

    @collect_errors
    async def send_push_notifications(
        self,
        *,
        receiver: List[Receiver],
        notification: Notification,
        data: Optional[Data] = None,
        throttle_policies: Optional[ThrottlePolicies] = None,
        schedule: datetime = None,
        adjust_time_zone: bool = True,
        ttl: int = 360,
        dry_run: bool = False,
        **kwargs,
    ) -> dict:
        """
        Sends push notification to specified receivers.
        Returns push notification id, total number of recipients of the notification
        received after all values in the receiver block have been resolved.
        :param receiver: List of recipients. If different identifiers refer
            to the same recipient (e.g., did and uuid refer to the same device),
            SUP will send only one push notification.
        :param notification: Notification content
        :param data: Push notification data for user actions and analytics log tracking.
        :param schedule: Scheduled delivery time. Default: now.
        :param adjust_time_zone: Time zone allowance.
            If False, timezone will not be considered.
        :param ttl: Time in seconds during which you will
            attempt to send a push notification.
            Recommended value 3600. It's not recommended
            to set ttl less than 60.
        :param dry_run: If True push request stack trace will be returned
        :param kwargs: optional params.
            Detailed param list can be found in SUP documentation
        :return:
            {
                "id": "push_notification_id",
                "receiver": ['login:861cae2a060f47c88e7dea0422cc9699', 'uid:15678'],
                "strace": {"stack_trace": "object"}
            }
        """
        if not receiver:
            raise EmptyReceiverList()

        if not notification or not isinstance(notification, Notification):
            raise InvalidNotification("Notification must be passed.")

        if data and not isinstance(data, Data):
            raise InvalidData("Data param must be Data entity object.")

        payload = {
            "project": self.project,
            "notification": notification.to_dict(),
            "receiver": [str(r) for r in receiver],
            "schedule": schedule.strftime("%Y-%m-%d'T'%H:%M:%S") if schedule else "now",
            "adjust_time_zone": adjust_time_zone,
            "ttl": ttl,
            **kwargs,
        }
        if data:
            payload["data"] = data.to_dict()

        if throttle_policies:
            payload["throttle_policies"] = throttle_policies.to_dict()
        elif self.throttle_policies:
            payload["throttle_policies"] = self.throttle_policies.to_dict()

        response = await self.request(
            method="POST",
            uri="/pushes",
            expected_statuses=[200],
            params={"dry_run": int(dry_run)},
            headers={
                "Content-Type": "application/json;charset=UTF-8",
                "Authorization": f"OAuth {self.auth_token}",
            },
            json=payload,
            metric_name="/pushes"
        )

        stack_trace = {"strace": response.get("strace")} if dry_run else {}

        return {
            "id": response.get("id"),
            "receiver": response.get("receiver"),
            **stack_trace,
        }
