import logging
from typing import Any, Dict, Iterable, List, Optional

from azure.communication.email import EmailClient
from azure.core.pipeline.policies import RetryPolicy

from django.core.exceptions import ImproperlyConfigured
from django.core.mail import EmailMessage
from django.core.mail.backends.base import BaseEmailBackend

from . import attachment, settings, utils


logger = logging.getLogger('django_azure_communication_email')


class ACEmailBackend(BaseEmailBackend):
    """A Django Email backend that uses Azure Communication Email service."""

    def __init__(
        self,
        *,
        connection_string: Optional[str] = None,
        tenant_id: Optional[str] = None,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
        endpoint: Optional[str] = None,
        key_credential: Optional[str] = None,
        retry_policy: Optional[RetryPolicy] = None,
        fail_silently=False,
        **kwargs,
    ) -> None:
        super().__init__(fail_silently=fail_silently, **kwargs)

        self._connection_string = connection_string \
            or settings.CONNECTION_STRING
        self._tenant_id = tenant_id or settings.TENANT_ID
        self._client_id = client_id or settings.CLIENT_ID
        self._client_secret = client_secret or settings.CLIENT_SECRET
        self._endpoint = endpoint or settings.ENDPOINT
        self._key_credential = key_credential or settings.KEY_CREDENTIAL
        self._retry_policy = retry_policy or settings.RETRY_POLICY

        self._client: EmailClient | None = None

    def open(self) -> None:
        if self._client is not None:
            return

        try:
            if conn_str := self._connection_string:
                self._client = EmailClient.from_connection_string(
                    conn_str,
                    retry_policy=self._retry_policy
                )
            elif self._tenant_id and self._endpoint:
                from azure.identity import DefaultAzureCredential
                self._client = EmailClient(
                    self._endpoint,
                    DefaultAzureCredential(),  # type: ignore
                    retry_policy=self._retry_policy
                )
            elif self._key_credential and self._endpoint:
                from azure.core.credentials import AzureKeyCredential
                self._client = EmailClient(
                    self._endpoint,
                    AzureKeyCredential(self._key_credential),
                    retry_policy=self._retry_policy
                )
            else:
                raise ImproperlyConfigured(
                    'You must specify either a connection string,'
                    ' or tenant ID & client ID & client secret & communication'
                    ' endpoint, or key credential & communication endpoint.'
                )
        except Exception as exc:  # noqa
            if not self.fail_silently:
                raise
            logger.warning(
                'Failed to open connection to Azure Communication Email.',
                exc_info=exc,
            )

    def close(self) -> None:
        self._client = None

    def send_messages(self, email_messages: Iterable[EmailMessage]) -> int:
        """
        It's your responsibility to validate all data before sending an email.
        """
        if not email_messages:
            return 0

        self.open()
        if self._client is None:
            # failed silently
            return 0

        sent = 0
        with self._client:
            for message in email_messages:
                try:
                    self._client.begin_send(self.convert_message(message))
                    sent += 1
                except Exception as exc:  # noqa
                    if not self.fail_silently:
                        raise
                    logger.warning('Failed to send email.', exc_info=exc)

        self.close()
        return sent

    def convert_message(self, message: EmailMessage) -> Dict[str, Any]:
        """Converts the EmailMessage object to dictionary."""
        msg = {
            'senderAddress': utils.get_name_and_email(message.from_email)[1],
            'content': {'subject': message.subject},
            'recipients': {},
        }
        if message.body:
            msg['content']['plainText'] = message.body
        if html_msg := utils.get_html_message(message):
            msg['content']['html'] = html_msg
        if message.to:
            msg['recipients']['to'] = self._build_recipients(message.to)
        if message.cc:
            msg['recipients']['cc'] = self._build_recipients(message.cc)
        if message.bcc:
            msg['recipients']['bcc'] = self._build_recipients(message.bcc)
        if message.attachments:
            msg['attachments'] = [
                self._build_attachment(file)
                for file in message.attachments
            ]
        if message.extra_headers:
            msg['headers'] = message.extra_headers
        if message.reply_to:
            msg['replyTo'] = self._build_recipients(message.reply_to)
        if settings.TRACKING_DISABLED:
            msg['userEngagementTrackingDisabled'] = True

        return msg

    @staticmethod
    def _build_recipients(recipients: Iterable[str]) -> List[Dict[str, str]]:
        return [
            {'displayName': name, 'address': email}
            for name, email in map(utils.get_name_and_email, recipients)
        ]

    @staticmethod
    def _build_attachment(file: attachment.Attachment) -> Dict[str, str]:
        converter = attachment.get_converter(file)
        return {
            'name': converter.get_filename(),
            'contentType': converter.get_filetype(),
            'contentInBase64': converter.get_content(),
        }
