"""Email sender module.

Module for sending email messages via SMTP.

Example usage:
    from mail_sender import EmailSender
    mail_sender_inst = EmailSender()
    mail_sender_inst.send_mail(
        subject="My subject",
        mail_body="My body",
        addr_to="who_get_mail@mail.com",
        addr_from="no_reply@blackant.app",
        addr_cc="get_copy_mail@mail.com",
        attachments=["file1.txt", "file2.json"],
        reply_to="personal_mail@mail.com"
    )
"""

import os
import re
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from logging import Logger
from typing import List, Optional, Union

from blackant.utils.logging import get_logger


class EmailSender:  # pylint: disable=too-few-public-methods
    """Email sender class for SMTP operations.

    This class handles email sending operations with support for
    attachments, CC recipients, and reply-to addresses.

    The init method creates an SMTP client and handles authentication.
    Emails are constructed using the email.mime module and sent to recipients.
    """

    def __init__(self, logger: Optional[Logger] = None) -> None:
        """Initialize the logger object and SMTP client.

        Args:
            logger: External logger to use. If None, creates default logger.

        Environment variables:
            SMTP_HOST: SMTP server hostname (default: mail.blackant.app)
            SMTP_PORT: SMTP server port (default: 26)
            SMTP_USER: SMTP username (required)
            SMTP_PASSWORD: SMTP password (required)
        """
        self.__logger = logger or get_logger("email_sender")

        # Get SMTP configuration from environment
        smtp_host = os.getenv("SMTP_HOST", "mail.blackant.app")
        smtp_port = int(os.getenv("SMTP_PORT", "26"))
        smtp_user = os.getenv("SMTP_USER")
        smtp_password = os.getenv("SMTP_PASSWORD")

        if not smtp_user or not smtp_password:
            raise ValueError(
                "SMTP_USER and SMTP_PASSWORD environment variables are required. "
                "Please set them before using EmailSender."
            )

        self.__logger.info("Initialize the SMTP client")
        try:
            self.__smtp_client = smtplib.SMTP(host=smtp_host, port=smtp_port)
        except Exception:
            self.__logger.exception("Cannot create the SMTP client")
            raise
        self.__logger.info("The SMTP client has been initialized")
        try:
            self.__smtp_client.login(user=smtp_user, password=smtp_password)
        except Exception:
            self.__logger.exception("Cannot login to SMTP server")
            raise
        self.__logger.info("Login to SMTP server has been successful")

    def __string_to_list(self, string_param: Union[str, List, tuple, None]) -> List[str]:
        """Convert input to list type.

        Attempts to convert the input argument to list type.
        Handles most built-in types that can be cast to list.

        Args:
            string_param: The object to convert, typically a string.
                Can also handle other types (e.g., Tuple).

        Returns:
            list: The converted list.
        """

        list_to_return: list = []
        if not string_param:
            return list_to_return
        if isinstance(string_param, str):
            list_to_return.extend(re.split(r"[,;]", string_param))
            return list_to_return
        try:
            return list(string_param)
        except TypeError as type_error:
            self.__logger.exception("Cannot convert the string to list.")
            raise type_error

    def send_mail(  # pylint: disable=too-many-arguments
        self,
        subject: str,
        mail_body: str,
        addr_to: Union[str, List[str]],
        addr_from: str = "no-reply@blackant.app",
        addr_cc: Optional[Union[str, List[str]]] = None,
        attachments: Optional[List[str]] = None,
        reply_to: Optional[str] = None,
    ) -> None:
        """Send an email message.

        Constructs and sends an email based on the provided arguments.

        Args:
            subject: Email subject line.
            mail_body: Email content (Text/HTML).
            addr_to: Recipient email address.
            addr_from: Sender email address. Defaults to no-reply@blackant.app.
            addr_cc: Carbon copy recipient email address.
            attachments: List of file paths to attach.
            reply_to: Email address to use for replies.
        """

        addr_to = self.__string_to_list(addr_to)
        addr_cc = self.__string_to_list(addr_cc)

        if attachments:
            if not isinstance(attachments, list):
                raise TypeError("Attachments has to be list type.")
            if all(single_attachment is None for single_attachment in attachments):
                self.__logger.warning(
                    "All elements of attachment list are None. Nothing will be attached to mail."
                )
                attachments = []
        try:
            self.__logger.info("E-mail will be sent to {}".format(addr_to))
            if addr_cc:
                self.__logger.info("Copy will be sent to {}".format(addr_cc))
            message = self.__get_message_body(
                subject=subject,
                mail_body=mail_body,
                addr_to=addr_to,
                addr_from=addr_from,
                addr_cc=addr_cc,
                attachments=attachments,
                reply_to=reply_to,
            )

            self.__logger.debug("The created message: {}".format(message.as_string()))

            addr_to.extend(addr_cc)
            self.__logger.info("Starting to send out the E-mail.")
            self.__smtp_client.sendmail(addr_from, addr_to, message.as_string())
            self.__logger.info("The E-mail has been sent out successfully.")
        except smtplib.SMTPConnectError:
            self.__logger.exception("Cannot connect to SMTP server.")
        except smtplib.SMTPDataError:
            self.__logger.exception("Data error in mail sending.")
        finally:
            self.__logger.info("Closing the SMTP connection.")
            self.__smtp_client.quit()

    @staticmethod
    def __get_message_body(  # pylint: disable=too-many-arguments
        subject: str,
        mail_body: str,
        addr_to: List[str],
        addr_from: str,
        addr_cc: List[str],
        attachments: Optional[List[str]] = None,
        reply_to: Optional[str] = None
    ) -> MIMEMultipart:
        """Construct the email message body.

        Creates the email content based on the provided arguments.

        Args:
            subject: Email subject line.
            mail_body: Email content (Text/HTML).
            addr_to: Recipient email address.
            addr_from: Sender email address. Defaults to no-reply@blackant.app.
            addr_cc: Carbon copy recipient email address.
            attachments: List of file paths to attach.
            reply_to: Email address to use for replies.

        Returns:
            MIMEMultipart: The constructed email message.
        """

        msg = MIMEMultipart("alternative")
        msg["To"] = ", ".join(addr_to) if addr_to else ""
        msg["CC"] = ", ".join(addr_cc) if addr_cc else ""
        msg["From"] = addr_from
        msg["Subject"] = subject
        msg.attach(MIMEText(mail_body, "html"))
        if reply_to:
            msg.add_header("reply-to", reply_to)
        for single_file in attachments or []:
            with open(single_file, "rb") as opened_file:
                part = MIMEApplication(opened_file.read(), Name=os.path.basename(single_file))
                part["Content-Disposition"] = 'attachment; filename="{}"'.format(
                    os.path.basename(single_file)
                )
                msg.attach(part)
        return msg
