"""SMTP client wrapper — handles sending, replying, forwarding, draft saving.

Signature handling:
- Plain text: appended after body with a "--" separator
- HTML: multipart/related wrapping multipart/alternative, logo embedded as CID

MIME structure when signature logo is present:
  multipart/related
    ├── multipart/alternative
    │     ├── text/plain  (body + text signature)
    │     └── text/html   (body + HTML signature with <img src="cid:sig_logo">)
    └── image/png         (logo, Content-ID: sig_logo)

Signature parameter convention (send/reply/forward/build_draft_bytes):
  "default"   → account's configured signature (logo + text) — factory default
  ""          → no signature at all
  "any text"  → custom plain-text signature, appended as "--\\n<text>" (no logo)
"""

from __future__ import annotations

import mimetypes
import smtplib
import ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formataddr, formatdate, make_msgid
from pathlib import Path

from mail_mcp.config import AccountConfig, SignatureConfig
from mail_mcp.core.models import Message

_PKG_DIR = Path(__file__).parent.parent   # src/mail_mcp/
_SIG_CID = "sig_logo_polytechnique"


# ---------------------------------------------------------------------------
# Signature helpers
# ---------------------------------------------------------------------------


def _sig_text(sig: SignatureConfig) -> str:
    """Plain-text signature block."""
    if not sig.before_logo and not sig.after_logo:
        return ""
    parts = []
    if sig.before_logo:
        parts.append(sig.before_logo.strip())
    if sig.after_logo:
        parts.append(sig.after_logo.strip())
    return "\n\n--\n" + "\n".join(parts)


def _sig_html(sig: SignatureConfig) -> str:
    """HTML signature block (uses CID reference for logo)."""
    before = sig.before_logo.strip().replace("\n", "<br>") if sig.before_logo else ""
    after = sig.after_logo.strip().replace("\n", "<br>") if sig.after_logo else ""

    logo_html = ""
    if sig.logo_path:
        logo_html = (
            f'<img src="cid:{_SIG_CID}" alt="Ecole Polytechnique / Institut Polytechnique de Paris"'
            f' style="max-width:280px; display:block; margin:6px 0;">'
        )

    return (
        '<div style="font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#1a1a2e;line-height:1.5;">'
        f"<p style='margin:0 0 4px 0;'><strong>{before}</strong></p>"
        f"{logo_html}"
        f"<p style='margin:4px 0 0 0;'>{after}</p>"
        "</div>"
    )


def _load_logo(sig: SignatureConfig) -> bytes | None:
    """Load the logo image bytes from the configured path (relative to package dir)."""
    if not sig.logo_path:
        return None
    logo_path = _PKG_DIR / sig.logo_path
    if not logo_path.exists():
        return None
    return logo_path.read_bytes()


# ---------------------------------------------------------------------------
# SMTP client
# ---------------------------------------------------------------------------


class SMTPClient:
    """Stateless SMTP sender — connects, sends, disconnects per call."""

    def __init__(self, account: AccountConfig) -> None:
        self.account = account

    # ------------------------------------------------------------------
    # Connection
    # ------------------------------------------------------------------

    def _connect(self) -> smtplib.SMTP:
        cfg = self.account.smtp
        if cfg.starttls:
            server = smtplib.SMTP(cfg.host, cfg.port, timeout=30)
            server.ehlo()
            server.starttls(context=ssl.create_default_context())
            server.ehlo()
        else:
            server = smtplib.SMTP_SSL(cfg.host, cfg.port, timeout=30)
        server.login(self.account.username, self.account.password)
        return server

    # ------------------------------------------------------------------
    # Message builder
    # ------------------------------------------------------------------

    def _resolve_signature(self, signature: str) -> tuple[str, str, bytes | None]:
        """Return (plain_sig_block, html_sig_block, logo_bytes) for the given signature param.

        "default"  → account's configured SignatureConfig (logo + text)
        ""         → no signature (all empty)
        "any text" → custom plain-text only, no logo
        """
        if signature == "":
            return "", "", None

        if signature == "default":
            sig = self.account.signature
            return _sig_text(sig), _sig_html(sig), _load_logo(sig)

        # Custom string: plain text only, no logo
        plain = f"\n\n--\n{signature.strip()}"
        html = (
            '<hr style="border:none;border-top:1px solid #ccc;margin:16px 0;">'
            f'<div style="font-family:Arial,sans-serif;font-size:12px;color:#333;">'
            f'{signature.strip().replace(chr(10), "<br>")}'
            f"</div>"
        )
        return plain, html, None

    def _build_message(
        self,
        to: list[str],
        subject: str,
        body_text: str,
        body_html: str = "",
        cc: list[str] | None = None,
        bcc: list[str] | None = None,
        in_reply_to: str = "",
        references: list[str] | None = None,
        message_id: str = "",
        signature: str = "default",
        attachments: list[str] | None = None,
    ) -> MIMEMultipart:
        plain_sig, html_sig, logo_bytes = self._resolve_signature(signature)

        # Build text part (body + plain sig)
        full_text = body_text + plain_sig

        # Build HTML part (body + html sig)
        if body_html or html_sig:
            body_html_content = body_html or f"<p>{body_text.replace(chr(10), '<br>')}</p>"
            full_html = body_html_content + html_sig
        else:
            full_html = ""

        # --- Build the content part (text/html/logo structure) ---
        # With logo: multipart/related [multipart/alternative + inline PNG]
        # Without logo: multipart/alternative [plain + html] or just plain
        if logo_bytes:
            alt = MIMEMultipart("alternative")
            alt.attach(MIMEText(full_text, "plain", "utf-8"))
            alt.attach(MIMEText(full_html, "html", "utf-8"))
            content_part: MIMEMultipart = MIMEMultipart("related")
            content_part.attach(alt)
            img = MIMEImage(logo_bytes)
            img.add_header("Content-ID", f"<{_SIG_CID}>")
            img.add_header("Content-Disposition", "inline", filename="logo.png")
            content_part.attach(img)
        elif full_html:
            content_part = MIMEMultipart("alternative")
            content_part.attach(MIMEText(full_text, "plain", "utf-8"))
            content_part.attach(MIMEText(full_html, "html", "utf-8"))
        else:
            content_part = MIMEMultipart("alternative")
            content_part.attach(MIMEText(full_text, "plain", "utf-8"))

        # --- Wrap in mixed if logo or file attachments require a container ---
        if attachments or logo_bytes:
            root = MIMEMultipart("mixed")
            root.attach(content_part)
        else:
            root = content_part  # already a MIMEMultipart

        # --- Attach files ---
        if attachments:
            for filepath in attachments:
                p = Path(filepath)
                mime_type, _ = mimetypes.guess_type(str(p))
                maintype, subtype = (mime_type or "application/octet-stream").split("/", 1)
                part = MIMEBase(maintype, subtype)
                part.set_payload(p.read_bytes())
                encoders.encode_base64(part)
                part.add_header("Content-Disposition", "attachment", filename=p.name)
                root.attach(part)

        # Headers on root (BCC is intentionally NOT added to headers — SMTP envelope only)
        root["From"] = formataddr((self.account.display_name, self.account.from_address))
        root["To"] = ", ".join(to)
        if cc:
            root["Cc"] = ", ".join(cc)
        root["Subject"] = subject
        root["Date"] = formatdate(localtime=True)
        root["Message-ID"] = message_id or make_msgid()
        if in_reply_to:
            root["In-Reply-To"] = in_reply_to
        if references:
            root["References"] = " ".join(references)

        return root

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def send(
        self,
        to: list[str],
        subject: str,
        body_text: str,
        body_html: str = "",
        cc: list[str] | None = None,
        bcc: list[str] | None = None,
        signature: str = "default",
        attachments: list[str] | None = None,
    ) -> str:
        """Send a new message. Returns Message-ID.

        signature: "default" → configured sig with logo | "" → none | "text" → custom plain text
        attachments: list of absolute file paths to attach
        bcc: blind carbon copy — added to SMTP envelope only, never in headers
        """
        msg = self._build_message(
            to, subject, body_text, body_html, cc, bcc,
            signature=signature, attachments=attachments,
        )
        mid = msg["Message-ID"]
        all_rcpt = to + (cc or []) + (bcc or [])
        with self._connect() as server:
            server.sendmail(self.account.from_address, all_rcpt, msg.as_bytes())
        return mid

    def reply(
        self,
        original: Message,
        body_text: str,
        body_html: str = "",
        reply_all: bool = False,
        bcc: list[str] | None = None,
        signature: str = "default",
    ) -> str:
        """Reply to an existing message.

        signature: "default" → configured sig | "" → none | "text" → custom plain text.
        Note: replies include the full signature by default (same as a new message).
        Pass "" to omit it, or a short custom string for a lighter reply signature.
        """
        subject = original.subject
        if not subject.lower().startswith("re:"):
            subject = f"Re: {subject}"

        to_list = [original.sender.email] if original.sender else []
        cc_list: list[str] = []
        if reply_all:
            me = self.account.from_address
            to_list = [e for e in {a.email for a in original.recipients} if e != me] or to_list
            cc_list = [e for e in {a.email for a in original.cc} if e != me]

        refs = original.references + ([original.message_id] if original.message_id else [])

        msg = self._build_message(
            to=to_list,
            subject=subject,
            body_text=body_text,
            body_html=body_html,
            cc=cc_list or None,
            bcc=bcc,
            in_reply_to=original.message_id,
            references=refs,
            signature=signature,
        )
        mid = msg["Message-ID"]
        all_rcpt = to_list + cc_list + (bcc or [])
        with self._connect() as server:
            server.sendmail(self.account.from_address, all_rcpt, msg.as_bytes())
        return mid

    def forward(
        self,
        original: Message,
        to: list[str],
        body_text: str = "",
        cc: list[str] | None = None,
        bcc: list[str] | None = None,
        signature: str = "default",
    ) -> str:
        """Forward a message. signature: "default" | "" | "custom text"."""
        subject = original.subject
        if not subject.lower().startswith("fwd:") and not subject.lower().startswith("fw:"):
            subject = f"Fwd: {subject}"

        sender_str = original.sender.email if original.sender else "unknown"
        date_str = original.date.strftime("%Y-%m-%d %H:%M") if original.date else ""
        prefix = (
            f"\n\n---------- Forwarded message ----------\n"
            f"From: {sender_str}\n"
            f"Date: {date_str}\n"
            f"Subject: {original.subject}\n\n"
        )
        full_body = (body_text + prefix + original.body_text).strip()

        msg = self._build_message(to=to, subject=subject, body_text=full_body, cc=cc, bcc=bcc, signature=signature)
        mid = msg["Message-ID"]
        all_rcpt = to + (cc or []) + (bcc or [])
        with self._connect() as server:
            server.sendmail(self.account.from_address, all_rcpt, msg.as_bytes())
        return mid

    def build_draft_bytes(
        self,
        to: list[str],
        subject: str,
        body_text: str,
        body_html: str = "",
        cc: list[str] | None = None,
        bcc: list[str] | None = None,
        signature: str = "default",
        attachments: list[str] | None = None,
    ) -> tuple[bytes, str]:
        """Build a draft as raw bytes for IMAP APPEND. Returns (bytes, message_id)."""
        msg = self._build_message(
            to, subject, body_text, body_html, cc, bcc,
            signature=signature, attachments=attachments,
        )
        return msg.as_bytes(), msg["Message-ID"]
