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

from azure.communication.email import EmailClient

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_better_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,
        fail_silently=False,
        **kwargs,
    ) -> None:
        super().__init__(fail_silently=fail_silently, **kwargs)

        self._connection_string = (
            connection_string or settings.CONNECTION_STRING
        )  # noqa: E501
        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._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)
            elif self._tenant_id and self._endpoint:
                from azure.identity import DefaultAzureCredential

                self._client = EmailClient(
                    self._endpoint,
                    DefaultAzureCredential(),  # type: ignore
                )
            elif self._key_credential and self._endpoint:
                from azure.core.credentials import AzureKeyCredential

                self._client = EmailClient(
                    self._endpoint,
                    AzureKeyCredential(self._key_credential),
                )
            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)
        payload = {
            "name": converter.get_filename(),
            "contentType": converter.get_content_type(),
            "contentInBase64": converter.get_content(),
        }

        content_id = converter.get_content_id()
        if content_id:
            payload["contentId"] = content_id
        return payload
