from typing import List, Optional, Tuple

from maps_adv.common import email_sender
from maps_adv.common.yasms import YasmsClient
from maps_adv.geosmb.clients.bvm import BvmClient
from maps_adv.geosmb.clients.geosearch import GeoSearchClient
from maps_adv.geosmb.clients.notify_me import NotifyMeClient
from maps_adv.geosmb.doorman.client import DoormanClient
from maps_adv.geosmb.telegraphist.server.lib.enums import NotificationType, Transport
from maps_adv.geosmb.telegraphist.server.lib.exceptions import TransportException
from maps_adv.geosmb.telegraphist.server.lib.notification_router import NotificationRouter
from maps_adv.geosmb.telegraphist.server.lib.notification_router_v3 import NotificationRouterV3

from .exceptions import NoOrginfo, NoOrgsForBizId, RecipientDenied

__all__ = ["Domain", "ru_format_date"]


class Domain:
    __slots__ = (
        "_email_client",
        "_yasms",
        "_notify_me",
        "_bvm_client",
        "_geosearch_client",
        "_nofitication_router",
        "_nofitication_router_v3",
        "_purchased_certificate_template_code",
        "_limit_recipients",
        "_doorman_client",
    )

    _email_client: email_sender.Client
    _yasms: YasmsClient
    _notify_me: NotifyMeClient
    _bvm_client: BvmClient
    _geosearch_client: GeoSearchClient
    _doorman_client: DoormanClient
    _nofitication_router: NotificationRouter
    _nofitication_router_v3: NotificationRouterV3

    _purchased_certificate_template_code: str
    _limit_recipients: bool

    def __init__(
        self,
        *,
        email_client: email_sender.Client,
        yasms: YasmsClient,
        notify_me: NotifyMeClient,
        bvm_client: BvmClient,
        geosearch_client: GeoSearchClient,
        doorman_client: DoormanClient,
        notification_router: NotificationRouter,
        notification_router_v3: NotificationRouterV3,
        purchased_certificate_template_code: str,
        limit_recipients: bool = False,
    ):
        self._email_client = email_client
        self._yasms = yasms
        self._notify_me = notify_me
        self._bvm_client = bvm_client
        self._geosearch_client = geosearch_client
        self._doorman_client = doorman_client

        self._purchased_certificate_template_code = purchased_certificate_template_code
        self._limit_recipients = limit_recipients
        self._nofitication_router = notification_router
        self._nofitication_router_v3 = notification_router_v3

    async def send_notification(
        self, *, recipient: str, purchased_certificate_details: dict
    ):
        if self._limit_recipients and not recipient.endswith("@yandex-team.ru"):
            raise RecipientDenied()

        params = purchased_certificate_details.copy()
        params["active_from"] = ru_format_date(
            purchased_certificate_details["active_from"]
        )
        params["active_to"] = ru_format_date(
            purchased_certificate_details["active_to"], with_year=True
        )

        await self._email_client.send_message(
            args=params,
            asynchronous=True,
            template_code=self._purchased_certificate_template_code,
            to_email=recipient,
        )

    async def send_notification_v2(
        self,
        *,
        recipient: dict,
        transports: List[Transport],
        order_created: Optional[dict] = None,
        order_reminder: Optional[dict] = None,
        order_changed: Optional[dict] = None,
        order_cancelled: Optional[dict] = None,
    ):
        notification_type, notification_details = self._select_notification(
            order_created=order_created,
            order_reminder=order_reminder,
            order_changed=order_changed,
            order_cancelled=order_cancelled,
        )

        permalinks = await self._bvm_client.fetch_permalinks_by_biz_id(
            biz_id=recipient["biz_id"]
        )
        if not permalinks:
            raise NoOrgsForBizId(
                f"No permalinks for biz_id {recipient['biz_id']} in BVM."
            )

        orginfo = await self._geosearch_client.resolve_org(permalink=permalinks[0])
        if not orginfo:
            raise NoOrginfo(
                f"Can't resolve org with permalink {permalinks[0]} in Geosearch."
            )

        notification_details["org"] = {
            "permalink": orginfo.permalink,
            "name": orginfo.name,
            "formatted_address": orginfo.formatted_address,
        }
        if orginfo.tz_offset is not None:
            notification_details["org"]["tz_offset"] = orginfo.tz_offset
        if orginfo.formatted_phones:
            notification_details["org"]["phone"] = orginfo.formatted_phones[0]
        if orginfo.own_links:
            notification_details["org"]["url"] = orginfo.own_links[0]
        if orginfo.categories_names:
            notification_details["org"]["categories"] = orginfo.categories_names

        send_result = await self._nofitication_router.send_client_notification(
            recipient=recipient,
            transports=transports,
            notification_type=notification_type,
            notification_details=notification_details,
        )

        return self._compose_send_result(send_result)

    async def send_notification_for_business(
        self,
        *,
        recipient: dict,
        transports: List[Transport],
        order_created: Optional[dict] = None,
        order_changed: Optional[dict] = None,
        order_cancelled: Optional[dict] = None,
        certificate_expiring: Optional[dict] = None,
        certificate_expired: Optional[dict] = None,
        certificate_connect_payment: Optional[dict] = None,
        certificate_rejected: Optional[dict] = None,
        first_certificate_approved: Optional[dict] = None,
        subsequent_certificate_approved: Optional[dict] = None,
        certificate_created: Optional[dict] = None,
        certificate_purchased: Optional[dict] = None,
        request_created: Optional[dict] = None,
    ):
        notification_type, notification_details = self._select_notification(
            order_created_for_business=order_created,
            order_changed_for_business=order_changed,
            order_cancelled_for_business=order_cancelled,
            certificate_expiring=certificate_expiring,
            certificate_expired=certificate_expired,
            certificate_connect_payment=certificate_connect_payment,
            certificate_rejected=certificate_rejected,
            first_certificate_approved=first_certificate_approved,
            subsequent_certificate_approved=subsequent_certificate_approved,
            certificate_created=certificate_created,
            certificate_purchased=certificate_purchased,
            request_created_for_business=request_created,
        )

        notification_details["org"] = {
            "company_link": recipient.pop("company_link"),
            "cabinet_link": recipient.pop("cabinet_link"),
        }
        needs_orginfo = notification_type in (
            NotificationType.ORDER_CREATED_FOR_BUSINESS,
            NotificationType.ORDER_CHANGED_FOR_BUSINESS,
            NotificationType.ORDER_CANCELLED_FOR_BUSINESS,
        )
        if needs_orginfo:
            permalinks = await self._bvm_client.fetch_permalinks_by_biz_id(
                biz_id=recipient["biz_id"]
            )
            if not permalinks:
                raise NoOrgsForBizId(
                    f"No permalinks for biz_id {recipient['biz_id']} in BVM."
                )

            orginfo = await self._geosearch_client.resolve_org(permalink=permalinks[0])
            if not orginfo:
                raise NoOrginfo(
                    f"Can't resolve org with permalink {permalinks[0]} in Geosearch."
                )

            notification_details["org"]["formatted_address"] = orginfo.formatted_address
            if orginfo.tz_offset is not None:
                notification_details["org"]["tz_offset"] = orginfo.tz_offset

        if "client" in notification_details:
            client_info = await self._doorman_client.retrieve_client(
                biz_id=recipient["biz_id"],
                client_id=notification_details["client"]["client_id"],
            )

            notification_details["client"] = {
                "name": " ".join(
                    filter(
                        None,
                        [client_info.get("first_name"), client_info.get("last_name")],
                    )
                ),
                "phone": client_info.get("phone"),
            }

        if "order" in notification_details:
            notification_details["order"]["items"] = sorted(
                notification_details["order"]["items"],
                key=lambda x: x["booking_timestamp"],
            )

        send_result = await self._nofitication_router.send_business_notification(
            recipient=recipient,
            transports=transports,
            notification_type=notification_type,
            notification_details=notification_details,
        )

        return self._compose_send_result(send_result)

    async def send_notification_v3(
        self,
        *,
        transport: Transport,
        request_created: Optional[dict] = None,
        cart_order_created: Optional[dict] = None,

    ):
        notification_type, notification_details = self._select_notification(
            request_created_for_business=request_created,
            cart_order_created=cart_order_created,
        )

        send_result = await self._nofitication_router_v3.send_notification(
            transport=transport,
            notification_type=notification_type,
            notification_details=notification_details,
        )

        return self._compose_send_result(send_result)

    def _compose_send_result(self, send_result: List[dict]) -> List[dict]:
        for result in send_result:
            for transport, transport_result in list(result.items()):
                if isinstance(transport_result, TransportException):
                    result[transport] = {"error": transport_result.text}

        return send_result

    @staticmethod
    def _select_notification(**kwargs: dict) -> Tuple[NotificationType, dict]:
        nonempty_keys = []
        for key, value in kwargs.items():
            if value is not None:
                nonempty_keys.append(key)

        if len(nonempty_keys) != 1:
            raise ValueError(
                f"Exactly one notification expected, {len(nonempty_keys)} provided"
            )

        key = nonempty_keys[0]
        return NotificationType(key), kwargs[key]


_months = (
    "января",
    "февраля",
    "марта",
    "апреля",
    "мая",
    "июня",
    "июля",
    "августа",
    "сентября",
    "октября",
    "ноября",
    "декабря",
)


def ru_format_date(_datetime, with_year=False):
    formatted = f"{_datetime.day} {_months[_datetime.month - 1]}"
    if with_year:
        formatted = f"{formatted} {_datetime.year}"
    return formatted
