"""Core ZeptoMail client for sending transactional emails."""

from __future__ import annotations

from typing import Any, Sequence

import requests

from zeptomail.exceptions import ZeptoMailAPIError
from zeptomail.mime import MimeType


# ── Internal helpers ────────────────────────────────────────


def _safe_set(key: str, value: Any, target: dict) -> dict:
    """Set *key* in *target* only when *value* is truthy."""
    if value:
        target[key] = value
    return target


# ── Config ──────────────────────────────────────────────────


class Config:
    """Holds API credentials and HTTP headers for ZeptoMail.

    Parameters:
        api_key: Your ZeptoMail API key (Zoho-enczapikey).
        url: Override the default API endpoint.
        timeout: Request timeout in seconds (default 30).
    """

    DEFAULT_URL = "https://api.zeptomail.com/v1.1/email"

    def __init__(
        self,
        api_key: str,
        *,
        url: str | None = None,
        timeout: int = 30,
    ) -> None:
        if not api_key:
            raise ValueError("api_key must not be empty")

        self.api_key = api_key
        self.url = url or self.DEFAULT_URL
        self.timeout = timeout
        self.headers: dict[str, str] = {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "Authorization": f"Zoho-enczapikey {api_key}",
        }


# ── Email ───────────────────────────────────────────────────

_Recipient = str | dict[str, str]
"""A recipient can be a plain email strings or ``{"address": ..., "name": ...}``."""

_ReplyTo = tuple[str, str]
"""A ``(email, name)`` pair."""

_Attachment = tuple[str, MimeType, str]
"""A ``(base64_content, mime_type, filename)`` triple."""


class Email:
    """Send transactional emails through the ZeptoMail API.

    Parameters:
        config: A :class:`Config` instance with credentials.
        bounce_address: Optional envelope bounce address.

    Example::

        config = Config(api_key="…")
        email  = Email(config)
        resp   = email.send(
            from_="noreply@example.com",
            from_name="Acme Corp",
            to=["alice@example.com"],
            subject="Welcome!",
            html_body="<h1>Hello, Alice!</h1>",
        )
    """

    DEFAULT_MIME_TYPE = MimeType("text", "plain")

    def __init__(self, config: Config, bounce_address: str | None = None) -> None:
        self.config = config
        self.bounce_address = bounce_address

    # ------------------------------------------------------------------

    def send(
        self,
        *,
        from_: str,
        from_name: str,
        to: Sequence[_Recipient],
        subject: str,
        text_body: str | None = None,
        html_body: str | None = None,
        reply_to: Sequence[_ReplyTo] = (),
        attachments: Sequence[_Attachment] = (),
        client_reference: str | None = None,
    ) -> dict[str, Any]:
        """Build and send an email.

        Parameters:
            from\_: Sender email address.
            from_name: Sender display name.
            to: List of recipient email addresses (strings) or dicts
                with ``address`` and ``name`` keys.
            subject: Email subject line.
            text_body: Plain-text body.
            html_body: HTML body.
            reply_to: Sequence of ``(email, name)`` tuples.
            attachments: Sequence of ``(base64_content, MimeType, filename)`` tuples.
            client_reference: Optional tracking reference.

        Returns:
            Parsed JSON response from the ZeptoMail API.

        Raises:
            ZeptoMailAPIError: If the API returns a non-2xx status.
        """
        payload = self._build_payload(
            from_=from_,
            from_name=from_name,
            to=to,
            subject=subject,
            text_body=text_body,
            html_body=html_body,
            reply_to=reply_to,
            attachments=attachments,
            client_reference=client_reference,
        )

        response = requests.post(
            self.config.url,
            headers=self.config.headers,
            json=payload,
            timeout=self.config.timeout,
        )

        if not response.ok:
            try:
                body = response.json()
            except ValueError:
                body = response.text
            raise ZeptoMailAPIError(
                f"ZeptoMail API error ({response.status_code})",
                status_code=response.status_code,
                response_body=body,
            )

        return response.json()

    # ------------------------------------------------------------------

    def _build_payload(
        self,
        *,
        from_: str,
        from_name: str,
        to: Sequence[_Recipient],
        subject: str,
        text_body: str | None = None,
        html_body: str | None = None,
        reply_to: Sequence[_ReplyTo] = (),
        attachments: Sequence[_Attachment] = (),
        client_reference: str | None = None,
    ) -> dict[str, Any]:
        """Assemble the JSON payload for the ZeptoMail API."""
        payload: dict[str, Any] = {
            "subject": subject,
            "from": {
                "address": from_,
                "name": from_name,
            },
            "to": [self._normalize_recipient(r) for r in to],
        }

        _safe_set("bounce_address", self.bounce_address, payload)
        _safe_set(
            "reply_to",
            [{"address": email, "name": name} for email, name in reply_to],
            payload,
        )
        _safe_set("textbody", text_body, payload)
        _safe_set("htmlbody", html_body, payload)
        _safe_set("client_reference", client_reference, payload)
        _safe_set(
            "attachments",
            [
                {
                    "content": content,
                    "mime_type": (
                        mime_type.validate() if mime_type else self.DEFAULT_MIME_TYPE
                    ).value,
                    "name": name,
                }
                for content, mime_type, name in attachments
            ],
            payload,
        )

        return payload

    # ------------------------------------------------------------------

    @staticmethod
    def _normalize_recipient(recipient: _Recipient) -> dict:
        """Accept a plain email string **or** a dict with address/name."""
        if isinstance(recipient, str):
            return {"email_address": {"address": recipient, "name": recipient}}
        # Already a dict – pass through
        return {"email_address": recipient}
