import html as _html
import mimetypes
import tempfile as _tempfile
from datetime import datetime, timedelta
from pathlib import Path

from backend.config import GOOGLE_API_SUPPORT, OUTLOOK_SUPPORT, _google_service, _outlook_session
from backend.gmail_helpers_runtime import _gmail_extract_plain_text_body, _gmail_message_headers
from backend.mail_helpers_runtime import (
    _STANDARD_MAIL_FOLDERS,
    _attachment_is_probably_inline,
    _extract_attachment_content_from_saved_file,
    _gmail_collect_attachments,
    _gmail_download_attachment_bytes,
    _is_date_only,
    _normalize_mail_folder,
    _outlook_build_mail_restriction,
    _outlook_get_mail_item,
    _outlook_is_primary_mailbox_request,
    _outlook_mailbox_date_field,
    _outlook_mailbox_party,
    _outlook_resolve_folder_by_path,
    _outlook_select_mailboxes,
    _save_attachment_file,
    _unique_attachment_path,
)


_GMAIL_FOLDER_QUERIES = {
    "Inbox": "in:inbox",
    "Sent": "in:sent",
    "Drafts": "in:drafts",
    "Deleted": "in:trash",
}

_ATTACHMENT_EXTRACTION_PRIORITY: dict[str, int] = {
    ".pdf": 0,
    ".doc": 1,
    ".docx": 1,
    ".ppt": 1,
    ".pptx": 1,
    ".xls": 1,
    ".xlsx": 1,
    ".csv": 1,
    ".txt": 1,
    ".md": 1,
    ".rtf": 1,
    ".json": 1,
    ".xml": 1,
    ".html": 1,
    ".htm": 1,
    ".png": 2,
    ".jpg": 2,
    ".jpeg": 2,
    ".gif": 2,
    ".bmp": 2,
    ".webp": 2,
    ".tif": 2,
    ".tiff": 2,
}


def _attachment_extraction_rank(filename: str) -> tuple[int, str]:
    suffix = Path(str(filename or "")).suffix.lower()
    return (_ATTACHMENT_EXTRACTION_PRIORITY.get(suffix, 3), str(filename or "").lower())


def _prioritize_attachment_entries(entries: list[dict]) -> list[dict]:
    return sorted(list(entries or []), key=lambda item: _attachment_extraction_rank(str(item.get("filename") or item.get("name") or "")))


def _gmail_build_date_query_parts(today_only: bool = False, since: str = "", until: str = "") -> list[str]:
    parts: list[str] = []
    if today_only and not since and not until:
        today = datetime.now().date()
        tomorrow = today + timedelta(days=1)
        parts.append(f"after:{today.strftime('%Y/%m/%d')}")
        parts.append(f"before:{tomorrow.strftime('%Y/%m/%d')}")
        return parts
    if since:
        parts.append(f"after:{since.replace('-', '/')}")
    if until:
        until_value = until.replace('-', '/')
        if _is_date_only(until):
            try:
                until_dt = datetime.strptime(until, "%Y-%m-%d") + timedelta(days=1)
                until_value = until_dt.strftime("%Y/%m/%d")
            except Exception:
                until_value = until.replace('-', '/')
        parts.append(f"before:{until_value}")
    return parts


def _outlook_get_inbox(
    count: int = 250, unread_only: bool = False, folder: str = "Inbox",
    mailbox: str = "ALL", today_only: bool = False, since: str = "", until: str = "",
    body_length: int = 300,
) -> str:
    if not OUTLOOK_SUPPORT:
        return "Outlook not available: pywin32 not installed."
    try:
        folder_ids = {"Inbox": 6, "Sent": 5, "Drafts": 16, "Deleted": 3}
        folder = _normalize_mail_folder(folder)
        is_custom = folder not in _STANDARD_MAIL_FOLDERS
        fid = folder_ids.get(folder, 6)
        date_field = _outlook_mailbox_date_field(folder)
        with _outlook_session() as (_, ns):
            if is_custom:
                custom_folder = _outlook_resolve_folder_by_path(ns, folder)
                if custom_folder:
                    store_name = "Default"
                    try:
                        st = getattr(custom_folder, "Store", None)
                        store_name = str(getattr(st, "DisplayName", "") or "Default")
                    except Exception:
                        pass
                    all_targets = [(store_name, custom_folder)]
                    targets = all_targets
                else:
                    return f"Could not find Outlook folder: {folder}"
            else:
                all_targets = _outlook_select_mailboxes(ns, folder_id=fid, mailbox="ALL")
                targets = _outlook_select_mailboxes(ns, folder_id=fid, mailbox=mailbox)
            if not targets:
                try:
                    targets = [("Default", ns.GetDefaultFolder(fid))]
                except Exception:
                    pass
            if not targets:
                available = [name for name, _ in _outlook_select_mailboxes(ns, folder_id=6, mailbox="ALL")]
                return (
                    f"No mailbox matched '{mailbox}'. "
                    f"Available mailboxes: {', '.join(available) if available else 'none'}"
                )
            results: list[str] = []
            load_errors: list[str] = []
            for store_name, mbox in targets:
                if len(results) >= count:
                    break
                try:
                    items = mbox.Items
                    restriction = _outlook_build_mail_restriction(
                        today_only=today_only, since=since, until=until,
                    ).replace("ReceivedTime", date_field)
                    if restriction:
                        try:
                            items = items.Restrict(restriction)
                        except Exception as _re:
                            load_errors.append(f"{store_name}: Restrict failed ({_re}), scanning all items")
                    items.Sort(f"[{date_field}]", True)
                    total = int(items.Count)
                except Exception as _ie:
                    load_errors.append(f"{store_name}: cannot open folder ({_ie})")
                    continue
                for idx in range(1, total + 1):
                    if len(results) >= count:
                        break
                    try:
                        msg = items.Item(idx)
                        msg_class = getattr(msg, "MessageClass", "IPM.Note")
                        if not str(msg_class).startswith("IPM.Note"):
                            continue
                        if unread_only and not msg.UnRead:
                            continue
                        party_label, party_value = _outlook_mailbox_party(msg, folder)
                        message_dt = getattr(msg, date_field, "")
                        att_count = 0
                        att_names: list[str] = []
                        try:
                            att_count = int(msg.Attachments.Count)
                            for ai in range(1, min(att_count + 1, 6)):
                                att_names.append(str(msg.Attachments.Item(ai).FileName or ""))
                        except Exception:
                            pass
                        att_line = f"\nAttachments: {att_count}" if att_count else ""
                        att_names_line = f"\nAttachment Names: {', '.join(n for n in att_names if n)}" if att_names else ""
                        is_unread = bool(getattr(msg, "UnRead", False))
                        last_verb = int(getattr(msg, "LastVerbExecuted", 0) or 0)
                        status = "Replied" if last_verb in (102, 103) else ("Unread" if is_unread else "Read")
                        results.append(
                            f"Mailbox: {store_name}\n"
                            f"Folder: {folder}\n"
                            f"Message ID: {getattr(msg, 'EntryID', '') or '?'}\n"
                            f"Thread ID: {getattr(msg, 'ConversationID', '') or '?'}\n"
                            f"Status: {status}\n"
                            f"{party_label}: {party_value}\n"
                            f"Subject: {msg.Subject}\n"
                            f"Date: {message_dt}"
                            f"{att_line}{att_names_line}\n"
                            f"Preview: {(msg.Body or '').strip()[:body_length]}"
                        )
                    except Exception:
                        pass
            if not results and _outlook_is_primary_mailbox_request(mailbox) and len(all_targets) > 1:
                load_errors.append("Primary mailbox returned no emails; retried across all Outlook mailboxes")
                for store_name, mbox in all_targets:
                    if len(results) >= count:
                        break
                    try:
                        items = mbox.Items
                        restriction = _outlook_build_mail_restriction(
                            today_only=today_only, since=since, until=until,
                        ).replace("ReceivedTime", date_field)
                        if restriction:
                            try:
                                items = items.Restrict(restriction)
                            except Exception as _re:
                                load_errors.append(f"{store_name}: Restrict failed ({_re}), scanning all items")
                        items.Sort(f"[{date_field}]", True)
                        total = int(items.Count)
                    except Exception as _ie:
                        load_errors.append(f"{store_name}: cannot open folder ({_ie})")
                        continue
                    for idx in range(1, total + 1):
                        if len(results) >= count:
                            break
                        try:
                            msg = items.Item(idx)
                            msg_class = getattr(msg, "MessageClass", "IPM.Note")
                            if not str(msg_class).startswith("IPM.Note"):
                                continue
                            if unread_only and not msg.UnRead:
                                continue
                            party_label, party_value = _outlook_mailbox_party(msg, folder)
                            message_dt = getattr(msg, date_field, "")
                            att_count = 0
                            att_names: list[str] = []
                            try:
                                att_count = int(msg.Attachments.Count)
                                for ai in range(1, min(att_count + 1, 6)):
                                    att_names.append(str(msg.Attachments.Item(ai).FileName or ""))
                            except Exception:
                                pass
                            att_line = f"\nAttachments: {att_count}" if att_count else ""
                            att_names_line = f"\nAttachment Names: {', '.join(n for n in att_names if n)}" if att_names else ""
                            is_unread = bool(getattr(msg, "UnRead", False))
                            last_verb = int(getattr(msg, "LastVerbExecuted", 0) or 0)
                            status = "Replied" if last_verb in (102, 103) else ("Unread" if is_unread else "Read")
                            results.append(
                                f"Mailbox: {store_name}\n"
                                f"Folder: {folder}\n"
                                f"Message ID: {getattr(msg, 'EntryID', '') or '?'}\n"
                                f"Thread ID: {getattr(msg, 'ConversationID', '') or '?'}\n"
                                f"Status: {status}\n"
                                f"{party_label}: {party_value}\n"
                                f"Subject: {msg.Subject}\n"
                                f"Date: {message_dt}"
                                f"{att_line}{att_names_line}\n"
                                f"Preview: {(msg.Body or '').strip()[:body_length]}"
                            )
                        except Exception:
                            pass
        parts: list[str] = []
        if results:
            parts.append("\n\n---\n\n".join(results))
        else:
            parts.append("No emails found.")
        if load_errors:
            parts.append("\n[Warnings: " + "; ".join(load_errors) + "]")
        return "".join(parts)
    except Exception as e:
        return f"Outlook inbox error: {e}"


def _outlook_get_thread(conversation_id: str, body_length: int = 500, max_messages: int = 10) -> str:
    if not OUTLOOK_SUPPORT:
        return "Outlook not available: pywin32 not installed."
    cid = str(conversation_id or "").strip()
    if not cid or cid == "?":
        return "No conversation ID provided."
    try:
        with _outlook_session() as (_, ns):
            targets = _outlook_select_mailboxes(ns, folder_id=6, mailbox="ALL")
            if not targets:
                try:
                    targets = [("Default", ns.GetDefaultFolder(6))]
                except Exception:
                    return "Cannot open Outlook mailbox."
            results: list[str] = []
            restriction = f"@SQL=\"http://schemas.microsoft.com/mapi/proptag/0x30130102\" = '{cid}'"
            for _, mbox in targets:
                try:
                    items = mbox.Items
                    try:
                        filtered = items.Restrict(restriction)
                    except Exception:
                        filtered = None
                    if filtered and int(filtered.Count) > 0:
                        filtered.Sort("[ReceivedTime]", False)
                        for idx in range(1, min(int(filtered.Count) + 1, max_messages + 1)):
                            try:
                                msg = filtered.Item(idx)
                                _, party_value = _outlook_mailbox_party(msg, "Inbox")
                                results.append(
                                    f"From: {party_value}\n"
                                    f"Date: {getattr(msg, 'ReceivedTime', '?')}\n"
                                    f"Body: {(msg.Body or '').strip()[:body_length]}"
                                )
                            except Exception:
                                pass
                    elif filtered is None:
                        items.Sort("[ReceivedTime]", False)
                        total = int(items.Count)
                        for idx in range(1, min(total + 1, max_messages * 10 + 1)):
                            try:
                                msg = items.Item(idx)
                                if str(getattr(msg, "ConversationID", "") or "").strip() == cid:
                                    _, party_value = _outlook_mailbox_party(msg, "Inbox")
                                    results.append(
                                        f"From: {party_value}\n"
                                        f"Date: {getattr(msg, 'ReceivedTime', '?')}\n"
                                        f"Body: {(msg.Body or '').strip()[:body_length]}"
                                    )
                                    if len(results) >= max_messages:
                                        break
                            except Exception:
                                pass
                except Exception:
                    pass
                if len(results) >= max_messages:
                    break
            sent_targets = _outlook_select_mailboxes(ns, folder_id=5, mailbox="ALL")
            for _, sent_box in sent_targets:
                if len(results) >= max_messages:
                    break
                try:
                    sent_items = sent_box.Items
                    try:
                        sent_filtered = sent_items.Restrict(restriction)
                    except Exception:
                        sent_filtered = None
                    if sent_filtered and int(sent_filtered.Count) > 0:
                        sent_filtered.Sort("[SentOn]", False)
                        for idx in range(1, min(int(sent_filtered.Count) + 1, max_messages + 1)):
                            if len(results) >= max_messages:
                                break
                            try:
                                msg = sent_filtered.Item(idx)
                                results.append(
                                    f"From: Me\n"
                                    f"Date: {getattr(msg, 'SentOn', '?')}\n"
                                    f"Body: {(msg.Body or '').strip()[:body_length]}"
                                )
                            except Exception:
                                pass
                    elif sent_filtered is None:
                        sent_items.Sort("[SentOn]", False)
                        total = int(sent_items.Count)
                        for idx in range(1, min(total + 1, max_messages * 10 + 1)):
                            if len(results) >= max_messages:
                                break
                            try:
                                msg = sent_items.Item(idx)
                                if str(getattr(msg, "ConversationID", "") or "").strip() == cid:
                                    results.append(
                                        f"From: Me\n"
                                        f"Date: {getattr(msg, 'SentOn', '?')}\n"
                                        f"Body: {(msg.Body or '').strip()[:body_length]}"
                                    )
                            except Exception:
                                pass
                except Exception:
                    pass
        if not results:
            return "No messages found in this conversation."
        results.sort(key=lambda item: item.split("Date: ")[1].split("\n")[0] if "Date: " in item else "")
        return "\n\n---\n\n".join(results[:max_messages])
    except Exception as e:
        return f"Outlook thread error: {e}"


def _outlook_get_email(message_id: str, include_attachments: bool = False, max_attachments: int = 4) -> str:
    """Fetch a single Outlook email's full content by Message ID."""
    if not OUTLOOK_SUPPORT:
        return "Outlook not available: pywin32 not installed."
    mid = str(message_id or "").strip()
    if not mid:
        return "No message ID provided."
    try:
        with _outlook_session() as (_, ns):
            msg = _outlook_get_mail_item(ns, mid)
            if not msg:
                return f"Email not found for Message ID: {mid}"
            subject = getattr(msg, "Subject", "") or "(no subject)"
            sender = str(getattr(msg, "SenderEmailAddress", "") or getattr(msg, "SenderName", "?"))
            date = str(getattr(msg, "ReceivedTime", "?"))
            body = (msg.Body or "").strip()
            to = str(getattr(msg, "To", "") or "")
            cc = str(getattr(msg, "CC", "") or "")
            att_count = 0
            att_names = []
            attachment_sections: list[str] = []
            try:
                atts = msg.Attachments
                att_count = int(atts.Count)
                non_inline_entries: list[dict] = []
                for i in range(1, min(att_count + 1, 20)):
                    attachment = atts.Item(i)
                    att_name = str(attachment.FileName or "")
                    try:
                        att_size = int(getattr(attachment, "Size", 0) or 0)
                    except Exception:
                        att_size = 0
                    if _attachment_is_probably_inline(att_name, size_bytes=att_size):
                        continue
                    if att_name:
                        att_names.append(att_name)
                    non_inline_entries.append({"index": i, "filename": att_name, "size": att_size})
                if include_attachments and att_count > 0:
                    capped_max = max(1, min(int(max_attachments or 4), 8))
                    with _tempfile.TemporaryDirectory(prefix="agent_mail_att_") as temp_dir_name:
                        temp_dir = Path(temp_dir_name)
                        for extracted_count, entry in enumerate(_prioritize_attachment_entries(non_inline_entries)[:capped_max], start=1):
                            attachment = atts.Item(int(entry.get("index") or extracted_count))
                            att_name = str(entry.get("filename") or "")
                            att_size = int(entry.get("size") or 0)
                            saved_path = _unique_attachment_path(temp_dir, att_name or f"attachment_{extracted_count}.bin")
                            attachment.SaveAsFile(str(saved_path))
                            extracted = _extract_attachment_content_from_saved_file(saved_path)
                            section_lines = [f"[ATTACHMENT {extracted_count}] {saved_path.name}"]
                            if att_size:
                                section_lines.append(f"Size: {att_size} bytes")
                            if extracted:
                                section_lines.append(extracted)
                            attachment_sections.append("\n".join(section_lines).strip())
            except Exception:
                pass
            parts = [
                f"Subject: {subject}",
                f"From: {sender}",
                f"To: {to}" if to else None,
                f"CC: {cc}" if cc else None,
                f"Date: {date}",
                f"Attachments: {', '.join(n for n in att_names if n)}" if att_names else None,
                f"Message-ID: {mid}",
                f"Thread ID: {getattr(msg, 'ConversationID', '') or '?'}",
                "Provider: outlook",
                f"\n{body}",
            ]
            if include_attachments:
                parts.append("")
                parts.append("[ATTACHMENT CONTENT]")
                if attachment_sections:
                    parts.append("Attachment content was extracted on demand because include_attachments=true.")
                    parts.append("\n\n---\n\n".join(attachment_sections))
                    omitted = max(0, len(att_names) - len(attachment_sections))
                    if omitted:
                        parts.append(f"[Attachment analysis limited to first {len(attachment_sections)} file(s); {omitted} more omitted.]")
                elif att_names:
                    parts.append("Attachments were detected, but no attachment content could be extracted.")
                else:
                    parts.append("No non-inline attachments were found for this email.")
            return "\n".join(p for p in parts if p)
    except Exception as e:
        return f"Outlook get-email error: {e}"


def _gmail_get_inbox(
    count: int = 20, unread_only: bool = False,
    today_only: bool = False, since: str = "", until: str = "", folder: str = "Inbox",
    body_length: int = 300,
) -> str:
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("gmail", "v1")
    if not service:
        return "Google not authorized. Ask the user to click 'Authorize Google' in Settings → Integrations."
    try:
        folder = _normalize_mail_folder(folder)
        q_parts: list[str] = []
        folder_query = _GMAIL_FOLDER_QUERIES.get(folder)
        if folder_query:
            q_parts.append(folder_query)
        elif folder not in _STANDARD_MAIL_FOLDERS:
            q_parts.append(f"label:{folder.replace(' ', '-')}")
        if unread_only:
            q_parts.append("is:unread")
        q_parts.extend(_gmail_build_date_query_parts(today_only=today_only, since=since, until=until))
        query = " ".join(q_parts)
        resp = service.users().messages().list(
            userId="me", q=query, maxResults=min(count, 100)
        ).execute()
        messages = resp.get("messages", [])
        if not messages:
            return "No emails found." if not unread_only else "No unread emails."
        fetch_full_body = body_length > 300
        results = []
        for msg_stub in messages:
            if fetch_full_body:
                msg = service.users().messages().get(
                    userId="me", id=msg_stub["id"], format="full",
                ).execute()
            else:
                msg = service.users().messages().get(
                    userId="me", id=msg_stub["id"], format="metadata",
                    metadataHeaders=["From", "To", "Subject", "Date"],
                ).execute()
            headers = _gmail_message_headers(msg)
            if fetch_full_body:
                preview_text = _gmail_extract_plain_text_body(msg.get("payload") or {})[:body_length]
                if not preview_text:
                    preview_text = _html.unescape(msg.get("snippet", ""))[:body_length]
            else:
                preview_text = _html.unescape(msg.get("snippet", ""))[:300]
            party_header = "To" if folder == "Sent" else "From"
            label_ids = msg.get("labelIds", [])
            status = "Unread" if "UNREAD" in label_ids else "Read"
            results.append(
                f"Mailbox: Gmail\n"
                f"Folder: {folder}\n"
                f"Message ID: {msg.get('id', '?')}\n"
                f"Thread ID: {msg.get('threadId', '?')}\n"
                f"Status: {status}\n"
                f"{party_header}: {headers.get(party_header, '?')}\n"
                f"Subject: {headers.get('Subject', '(no subject)')}\n"
                f"Date: {headers.get('Date', '?')}\n"
                f"Preview: {preview_text}"
            )
        return "\n\n---\n\n".join(results)
    except Exception as e:
        return f"Gmail inbox error: {e}"


def _gmail_get_thread(thread_id: str, body_length: int = 500, max_messages: int = 10) -> str:
    """Fetch all messages in a Gmail thread, oldest-first."""
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("gmail", "v1")
    if not service:
        return "Google not authorized."
    tid = str(thread_id or "").strip()
    if not tid or tid == "?":
        return "No thread ID provided."
    try:
        thread = service.users().threads().get(
            userId="me", id=tid, format="full",
        ).execute()
        messages = thread.get("messages") or []
        if not messages:
            return "Thread is empty."
        results: list[str] = []
        for msg in messages[:max_messages]:
            headers = _gmail_message_headers(msg)
            body_text = _gmail_extract_plain_text_body(msg.get("payload") or {})[:body_length]
            if not body_text:
                body_text = _html.unescape(msg.get("snippet", ""))[:body_length]
            results.append(
                f"From: {headers.get('From', '?')}\n"
                f"To: {headers.get('To', '?')}\n"
                f"Date: {headers.get('Date', '?')}\n"
                f"Body: {body_text}"
            )
        return "\n\n---\n\n".join(results)
    except Exception as e:
        return f"Gmail thread error: {e}"


def _gmail_get_email(message_id: str, include_attachments: bool = False, max_attachments: int = 4) -> str:
    """Fetch a single Gmail email's full content by Message ID."""
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("gmail", "v1")
    if not service:
        return "Google not authorized."
    mid = str(message_id or "").strip()
    if not mid:
        return "No message ID provided."
    try:
        msg = service.users().messages().get(
            userId="me", id=mid, format="full",
        ).execute()
        headers = _gmail_message_headers(msg)
        attachments = _gmail_collect_attachments(msg.get("payload") or {})
        non_inline_attachments = [
            item for item in attachments
            if not _attachment_is_probably_inline(
                str(item.get("filename") or ""),
                mime_type=str(item.get("mime_type") or ""),
                size_bytes=int(item.get("size") or 0),
                disposition=str(item.get("disposition") or ""),
            )
        ]
        attachment_names = [str(item.get("filename") or "attachment") for item in non_inline_attachments]
        body_text = _gmail_extract_plain_text_body(msg.get("payload") or {})
        if not body_text:
            body_text = msg.get("snippet", "")
        parts = [
            f"Subject: {headers.get('Subject', '(no subject)')}",
            f"From: {headers.get('From', '?')}",
            f"To: {headers.get('To', '')}" if headers.get('To') else None,
            f"CC: {headers.get('Cc', '')}" if headers.get('Cc') else None,
            f"Date: {headers.get('Date', '?')}",
            f"Attachments: {', '.join(attachment_names)}" if attachment_names else None,
            f"Message-ID: {mid}",
            f"Thread ID: {msg.get('threadId', '?')}",
            "Provider: gmail",
            f"\n{body_text}",
        ]
        if include_attachments:
            parts.append("")
            parts.append("[ATTACHMENT CONTENT]")
            if non_inline_attachments:
                capped_max = max(1, min(int(max_attachments or 4), 8))
                attachment_sections: list[str] = []
                with _tempfile.TemporaryDirectory(prefix="agent_mail_att_") as temp_dir_name:
                    temp_dir = Path(temp_dir_name)
                    prioritized_attachments = _prioritize_attachment_entries(non_inline_attachments)
                    for index, attachment in enumerate(prioritized_attachments[:capped_max], start=1):
                        filename = str(attachment.get("filename") or "").strip()
                        mime_type = str(attachment.get("mime_type") or "").strip()
                        file_bytes = _gmail_download_attachment_bytes(service, mid, attachment)
                        fallback_ext = mimetypes.guess_extension(mime_type) or ".bin"
                        saved_path = _save_attachment_file(temp_dir, filename or f"attachment_{index}{fallback_ext}", file_bytes)
                        extracted = _extract_attachment_content_from_saved_file(saved_path)
                        section_lines = [f"[ATTACHMENT {index}] {saved_path.name}"]
                        try:
                            size = int(attachment.get("size") or len(file_bytes))
                        except Exception:
                            size = len(file_bytes)
                        if size:
                            section_lines.append(f"Size: {size} bytes")
                        if extracted:
                            section_lines.append(extracted)
                        attachment_sections.append("\n".join(section_lines).strip())
                parts.append("Attachment content was extracted on demand because include_attachments=true.")
                parts.append("\n\n---\n\n".join(attachment_sections))
                omitted = max(0, len(non_inline_attachments) - len(attachment_sections))
                if omitted:
                    parts.append(f"[Attachment analysis limited to first {len(attachment_sections)} file(s); {omitted} more omitted.]")
            else:
                parts.append("No non-inline attachments were found for this email.")
        return "\n".join(p for p in parts if p)
    except Exception as e:
        return f"Gmail get-email error: {e}"
