import logging
from typing import List, Optional, Union, Tuple

from django.conf import settings
from django.core.cache import cache
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from smtplib import SMTPException

from utilities.models import GlobalSettings

logger = logging.getLogger(__name__)


class EmailWrapper:
    """
    Wrapper for sending HTML or templated emails using Django's email backend.

    Example:
        email = EmailWrapper(subject="Welcome!", to=["user@example.com"])
        context = {"name": "John"}
        success = email.send_with_template("emails/welcome.html", context)
        # Returns True or False
    """

    def __init__(
        self,
        subject: str,
        to: Union[str, List[str]],
        body: Optional[str] = "",
        cc: Optional[List[str]] = None,
        bcc: Optional[List[str]] = None,
        attachments: Optional[List[Tuple[str, bytes, str]]] = None,
        reply_to: Optional[List[str]] = None,
    ):
        self.subject = subject
        self.to = [to] if isinstance(to, str) else to
        self.body = body or "Please view this email in HTML format."
        self.cc = cc or []
        self.bcc = bcc or []
        self.attachments = attachments or []
        self.reply_to = reply_to or []

        self._load_email_config()

    # -------------------------------------------------------------------------
    # CONFIG
    # -------------------------------------------------------------------------
    def _load_email_config(self):
        """Load SMTP configuration from cache, DB, or Django settings."""
        gs = cache.get("global_email_settings")
        if not gs:
            gs = GlobalSettings.objects.first()
            cache.set("global_email_settings", gs, 300)  # cache 5 min

        self.from_email = getattr(gs, "smtp_email", None) or settings.DEFAULT_FROM_EMAIL
        self.password = getattr(gs, "smtp_password", None) or getattr(settings, "EMAIL_HOST_PASSWORD", None)

        # Flexible host/port config
        self.smtp_host = getattr(gs, "smtp_host", None) or getattr(settings, "EMAIL_HOST", "smtp.gmail.com")
        self.smtp_port = getattr(gs, "smtp_port", None) or getattr(settings, "EMAIL_PORT", 587)
        self.use_tls = getattr(gs, "smtp_use_tls", None) if gs else getattr(settings, "EMAIL_USE_TLS", True)
        self.use_ssl = getattr(gs, "smtp_use_ssl", None) if gs else getattr(settings, "EMAIL_USE_SSL", False)

    # -------------------------------------------------------------------------
    # CONNECTION
    # -------------------------------------------------------------------------
    def get_connection(self):
        """Return a live email connection using current config."""
        try:
            return get_connection(
                host=self.smtp_host,
                port=self.smtp_port,
                username=self.from_email,
                password=self.password,
                use_tls=self.use_tls,
                use_ssl=self.use_ssl,
                fail_silently=False,
            )
        except Exception as e:
            logger.exception(f"Failed to establish SMTP connection: {e}")
            raise

    # -------------------------------------------------------------------------
    # SEND
    # -------------------------------------------------------------------------
    def send(self, html_message: Optional[str] = None) -> bool:
        """
        Send a plain + HTML multipart email.
        """
        try:
            connection = self.get_connection()
            email = EmailMultiAlternatives(
                subject=self.subject,
                body=self.body,
                from_email=self.from_email,
                to=self.to,
                cc=self.cc,
                bcc=self.bcc,
                reply_to=self.reply_to,
                connection=connection,
            )

            # Attach HTML version if provided
            if html_message:
                email.attach_alternative(html_message, "text/html")

            # Add attachments
            for attachment in self.attachments:
                email.attach(*attachment)

            sent_count = email.send()
            if sent_count > 0:
                logger.info(f"Email sent successfully to {self.to}")
                return True

            logger.warning(f"Email send() returned 0 for recipients {self.to}")
            return False

        except SMTPException as e:
            logger.error(f"SMTP error while sending email to {self.to}: {e}")
            return False
        except Exception as e:
            logger.exception(f"Unexpected error while sending email to {self.to}: {e}")
            return False

    # -------------------------------------------------------------------------
    # SEND WITH TEMPLATE
    # -------------------------------------------------------------------------
    def send_with_template(self, template_path: str, context: dict) -> bool:
        """
        Render an HTML template and send the email.

        :param template_path: e.g. 'emails/welcome.html'
        :param context: Template context
        """
        try:
            html_message = render_to_string(template_path, context)
            return self.send(html_message=html_message)
        except Exception as e:
            logger.exception(f"Failed to render or send template '{template_path}': {e}")
            return False
