"""
SmtpSender – invio PEC via SMTP (SSL/STARTTLS) con aiosmtplib.

Costruisce il messaggio MIME, si connette al server SMTP della casella,
invia e restituisce il Message-ID e i byte raw EML per l'archiviazione.

Porta 465 → SSL diretto (use_tls=True, start_tls=False)
Porta 587 → STARTTLS (use_tls=False, start_tls=True)
Porta 25  → plain (use_tls=False, start_tls=False) – deprecato, non usato
"""

import base64
import io
import logging
import uuid
from email import encoders
from email.headerregistry import Address
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate, make_msgid

import aiosmtplib
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

from app.config import get_settings
from app.models import Mailbox

logger = logging.getLogger(__name__)


# ─── Helpers ──────────────────────────────────────────────────────────────────

def _decrypt(enc_value: str) -> str:
    """
    Decifra un campo credenziale cifrato con AES-256-GCM (ADR-002).

    Usa get_settings() in modo lazy (non module-level) per permettere
    ai test di iniettare la chiave tramite env var o mock.
    """
    raw = base64.b64decode(enc_value)
    nonce, ciphertext_tag = raw[:12], raw[12:]
    aesgcm = AESGCM(get_settings().encryption_key_bytes)
    return aesgcm.decrypt(nonce, ciphertext_tag, None).decode("utf-8")


def decrypt_smtp_credentials(mailbox: Mailbox) -> dict:
    """Restituisce le credenziali SMTP in chiaro della casella."""
    return {
        "host": _decrypt(mailbox.smtp_host_enc),
        "port": int(_decrypt(mailbox.smtp_port_enc)),
        "user": _decrypt(mailbox.smtp_user_enc),
        "password": _decrypt(mailbox.smtp_pass_enc),
        "use_tls": mailbox.smtp_use_tls,
    }


# ─── SmtpSender ───────────────────────────────────────────────────────────────

class SmtpSender:
    """
    Gestisce la connessione SMTP e l'invio di un singolo messaggio PEC.

    Esempio di utilizzo::

        sender = SmtpSender(mailbox)
        msg_id, raw_eml = await sender.send(
            to_addresses=["dest@pec.it"],
            cc_addresses=[],
            subject="Test",
            body_text="Corpo del messaggio",
        )
    """

    def __init__(self, mailbox: Mailbox) -> None:
        self.mailbox = mailbox
        self._creds = decrypt_smtp_credentials(mailbox)

    # ── Costruzione MIME ──────────────────────────────────────────────────────

    def build_mime_message(
        self,
        to_addresses: list[str],
        cc_addresses: list[str],
        subject: str,
        body_text: str,
        body_html: str | None = None,
        attachments: list[dict] | None = None,
    ) -> tuple[MIMEMultipart, str]:
        """
        Costruisce il messaggio MIME per la PEC.

        Args:
            to_addresses:  destinatari principali
            cc_addresses:  destinatari in copia (può essere vuoto)
            subject:       oggetto del messaggio
            body_text:     corpo in testo semplice
            body_html:     corpo HTML opzionale
            attachments:   lista di dict {filename, content: bytes, content_type}

        Returns:
            (msg MIME, message_id_header)
        """
        attachments = attachments or []

        # Struttura MIME
        if attachments:
            msg = MIMEMultipart("mixed")
            body_container = MIMEMultipart("alternative")
        elif body_html:
            msg = MIMEMultipart("alternative")
            body_container = msg
        else:
            msg = MIMEMultipart("mixed")
            body_container = msg

        # Headers obbligatori
        message_id = make_msgid(domain="pecflow.local")
        msg["From"] = self.mailbox.email_address
        msg["To"] = ", ".join(to_addresses)
        if cc_addresses:
            msg["Cc"] = ", ".join(cc_addresses)
        msg["Subject"] = subject
        msg["Date"] = formatdate(localtime=True)
        msg["Message-ID"] = message_id
        msg["MIME-Version"] = "1.0"

        # Corpo
        if body_text:
            body_container.attach(MIMEText(body_text, "plain", "utf-8"))
        if body_html:
            body_container.attach(MIMEText(body_html, "html", "utf-8"))
        elif not body_text:
            # Almeno un body vuoto per evitare messaggi malformati
            body_container.attach(MIMEText("", "plain", "utf-8"))

        # Se la struttura è mixed, aggiungi il body_container come parte
        if attachments and body_container is not msg:
            msg.attach(body_container)

        # Allegati
        for att in attachments:
            filename: str = att["filename"]
            content: bytes = att["content"]
            content_type: str = att.get("content_type", "application/octet-stream")

            try:
                main_type, sub_type = content_type.split("/", 1)
            except ValueError:
                main_type, sub_type = "application", "octet-stream"

            part = MIMEBase(main_type, sub_type)
            part.set_payload(content)
            encoders.encode_base64(part)
            part.add_header(
                "Content-Disposition",
                "attachment",
                filename=filename,
            )
            msg.attach(part)

        return msg, message_id

    # ── Invio SMTP ────────────────────────────────────────────────────────────

    async def send(
        self,
        to_addresses: list[str],
        cc_addresses: list[str],
        subject: str,
        body_text: str,
        body_html: str | None = None,
        attachments: list[dict] | None = None,
    ) -> tuple[str, bytes]:
        """
        Invia la PEC via SMTP.

        Supporta:
        - Porta 465 con SSL diretto (use_tls=True)
        - Porta 587 con STARTTLS (use_tls=False, porta 587)
        - Porta 25  plain (uso sconsigliato)

        Returns:
            (message_id_header, raw_eml_bytes)

        Raises:
            aiosmtplib.SMTPException: su errore SMTP non recuperabile
            aiosmtplib.SMTPConnectError: su timeout/connessione fallita
        """
        msg, message_id = self.build_mime_message(
            to_addresses=to_addresses,
            cc_addresses=cc_addresses,
            subject=subject,
            body_text=body_text,
            body_html=body_html,
            attachments=attachments,
        )

        raw_eml: bytes = msg.as_bytes()
        creds = self._creds
        all_recipients = list(to_addresses) + list(cc_addresses)

        # Determina la modalità di connessione in base alla porta e al flag
        port: int = creds["port"]
        use_tls: bool = creds["use_tls"]
        start_tls: bool = False

        if port == 587:
            # STARTTLS tipico
            use_tls = False
            start_tls = True
        elif port == 465:
            # SSL diretto
            use_tls = True
            start_tls = False
        # porta 25 → plain (entrambi False)

        logger.debug(
            f"SMTP connect: {creds['host']}:{port} "
            f"(use_tls={use_tls}, start_tls={start_tls})"
        )

        smtp = aiosmtplib.SMTP(
            hostname=creds["host"],
            port=port,
            use_tls=use_tls,
            start_tls=start_tls,
            timeout=30,
        )

        try:
            await smtp.connect()
            await smtp.login(creds["user"], creds["password"])
            errors, response = await smtp.sendmail(
                sender=self.mailbox.email_address,
                recipients=all_recipients,
                message=raw_eml,
            )
            if errors:
                failed = ", ".join(f"{addr}: {err}" for addr, err in errors.items())
                raise aiosmtplib.SMTPRecipientsRefused(
                    recipients={a: (code, msg_b) for a, (code, msg_b) in errors.items()}
                )
            await smtp.quit()
        except Exception:
            try:
                smtp.close()
            except Exception:
                pass
            raise

        logger.info(
            f"PEC inviata: {message_id} da {self.mailbox.email_address} "
            f"→ {all_recipients} ({len(raw_eml)} bytes)"
        )
        return message_id, raw_eml
