import base64
import mimetypes
import os.path
from typing import Iterable, Optional, Union

import requests

from django.utils.functional import SimpleLazyObject
from django.utils.translation import gettext_lazy as _

from lms.utils.requests import requests_retry_session

from .settings import (
    SENDER_ACCOUNT_SLUG, SENDER_AUTHORIZATION_KEY, SENDER_ENABLED, SENDER_HOST, SENDER_ROBOT_EMAIL, VERIFY_SENDER_HOST,
)


class EmailAttachmentException(Exception):
    pass


class EmailAttachment:
    def __init__(self, filename: str, data: bytes, mime_type: Optional[str] = None):
        self.filename = filename
        self.data = data
        self.mime_type = mime_type
        if self.mime_type is None:
            file_extension = os.path.splitext(filename)[1]
            if self.mime_type is None and file_extension == '.csv':
                self.mime_type = 'text/csv'
            else:
                mimetypes.init()
                if file_extension in mimetypes.types_map:
                    self.mime_type = mimetypes.types_map[file_extension]
                else:
                    raise EmailAttachmentException(_("cannot detect mimetype for file: %s") % filename)

    def to_json(self):
        return {
            'filename': self.filename,
            'mime_type': self.mime_type,
            'data': base64.b64encode(self.data).decode("utf-8"),
        }


class SenderClient:
    TRANSACTION_URL_TEMPLATE = '/api/0/{account_slug}/transactional/{campaign_slug}/send'
    STATUS_URL_TEMPLATE = '/api/0/{account_slug}/transactional/{campaign_slug}/status?message_id={message_id}'

    def __init__(
        self,
        host: str,
        authorization_key: str,
        account_slug: str,
        verify_host: bool = True,
        timeout: int = 10,
        retries: int = 3,
        session: Optional[requests.Session] = None,
        **kwargs
    ):
        self.host = host
        self.authorization_key = authorization_key
        self.account_slug = account_slug
        self.verify_host = verify_host

        self.timeout = timeout
        self.retries = retries
        self.session = session or requests.Session()

        backoff_factor = kwargs.get('backoff_factor', 0.3)
        status_forcelist = kwargs.get('status_forcelist', (500, 502, 504))

        self.session = requests_retry_session(
            self.retries, backoff_factor, status_forcelist, self.session,
        )

    def send_messages(
        self,
        receivers: Union[list, dict],
        campaign_slug: str,
        template_args: Optional[dict] = None,
        countdown: Optional[int] = None,
        expires: Optional[int] = None,
        ignore_empty_email: bool = True,
        email_headers: Optional[Iterable[str]] = None,
        attachments: Optional[Iterable[EmailAttachment]] = None,
        sync: bool = False,
    ):
        """
        Дергает ручку рассылятора с транзакционными рассылками
        :param receivers: список получателей
        список словарей с ключами email, name (опционально)
        :param sender_template_code: идентификатор шаблона в рассыляторе
        :param sync: True - сообщения рассылаются в порядке общей очереди рассылятора, True - сразу и асинхронно
        :param kwargs: переменные шаблона транзакционного письма, задаются в самом рассыляторе
        :return: сколько сообщений удалось отправить
        """
        if not SENDER_ENABLED:
            return {'result': {'status': 'cancelled', 'message': _("Отправка писем выключена (SENDER_ENABLED=false)")}}

        data = {
            'bcc': receivers,  # всех получателей указываем в скрытых, чтобы каждый получатель не видел остальных
            'to': [{'email': SENDER_ROBOT_EMAIL}],  # для рассылятора нужно указать хотя бы одного основного получателя
            'host': self.host,
            'async': not sync,
            'ignore_empty_email': ignore_empty_email,
        }
        if template_args:
            data['args'] = template_args
        if countdown:
            data['countdown'] = countdown
        if expires:
            data['expires'] = expires
        if attachments:
            data['attachments'] = [attachment.to_json() for attachment in attachments]
        if email_headers:
            data['headers'] = email_headers

        headers = {
            'Content-Type': 'application/json',
        }
        url_kwargs = {
            'account_slug': self.account_slug,
            'campaign_slug': campaign_slug,
        }
        url = f'{self.host}{self.TRANSACTION_URL_TEMPLATE.format(**url_kwargs)}'

        response = self.session.request(
            'post',
            url=url,
            json=data,
            headers=headers,
            auth=(self.authorization_key, ''),
            verify=self.verify_host,
            timeout=self.timeout,
        )
        response.raise_for_status()

        return response.json()

    def status(self, campaign_slug: str, message_id: str):
        url_kwargs = {
            'account_slug': self.account_slug,
            'campaign_slug': campaign_slug,
            'message_id': message_id,
        }

        url = f'{self.host}{self.STATUS_URL_TEMPLATE.format(**url_kwargs)}'
        response = self.session.request(
            'get',
            url=url,
            auth=(self.authorization_key, ''),
            verify=self.verify_host,
            timeout=self.timeout,
        )
        response.raise_for_status()

        return response.json()


def get_client() -> SenderClient:
    return SenderClient(
        host=SENDER_HOST,
        authorization_key=SENDER_AUTHORIZATION_KEY,
        verify_host=VERIFY_SENDER_HOST,
        account_slug=SENDER_ACCOUNT_SLUG,
    )


sender_client = SimpleLazyObject(lambda: get_client())
