#  Copyright (C) 2022
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Ilya Leontyev <i.leontyev@abm-jsc.ru>
#
import logging
from dataclasses import dataclass
from enum import IntEnum
from io import BytesIO

from aiohttp import FormData
from http_tools import AbmServiceConnector
from http_tools.mime_types import ContentType
from init_helpers import custom_dumps
from init_helpers.dict_to_dataclass import dict_to_dataclass, FieldErrors


logger = logging.getLogger(__name__)


class SendEmailError(Exception):
    pass


class GetEmailError(Exception):
    pass


class SendPriority(IntEnum):
    high = 1
    medium = 2


@dataclass
class EmailAttachedFile:
    id: int
    file_id: str
    email_id: int


@dataclass
class Email:
    id: int
    sender_id: int
    status_id: int
    priority_id: int
    subject: str
    body: str
    to_recipients: str
    cc_recipients: str | None
    bcc_recipients: str | None
    created_at: float
    attached_files: list[EmailAttachedFile]


@dataclass(slots=True)
class SendSettings:
    sender_name: str
    send_priority: SendPriority


@dataclass(slots=True)
class AttachedFile:
    content: bytes
    filename: str


class EmailServiceV2Connector:
    @dataclass(kw_only=True)
    class Config(AbmServiceConnector.Config):
        sender_name: str
        send_priority: SendPriority = SendPriority.medium

    @dataclass
    class Context(AbmServiceConnector.Context):
        project_name: str

    def __init__(self, config: Config, context: Context) -> None:
        self._config = config
        self._context = context
        self._connector = AbmServiceConnector(config, context)

    async def get_email(self, email_id: int) -> Email:
        answer = await self._connector.get(
            path="/v2/email/get", url_query={"id": email_id}, headers=self._construct_server_name_header(),
        )
        try:
            return dict_to_dataclass(answer, Email)
        except FieldErrors as e:
            error_message = f"Failed to parse answer from EmailServer. Error: {e}"
            logger.error(error_message)
            raise GetEmailError(error_message) from e

    async def send_email(
            self,
            subject: str,
            body: str,
            to_recipients: list[str] | str,
            cc_recipients: list[str] | str | None = None,
            bcc_recipients: list[str] | str | None = None,
            send_priority: SendPriority | None = None,
            attachment_files: list[AttachedFile] | None = None,
            reply_on_email_id: int | None = None,
    ) -> int:
        if not to_recipients:
            raise ValueError("No recipients")

        form_data = FormData()

        email = {"subject": subject, "body": body, "to_recipients": to_recipients}
        if cc_recipients:
            email["cc_recipients"] = cc_recipients
        if bcc_recipients:
            email["bcc_recipients"] = bcc_recipients
        if reply_on_email_id:
            email["reply_on_email_id"] = reply_on_email_id

        values = {
            "email": email,
            "send_settings": SendSettings(
                self._config.sender_name, send_priority if send_priority else self._config.send_priority,
            ),
        }
        form_data.add_field("values", custom_dumps(values), content_type=ContentType.Json.value)

        for number, file in enumerate(attachment_files or [], 1):
            form_data.add_field(f'file_{number}', BytesIO(file.content), filename=file.filename)

        logger.debug(f"Sending email: {form_data}")
        answer = await self._connector.post(
            path="/v2/email/send", payload=form_data, headers=self._construct_server_name_header(),
        )

        try:
            email_id = int(answer)
        except TypeError as e:
            error_message = f"Failed to parse answer from EmailServer. Error: {e}"
            logger.error(error_message)
            raise SendEmailError(error_message) from e

        return email_id

    def _construct_server_name_header(self) -> dict[str, str]:
        return {"server_name": self._context.project_name}
