from __future__ import annotations import asyncio import logging from datetime import datetime, UTC from .config import EmailSmtpConfigAdapter from .smtp_client import probe_smtp from .types import EmailAccount logger = logging.getLogger(__name__) class EmailSmtpStatusAdapter: def __init__(self): self._config_adapter = EmailSmtpConfigAdapter() self._last_probe: dict[str, dict] = {} async def probe(self, account: dict) -> bool: account_dict = self._config_adapter.resolve_account(account.get("account_id", "default")) email_acct = EmailSmtpConfigAdapter.make_email_account(account_dict) smtp_ok = await probe_smtp(email_acct.smtp.host, email_acct.smtp.port, email_acct.smtp.use_tls) imap_ok = await self._probe_imap(email_acct) result = { "status": "ok" if (smtp_ok and imap_ok) else ("degraded" if (smtp_ok or imap_ok) else "down"), "smtp_connected": smtp_ok, "imap_connected": imap_ok, "probed_at": datetime.now(UTC).isoformat(), "account_id": account.get("account_id", "default"), } self._last_probe[account.get("account_id", "default")] = result return smtp_ok and imap_ok def build_summary(self, snapshot: object) -> dict: account_id = getattr(snapshot, "account_id", "default") result = self._last_probe.get(account_id, {}) return { "channel_type": "email-smtp", "account_id": account_id, "status": result.get("status", "unknown"), "smtp_ok": result.get("smtp_connected", False), "imap_ok": result.get("imap_connected", False), "last_probe": result.get("probed_at", ""), } async def _probe_imap(self, account: EmailAccount) -> bool: from imap_tools import MailBox try: def _check(): with MailBox(account.imap.host, account.imap.port).login( account.imap.username, account.imap.password, initial_folder="INBOX" ) as mb: status = mb.folder.status("INBOX") return status is not None return await asyncio.to_thread(_check) except Exception as e: logger.debug("IMAP probe failed: %s", e) return False