"""SMTP email backend."""

import os
import smtplib
import ssl
from typing import Any

try:
    import aiosmtplib
except ImportError:
    aiosmtplib = None  # type: ignore[assignment]

from amsdal_mail.backends.base import BaseEmailBackend
from amsdal_mail.exceptions import EmailConnectionError
from amsdal_mail.exceptions import SendError
from amsdal_mail.message import EmailMessage
from amsdal_mail.status import RecipientStatus
from amsdal_mail.status import SendStatus

_SMTP_PERMANENT_ERROR_CODE = 500


class SMTPBackend(BaseEmailBackend):
    """
    Email backend that sends messages via SMTP.

    Supports TLS/SSL encryption and authentication.
    """

    def __init__(
        self,
        *,
        host: str | None = None,
        port: int | None = None,
        username: str | None = None,
        password: str | None = None,
        use_tls: bool | None = None,
        use_ssl: bool | None = None,
        timeout: int | None = None,
        **kwargs: Any,
    ) -> None:
        """
        Initialize the SMTP backend.

        Args:
            host: SMTP server hostname (env: AMSDAL_EMAIL_HOST)
            port: SMTP server port (env: AMSDAL_EMAIL_PORT)
            username: Authentication username (env: AMSDAL_EMAIL_USER)
            password: Authentication password (env: AMSDAL_EMAIL_PASSWORD)
            use_tls: Use STARTTLS (env: AMSDAL_EMAIL_USE_TLS)
            use_ssl: Use SSL/TLS from start (env: AMSDAL_EMAIL_USE_SSL)
            timeout: Connection timeout in seconds (env: AMSDAL_EMAIL_TIMEOUT)
            **kwargs: Additional backend options
        """
        super().__init__(**kwargs)

        # Load configuration from environment if not provided
        self.host = host or os.environ.get('AMSDAL_EMAIL_HOST', 'localhost')
        self.port = port or int(os.environ.get('AMSDAL_EMAIL_PORT', '25'))
        self.username = username or os.environ.get('AMSDAL_EMAIL_USER')
        self.password = password or os.environ.get('AMSDAL_EMAIL_PASSWORD')
        self.use_tls = use_tls if use_tls is not None else os.environ.get('AMSDAL_EMAIL_USE_TLS', '').lower() == 'true'
        self.use_ssl = use_ssl if use_ssl is not None else os.environ.get('AMSDAL_EMAIL_USE_SSL', '').lower() == 'true'
        self.timeout = timeout or int(os.environ.get('AMSDAL_EMAIL_TIMEOUT', '30'))

        self.connection: smtplib.SMTP | smtplib.SMTP_SSL | None = None

    def open(self) -> None:
        """
        Open a connection to the SMTP server.

        Raises:
            EmailConnectionError: If connection fails and fail_silently is False
        """
        if self.connection:
            return

        try:
            if self.use_ssl:
                # Create SSL context
                context = ssl.create_default_context()
                self.connection = smtplib.SMTP_SSL(
                    self.host,
                    self.port,
                    timeout=self.timeout,
                    context=context,
                )
            else:
                self.connection = smtplib.SMTP(
                    self.host,
                    self.port,
                    timeout=self.timeout,
                )

                if self.use_tls:
                    # Upgrade connection to TLS
                    context = ssl.create_default_context()
                    self.connection.starttls(context=context)

            # Authenticate if credentials provided
            if self.username and self.password:
                self.connection.login(self.username, self.password)

        except (smtplib.SMTPException, OSError, TimeoutError) as e:
            if not self.fail_silently:
                msg = f'Failed to connect to SMTP server: {e}'
                raise EmailConnectionError(msg) from e

    def close(self) -> None:
        """Close the connection to the SMTP server."""
        if self.connection:
            try:
                self.connection.quit()
            except (smtplib.SMTPException, OSError):
                # If quit() fails, try close() instead
                try:
                    self.connection.close()
                except (smtplib.SMTPException, OSError):
                    pass
            finally:
                self.connection = None

    def send_messages(self, email_messages: list[EmailMessage]) -> SendStatus:
        """
        Send one or more email messages via SMTP.

        Args:
            email_messages: List of EmailMessage instances to send

        Returns:
            SendStatus with per-recipient information

        Raises:
            SendError: If sending fails and fail_silently is False
        """
        status = SendStatus()

        if not email_messages:
            return status

        # Open connection
        self.open()

        if not self.connection:
            return status

        new_connection = True
        recipients = {}

        try:
            for message in email_messages:
                try:
                    # Convert to MIME format
                    mime_message = message.as_mime()

                    # Send message - returns dict of refused recipients
                    refused = self.connection.sendmail(
                        str(message.from_email),
                        message.recipients(),
                        mime_message.as_string(),
                    )

                    # Process results for each recipient
                    for recipient in message.recipients():
                        if recipient in refused:
                            # Recipient was refused
                            smtp_code, _smtp_msg = refused[recipient]
                            recipients[recipient] = RecipientStatus(
                                message_id=None,
                                status='rejected' if smtp_code >= _SMTP_PERMANENT_ERROR_CODE else 'failed',
                            )
                        else:
                            # Recipient was accepted
                            recipients[recipient] = RecipientStatus(
                                message_id=None,  # SMTP doesn't provide message IDs
                                status='sent',
                            )

                except (smtplib.SMTPException, OSError) as e:
                    # All recipients failed for this message
                    for recipient in message.recipients():
                        recipients[recipient] = RecipientStatus(
                            message_id=None,
                            status='failed',
                        )

                    if not self.fail_silently:
                        msg = f'Failed to send email: {e}'
                        raise SendError(msg) from e

        finally:
            # Close connection if we opened it
            if new_connection:
                self.close()

        status.set_recipient_status(recipients)
        return status

    async def asend_messages(self, email_messages: list[EmailMessage]) -> SendStatus:
        """
        Send one or more email messages via SMTP asynchronously.

        Args:
            email_messages: List of EmailMessage instances to send

        Returns:
            SendStatus with per-recipient information

        Raises:
            SendError: If sending fails and fail_silently is False
            ImportError: If aiosmtplib is not installed
        """
        if aiosmtplib is None:
            msg = 'aiosmtplib is required for async SMTP. Install with: pip install amsdal-mail[smtp]'
            raise ImportError(msg)

        status = SendStatus()

        if not email_messages:
            return status

        recipients = {}

        try:
            # Create async SMTP client
            if self.use_ssl:
                smtp = aiosmtplib.SMTP(
                    hostname=self.host,
                    port=self.port,
                    timeout=self.timeout,
                    use_tls=True,
                )
            else:
                smtp = aiosmtplib.SMTP(
                    hostname=self.host,
                    port=self.port,
                    timeout=self.timeout,
                    use_tls=False,
                    start_tls=self.use_tls,
                )

            # Connect and authenticate
            await smtp.connect()

            if self.use_tls and not self.use_ssl:
                await smtp.starttls(validate_certs=True)

            if self.username and self.password:
                await smtp.login(self.username, self.password)

            # Send messages
            for message in email_messages:
                try:
                    # Convert to MIME format
                    mime_message = message.as_mime()

                    # Send message - aiosmtplib returns (response, message) tuple or errors dict
                    result = await smtp.sendmail(
                        str(message.from_email),
                        message.recipients(),
                        mime_message.as_string(),
                    )

                    # aiosmtplib sendmail returns errors dict (like smtplib)
                    # Empty dict means all recipients accepted
                    refused: dict[str, Any] = result if isinstance(result, dict) else {}

                    # Process results for each recipient
                    for recipient in message.recipients():
                        if recipient in refused:
                            # Recipient was refused
                            smtp_code, _smtp_msg = refused[recipient]
                            recipients[recipient] = RecipientStatus(
                                message_id=None,
                                status='rejected' if smtp_code >= _SMTP_PERMANENT_ERROR_CODE else 'failed',
                            )
                        else:
                            # Recipient was accepted
                            recipients[recipient] = RecipientStatus(
                                message_id=None,  # SMTP doesn't provide message IDs
                                status='sent',
                            )

                except (aiosmtplib.SMTPException, OSError) as e:
                    # All recipients failed for this message
                    for recipient in message.recipients():
                        recipients[recipient] = RecipientStatus(
                            message_id=None,
                            status='failed',
                        )

                    if not self.fail_silently:
                        msg = f'Failed to send email: {e}'
                        raise SendError(msg) from e

            # Close connection
            await smtp.quit()

        except (aiosmtplib.SMTPException, OSError, TimeoutError) as e:
            if not self.fail_silently:
                msg = f'Failed to connect to SMTP server: {e}'
                raise EmailConnectionError(msg) from e

        status.set_recipient_status(recipients)
        return status
