from __future__ import annotations

import requests
import base64
from typing import Dict
from email.message import EmailMessage
from email.utils import getaddresses

from send.common.errors import TransportError
from send.common.logging import get_logger

logger = get_logger(__name__)

class MSGraphTransport:
    GRAPH_SENDMAIL_URL = "https://graph.microsoft.com/v1.0/me/sendMail"

    def __init__(self, access_token: str, from_address: str) -> None:
        self._access_token = access_token
        self._from_address = from_address

        self._headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        }

    def send_email(self, msg: EmailMessage) -> None:
        logger.info('Start. Class: MSGraphTransport. Method: send_email.')
        payload = self._emailmessage_to_graph_payload(msg)

        resp = requests.post(
            self.GRAPH_SENDMAIL_URL,
            headers=self._headers,
            json=payload,
            timeout=30,
        )

        if resp.status_code not in (200, 202):
            raise TransportError(
                f"Graph sendMail failed: {resp.status_code} {resp.text}"
            )

    def __enter__(self) -> "MSGraphTransport":
        return self

    def __exit__(self, *_) -> None:
        # nothing to close (HTTP)
        return None

    @classmethod
    def connect_with_oauth(
        cls,
        cfg: Dict,
        *,
        access_token: str | None = None,
    ) -> "MSGraphTransport":
        """
        Create and return an MSGraphTransport using OAuth2.

        - Prepares Graph sendMail client.
        - Raises RuntimeError if token or config is invalid.

        Usage:

            with MSGraphTransport.connect_with_oauth(cfg, access_token=token) as graph:
                graph.send_email(msg)
        """
        if not access_token:
            raise ValueError("MS Graph send requires an access_token.")

        from_address = cfg.get("ms_email_address")

        if not from_address:
            raise ValueError("MS email address is required to send mail via Graph.")

        return cls(
            access_token=access_token,
            from_address=from_address,
        )

    def _emailmessage_to_graph_payload(self, msg: EmailMessage) -> Dict:
        def _body_content(msg: EmailMessage) -> tuple[str, str]:
            """
            Returns (content, graph_content_type) where graph_content_type is
            either "Text" or "HTML".
            """
            # Prefer a plain part, fall back to HTML, then to the raw content.
            preferred_part = msg.get_body(preferencelist=("plain", "html"))
            if preferred_part:
                content_type = preferred_part.get_content_type()
                content = preferred_part.get_content()
            elif not msg.is_multipart():
                content_type = msg.get_content_type()
                content = msg.get_content()
            else:
                # Multipart without a text part; send an empty text body.
                content_type = "text/plain"
                content = ""

            graph_type = "HTML" if content_type == "text/html" else "Text"
            return content or "", graph_type

        def _extract_addrs(header: str) -> list[dict]:
            return [
                {"emailAddress": {"address": addr}}
                for _, addr in getaddresses(msg.get_all(header) or [])
                if addr
            ]

        to_addrs = _extract_addrs("To")
        cc_addrs = _extract_addrs("Cc")
        bcc_addrs = _extract_addrs("Bcc")

        body_text, body_type = _body_content(msg)

        payload = {
            "message": {
                "subject": msg.get("Subject", ""),
                "body": {
                    "contentType": body_type,
                    "content": body_text,
                },
                "from": {
                    "emailAddress": {"address": msg.get("From") or self._from_address}
                },
                "toRecipients": to_addrs,
            }
        }

        if cc_addrs:
            payload["message"]["ccRecipients"] = cc_addrs
        if bcc_addrs:
            payload["message"]["bccRecipients"] = bcc_addrs

        # Attachments (optional, but important for InvoiceMailer)
        attachments = []
        for part in msg.iter_attachments():
            filename = part.get_filename()
            content = part.get_payload(decode=True)
            if filename and content:
                attachments.append({
                    "@odata.type": "#microsoft.graph.fileAttachment",
                    "name": filename,
                    "contentBytes": base64.b64encode(content).decode("utf-8"),
                })

        if attachments:
            payload["message"]["attachments"] = attachments

        return payload
