"""Gmail API response helpers.

Helper functions to parse raw JSON dicts from the Gmail API
into typed Pydantic models.
"""

from __future__ import annotations

import base64
import html as _html_module
from html.parser import HTMLParser
from typing import Any, Optional

from .types import (
    AutoForwarding,
    Delegate,
    Draft,
    Email,
    EmailAddress,
    Filter,
    FilterAction,
    FilterCriteria,
    ForwardingAddress,
    HistoryRecord,
    ImapSettings,
    Label,
    LanguageSettings,
    PopSettings,
    SendAs,
    Thread,
    VacationSettings,
)


class _HTMLToTextParser(HTMLParser):
    """Minimal HTML → plain-text converter used for multipart/alternative
    fallback when the caller supplies only ``html_body``.

    Not a full email-quality renderer — strips tags, preserves line breaks
    for block-level elements, and unescapes HTML entities. Callers who
    need a polished plain-text variant should pass ``text_body`` explicitly.
    """

    _BLOCK_TAGS = {
        "p",
        "div",
        "br",
        "li",
        "tr",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "blockquote",
        "pre",
        "article",
        "section",
    }
    _SKIP_TAGS = {"script", "style", "head"}

    def __init__(self) -> None:
        super().__init__()
        self._parts: list[str] = []
        self._skip_depth = 0

    def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None:
        if tag in self._SKIP_TAGS:
            self._skip_depth += 1
            return
        if tag in self._BLOCK_TAGS:
            self._parts.append("\n")

    def handle_endtag(self, tag: str) -> None:
        if tag in self._SKIP_TAGS and self._skip_depth > 0:
            self._skip_depth -= 1
            return
        if tag in self._BLOCK_TAGS:
            self._parts.append("\n")

    def handle_data(self, data: str) -> None:
        if self._skip_depth > 0:
            return
        self._parts.append(data)

    def get_text(self) -> str:
        raw = "".join(self._parts)
        # Collapse runs of 3+ newlines to 2 (paragraph break), leave single
        # newlines alone. Strip leading/trailing whitespace.
        import re

        collapsed = re.sub(r"\n{3,}", "\n\n", raw)
        # Strip trailing whitespace on each line (but keep the newlines)
        lines = [line.rstrip() for line in collapsed.split("\n")]
        return "\n".join(lines).strip()


def html_to_text(html: str) -> str:
    """Convert an HTML email body to plain text for multipart fallback.

    Handles tag stripping, block-level line breaks, entity unescape
    (``&amp;`` → ``&``, ``&lt;`` → ``<``, etc.), and script/style removal.

    Args:
        html: HTML source to convert.

    Returns:
        Plain-text equivalent suitable for a ``text/plain`` MIME part.
    """
    parser = _HTMLToTextParser()
    parser.feed(html)
    text = parser.get_text()
    # Unescape any remaining numeric + named entities that slipped through
    return _html_module.unescape(text)


def parse_email_address(raw: str) -> EmailAddress:
    """Parse a 'Display Name <email>' string into an EmailAddress.

    Args:
        raw: Raw address string from a Gmail header value.

    Returns:
        Parsed EmailAddress with name and email fields.
    """
    raw = raw.strip()
    if "<" in raw and raw.endswith(">"):
        name_part = raw[: raw.index("<")].strip().strip('"')
        email_part = raw[raw.index("<") + 1 : -1].strip()
        return EmailAddress(email=email_part, name=name_part or None)
    return EmailAddress(email=raw)


def get_header(headers: list[dict[str, str]], name: str) -> str:
    """Extract a header value by name from the Gmail headers array.

    Args:
        headers: List of {"name": ..., "value": ...} dicts from the API.
        name: Case-insensitive header name to find.

    Returns:
        The header value, or empty string if not found.
    """
    lower_name = name.lower()
    for h in headers:
        if h.get("name", "").lower() == lower_name:
            return h.get("value", "")
    return ""


def extract_body(payload: dict[str, Any]) -> tuple[Optional[str], Optional[str]]:
    """Recursively extract plain-text and HTML body from message payload.

    Args:
        payload: The Gmail API message payload dict.

    Returns:
        Tuple of (plain_text_body, html_body), either may be None.
    """
    text_body: Optional[str] = None
    html_body: Optional[str] = None

    mime_type = payload.get("mimeType", "")

    if mime_type == "text/plain":
        data = payload.get("body", {}).get("data", "")
        if data:
            text_body = base64.urlsafe_b64decode(data + "==").decode("utf-8", errors="replace")
    elif mime_type == "text/html":
        data = payload.get("body", {}).get("data", "")
        if data:
            html_body = base64.urlsafe_b64decode(data + "==").decode("utf-8", errors="replace")
    elif mime_type.startswith("multipart/"):
        for part in payload.get("parts", []):
            t, h = extract_body(part)
            if t and not text_body:
                text_body = t
            if h and not html_body:
                html_body = h

    return text_body, html_body


def has_attachments(payload: dict[str, Any]) -> bool:
    """Check whether the message payload contains file attachments.

    Args:
        payload: The Gmail API message payload dict.

    Returns:
        True if at least one part has a non-empty filename.
    """
    for part in payload.get("parts", []):
        if part.get("filename"):
            return True
        if part.get("parts"):
            if has_attachments(part):
                return True
    return False


def parse_message(data: dict[str, Any]) -> Email:
    """Parse a Gmail API message response into an Email model.

    Args:
        data: Raw JSON response from GET /users/me/messages/{id}.

    Returns:
        Populated Email instance.
    """
    payload = data.get("payload", {})
    headers = payload.get("headers", [])

    subject = get_header(headers, "Subject")
    from_raw = get_header(headers, "From")
    to_raw = get_header(headers, "To")
    cc_raw = get_header(headers, "Cc")
    date_str = get_header(headers, "Date")

    from_addr = parse_email_address(from_raw) if from_raw else None
    to_addrs = [parse_email_address(a) for a in to_raw.split(",") if a.strip()] if to_raw else []
    cc_addrs = [parse_email_address(a) for a in cc_raw.split(",") if a.strip()] if cc_raw else []

    text_body, html_body = extract_body(payload)

    return Email(
        id=data.get("id", ""),
        thread_id=data.get("threadId", ""),
        subject=subject,
        from_address=from_addr,
        to=to_addrs,
        cc=cc_addrs,
        date=date_str,
        snippet=data.get("snippet", ""),
        body_text=text_body,
        body_html=html_body,
        labels=data.get("labelIds", []),
        has_attachments=has_attachments(payload),
    )


def parse_thread(data: dict[str, Any]) -> Thread:
    """Parse a Gmail API thread response into a Thread model.

    Args:
        data: Raw JSON response from a threads endpoint.

    Returns:
        Populated Thread instance.
    """
    return Thread(
        id=data.get("id", ""),
        snippet=data.get("snippet", ""),
        history_id=data.get("historyId"),
        messages_count=len(data.get("messages", [])),
    )


def parse_label(data: dict[str, Any]) -> Label:
    """Parse a Gmail API label response into a Label model.

    Args:
        data: Raw JSON response from a labels endpoint.

    Returns:
        Populated Label instance.
    """
    return Label(
        id=data.get("id", ""),
        name=data.get("name", ""),
        type=data.get("type", "user"),
        messages_total=data.get("messagesTotal", 0),
        messages_unread=data.get("messagesUnread", 0),
    )


def parse_draft(data: dict[str, Any]) -> Draft:
    """Parse a Gmail API draft response into a Draft model.

    Args:
        data: Raw JSON response from a drafts endpoint.

    Returns:
        Populated Draft instance with parsed message if present.
    """
    msg_data = data.get("message", {})
    message = parse_message(msg_data) if msg_data.get("id") else None
    return Draft(
        id=data.get("id", ""),
        message=message,
    )


def parse_history_record(data: dict[str, Any]) -> HistoryRecord:
    """Parse a Gmail API history entry into a HistoryRecord model.

    Args:
        data: A single entry from the history list response.

    Returns:
        Populated HistoryRecord instance.
    """
    messages_added = [m.get("message", {}).get("id", "") for m in data.get("messagesAdded", [])]
    messages_deleted = [m.get("message", {}).get("id", "") for m in data.get("messagesDeleted", [])]
    labels_added = [m.get("message", {}).get("id", "") for m in data.get("labelsAdded", [])]
    labels_removed = [m.get("message", {}).get("id", "") for m in data.get("labelsRemoved", [])]
    return HistoryRecord(
        id=str(data.get("id", "")),
        messages_added=messages_added,
        messages_deleted=messages_deleted,
        labels_added=labels_added,
        labels_removed=labels_removed,
    )


def parse_vacation_settings(data: dict[str, Any]) -> VacationSettings:
    """Parse a Gmail API vacation settings response.

    Args:
        data: Raw JSON response from the vacation settings endpoint.

    Returns:
        Populated VacationSettings instance.
    """
    return VacationSettings(
        enable_auto_reply=data.get("enableAutoReply", False),
        response_subject=data.get("responseSubject"),
        response_body_plain_text=data.get("responseBodyPlainText"),
        response_body_html=data.get("responseBodyHtml"),
        start_time=str(data["startTime"]) if data.get("startTime") else None,
        end_time=str(data["endTime"]) if data.get("endTime") else None,
    )


# ---------------------------------------------------------------------------
# Settings parsers
# ---------------------------------------------------------------------------


def parse_filter(data: dict[str, Any]) -> Filter:
    """Parse a Gmail filter response."""
    criteria_raw = data.get("criteria", {}) or {}
    action_raw = data.get("action", {}) or {}
    criteria = FilterCriteria(
        from_address=criteria_raw.get("from"),
        to=criteria_raw.get("to"),
        subject=criteria_raw.get("subject"),
        query=criteria_raw.get("query"),
        negated_query=criteria_raw.get("negatedQuery"),
        has_attachment=criteria_raw.get("hasAttachment"),
        exclude_chats=criteria_raw.get("excludeChats"),
        size=criteria_raw.get("size"),
        size_comparison=criteria_raw.get("sizeComparison"),
    )
    action = FilterAction(
        add_label_ids=action_raw.get("addLabelIds", []) or [],
        remove_label_ids=action_raw.get("removeLabelIds", []) or [],
        forward=action_raw.get("forward"),
    )
    return Filter(id=data.get("id", ""), criteria=criteria, action=action)


def build_filter_criteria_payload(criteria: FilterCriteria) -> dict[str, Any]:
    """Convert a FilterCriteria model back into the API's snake→camel shape."""
    payload: dict[str, Any] = {}
    if criteria.from_address is not None:
        payload["from"] = criteria.from_address
    if criteria.to is not None:
        payload["to"] = criteria.to
    if criteria.subject is not None:
        payload["subject"] = criteria.subject
    if criteria.query is not None:
        payload["query"] = criteria.query
    if criteria.negated_query is not None:
        payload["negatedQuery"] = criteria.negated_query
    if criteria.has_attachment is not None:
        payload["hasAttachment"] = criteria.has_attachment
    if criteria.exclude_chats is not None:
        payload["excludeChats"] = criteria.exclude_chats
    if criteria.size is not None:
        payload["size"] = criteria.size
    if criteria.size_comparison is not None:
        payload["sizeComparison"] = criteria.size_comparison
    return payload


def build_filter_action_payload(action: FilterAction) -> dict[str, Any]:
    """Convert a FilterAction model back into the API's snake→camel shape."""
    payload: dict[str, Any] = {}
    if action.add_label_ids:
        payload["addLabelIds"] = action.add_label_ids
    if action.remove_label_ids:
        payload["removeLabelIds"] = action.remove_label_ids
    if action.forward is not None:
        payload["forward"] = action.forward
    return payload


def parse_send_as(data: dict[str, Any]) -> SendAs:
    """Parse a Gmail send-as alias response."""
    smtp = data.get("smtpMsa") or {}
    return SendAs(
        send_as_email=data.get("sendAsEmail", ""),
        display_name=data.get("displayName"),
        reply_to_address=data.get("replyToAddress"),
        signature=data.get("signature"),
        is_primary=data.get("isPrimary", False),
        is_default=data.get("isDefault", False),
        treat_as_alias=data.get("treatAsAlias", False),
        verification_status=data.get("verificationStatus"),
        smtp_msa_host=smtp.get("host"),
        smtp_msa_port=smtp.get("port"),
        smtp_msa_username=smtp.get("username"),
        smtp_msa_security_mode=smtp.get("securityMode"),
    )


def parse_delegate(data: dict[str, Any]) -> Delegate:
    """Parse a Gmail delegate response."""
    return Delegate(
        delegate_email=data.get("delegateEmail", ""),
        verification_status=data.get("verificationStatus"),
    )


def parse_forwarding_address(data: dict[str, Any]) -> ForwardingAddress:
    """Parse a Gmail forwarding address response."""
    return ForwardingAddress(
        forwarding_email=data.get("forwardingEmail", ""),
        verification_status=data.get("verificationStatus"),
    )


def parse_auto_forwarding(data: dict[str, Any]) -> AutoForwarding:
    """Parse a Gmail auto-forwarding settings response."""
    return AutoForwarding(
        enabled=data.get("enabled", False),
        email_address=data.get("emailAddress"),
        disposition=data.get("disposition"),
    )


def parse_imap_settings(data: dict[str, Any]) -> ImapSettings:
    """Parse a Gmail IMAP settings response."""
    return ImapSettings(
        enabled=data.get("enabled", False),
        auto_expunge=data.get("autoExpunge", True),
        expunge_behavior=data.get("expungeBehavior"),
        max_folder_size=data.get("maxFolderSize", 0),
    )


def parse_pop_settings(data: dict[str, Any]) -> PopSettings:
    """Parse a Gmail POP settings response."""
    return PopSettings(
        access_window=data.get("accessWindow"),
        disposition=data.get("disposition"),
    )


def parse_language_settings(data: dict[str, Any]) -> LanguageSettings:
    """Parse a Gmail language settings response."""
    return LanguageSettings(display_language=data.get("displayLanguage", "en"))
