"""Native email integration via IMAP/SMTP. No external tools needed."""

from __future__ import annotations

import email
import email.header
import email.utils
import imaplib
import logging
import smtplib
from dataclasses import dataclass, field
from datetime import UTC, datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from agntspace.integrations.base import Integration, IntegrationStatus

log = logging.getLogger(__name__)


@dataclass
class EmailMessage:
    """A parsed email message."""

    uid: str
    subject: str
    sender: str
    to: str
    date: str
    body: str
    is_read: bool = False
    folder: str = "INBOX"
    snippet: str = ""
    headers: dict = field(default_factory=dict)


@dataclass
class EmailConfig:
    """IMAP/SMTP configuration."""

    email_address: str = ""
    imap_host: str = ""
    imap_port: int = 993
    smtp_host: str = ""
    smtp_port: int = 587
    username: str = ""
    password: str = ""
    use_ssl: bool = True


class EmailIntegration(Integration):
    """Native email client using IMAP for reading and SMTP for sending."""

    name = "email"
    description = "Read, search, and send emails via IMAP/SMTP"

    def __init__(self) -> None:
        self._config: EmailConfig | None = None
        self._connected = False
        self._imap: imaplib.IMAP4_SSL | imaplib.IMAP4 | None = None
        self._last_sync: str = ""

    async def connect(self, credentials: dict) -> None:
        """Connect with IMAP/SMTP credentials."""
        import asyncio

        self._config = EmailConfig(
            email_address=credentials.get("email", "").strip(),
            imap_host=credentials.get("imap_host", "").strip(),
            imap_port=int(credentials.get("imap_port", 993)),
            smtp_host=credentials.get("smtp_host", "").strip(),
            smtp_port=int(credentials.get("smtp_port", 587)),
            username=credentials.get("username", credentials.get("email", "")).strip(),
            password=credentials.get("password", "").replace(" ", ""),
            use_ssl=credentials.get("use_ssl", True),
        )

        # Test IMAP connection in thread pool (imaplib is blocking)
        def _test_connection() -> None:
            imap = self._connect_imap()
            imap.logout()

        try:
            await asyncio.get_event_loop().run_in_executor(None, _test_connection)
            self._connected = True
            log.debug("Email connected: %s via %s", self._config.email_address, self._config.imap_host)
        except Exception as e:
            self._connected = False
            raise ConnectionError(f"IMAP connection failed: {e}") from e

    async def disconnect(self) -> None:
        import contextlib

        if self._imap:
            with contextlib.suppress(Exception):
                self._imap.logout()
            self._imap = None
        self._connected = False
        self._config = None

    async def check_status(self) -> IntegrationStatus:
        return IntegrationStatus(
            name=self.name,
            connected=self._connected,
            last_sync=self._last_sync,
            metadata={"email": self._config.email_address if self._config else ""},
        )

    async def sync(self) -> dict:
        """Fetch recent unread email count."""
        import asyncio

        if not self._connected or not self._config:
            return {"error": "Not connected"}
        try:
            emails = await asyncio.get_event_loop().run_in_executor(
                None, lambda: self.list_emails(folder="INBOX", unread_only=True, limit=10)
            )
            self._last_sync = datetime.now(UTC).isoformat()
            return {
                "unread": len(emails),
                "subjects": [e.subject for e in emails[:5]],
                "status": "ok",
            }
        except Exception as e:
            return {"error": str(e)}

    @property
    def is_connected(self) -> bool:
        return self._connected

    # ---- Email operations ----

    def get_inbox_count(self, folder: str = "INBOX") -> dict:
        """Get total and unread email counts for a folder.

        For Gmail: uses X-GM-RAW to query only the Primary category,
        excluding Promotions, Social, Updates, Forums.
        """
        if not self._config:
            return {"total": 0, "unread": 0}

        imap = self._connect_imap()
        try:
            status, data = imap.select(folder, readonly=True)
            total_all = int(data[0]) if data[0] else 0

            # Try Gmail-specific: only Primary category
            is_gmail = "gmail" in (getattr(self._config, "imap_host", "") or "").lower()
            if is_gmail:
                try:
                    _, primary_data = imap.search(None, 'X-GM-RAW', '"category:primary"')
                    total = len(primary_data[0].split()) if primary_data[0] else 0

                    _, unread_data = imap.search(None, 'X-GM-RAW', '"category:primary is:unread"')
                    unread = len(unread_data[0].split()) if unread_data[0] else 0

                    return {"total": total, "unread": unread, "total_all": total_all, "folder": folder}
                except Exception:
                    pass  # fall through to standard IMAP

            # Standard IMAP fallback
            _, unread_data = imap.search(None, "(UNSEEN)")
            unread = len(unread_data[0].split()) if unread_data[0] else 0

            return {"total": total_all, "unread": unread, "folder": folder}
        finally:
            imap.logout()

    def save_draft(self, to: str, subject: str, body: str) -> bool:
        """Save an email as a draft in the Gmail/IMAP Drafts folder."""
        if not self._config:
            return False

        msg = MIMEMultipart("alternative")
        msg["From"] = self._config.email_address
        msg["To"] = to
        msg["Subject"] = subject
        msg.attach(MIMEText(body, "plain", "utf-8"))

        imap = self._connect_imap()
        try:
            # Gmail uses "[Gmail]/Drafts", other providers may use "Drafts"
            draft_folder = "[Gmail]/Drafts"
            try:
                imap.select(draft_folder)
            except Exception:
                draft_folder = "Drafts"
                imap.select(draft_folder)

            imap.append(
                draft_folder, "\\Draft",
                None, msg.as_bytes(),
            )
            log.info("Draft saved to %s: %s", draft_folder, subject)
            return True
        except Exception:
            log.exception("Failed to save draft")
            return False
        finally:
            imap.logout()

    def list_emails(
        self,
        folder: str = "INBOX",
        limit: int = 20,
        unread_only: bool = False,
        search_query: str | None = None,
    ) -> list[EmailMessage]:
        """List emails from a folder."""
        if not self._config:
            return []

        imap = self._connect_imap()
        try:
            imap.select(folder, readonly=True)

            is_gmail = "gmail" in (getattr(self._config, "imap_host", "") or "").lower()

            if search_query:
                if is_gmail:
                    _, data = imap.search(None, 'X-GM-RAW', f'"category:primary {search_query}"')
                else:
                    criteria = f'(OR SUBJECT "{search_query}" FROM "{search_query}")'
                    _, data = imap.search(None, criteria)
            elif unread_only:
                if is_gmail:
                    _, data = imap.search(None, 'X-GM-RAW', '"category:primary is:unread"')
                else:
                    _, data = imap.search(None, "(UNSEEN)")
            else:
                if is_gmail:
                    _, data = imap.search(None, 'X-GM-RAW', '"category:primary"')
                else:
                    _, data = imap.search(None, "ALL")
            uids = data[0].split()

            # Take the most recent
            uids = uids[-limit:] if len(uids) > limit else uids
            uids.reverse()

            messages = []
            for uid in uids:
                msg = self._fetch_message(imap, uid, folder)
                if msg:
                    messages.append(msg)

            return messages
        finally:
            imap.logout()

    def read_email(self, uid: str, folder: str = "INBOX") -> EmailMessage | None:
        """Read a single email by UID."""
        if not self._config:
            return None

        imap = self._connect_imap()
        try:
            imap.select(folder, readonly=False)
            msg = self._fetch_message(imap, uid.encode(), folder, full_body=True)
            if msg:
                # Mark as read
                imap.store(uid.encode(), "+FLAGS", "\\Seen")
            return msg
        finally:
            imap.logout()

    def search_emails(self, query: str, folder: str = "INBOX", limit: int = 20) -> list[EmailMessage]:
        """Search emails by subject or sender."""
        return self.list_emails(folder=folder, limit=limit, search_query=query)

    def send_email(self, to: str, subject: str, body: str, reply_to_uid: str | None = None) -> bool:
        """Send an email via SMTP."""
        if not self._config:
            return False

        msg = MIMEMultipart("alternative")
        msg["From"] = self._config.email_address
        msg["To"] = to
        msg["Subject"] = subject
        msg["Date"] = email.utils.formatdate(localtime=True)

        # Plain text body
        msg.attach(MIMEText(body, "plain", "utf-8"))

        try:
            if self._config.smtp_port == 465:
                smtp = smtplib.SMTP_SSL(self._config.smtp_host, self._config.smtp_port)
            else:
                smtp = smtplib.SMTP(self._config.smtp_host, self._config.smtp_port)
                smtp.starttls()

            smtp.login(self._config.username, self._config.password)
            smtp.send_message(msg)
            smtp.quit()
            log.info("Email sent to %s: %s", to, subject)
            return True
        except Exception:
            log.exception("Failed to send email")
            return False

    def list_folders(self) -> list[str]:
        """List available email folders."""
        if not self._config:
            return []

        imap = self._connect_imap()
        try:
            _, folders = imap.list()
            result = []
            for f in folders or []:
                if isinstance(f, bytes):
                    # Parse folder name from IMAP response
                    parts = f.decode().split('"')
                    if len(parts) >= 4:
                        result.append(parts[-2])
                    else:
                        result.append(f.decode().split()[-1])
            return result
        finally:
            imap.logout()

    def move_email(self, uid: str, from_folder: str, to_folder: str) -> bool:
        """Move an email to another folder."""
        if not self._config:
            return False

        imap = self._connect_imap()
        try:
            imap.select(from_folder)
            imap.copy(uid.encode(), to_folder)
            imap.store(uid.encode(), "+FLAGS", "\\Deleted")
            imap.expunge()
            return True
        except Exception:
            log.exception("Failed to move email")
            return False
        finally:
            imap.logout()

    def mark_read(self, uid: str, folder: str = "INBOX") -> bool:
        """Mark an email as read."""
        if not self._config:
            return False

        imap = self._connect_imap()
        try:
            imap.select(folder)
            imap.store(uid.encode(), "+FLAGS", "\\Seen")
            return True
        except Exception:
            return False
        finally:
            imap.logout()

    def mark_unread(self, uid: str, folder: str = "INBOX") -> bool:
        """Mark an email as unread."""
        if not self._config:
            return False

        imap = self._connect_imap()
        try:
            imap.select(folder)
            imap.store(uid.encode(), "-FLAGS", "\\Seen")
            return True
        except Exception:
            return False
        finally:
            imap.logout()

    # ---- Internal helpers ----

    def _connect_imap(self) -> imaplib.IMAP4_SSL | imaplib.IMAP4:
        if not self._config:
            raise ConnectionError("Email not configured")

        if self._config.use_ssl:
            imap = imaplib.IMAP4_SSL(self._config.imap_host, self._config.imap_port)
        else:
            imap = imaplib.IMAP4(self._config.imap_host, self._config.imap_port)

        imap.login(self._config.username, self._config.password)
        return imap

    def _fetch_message(
        self,
        imap: imaplib.IMAP4_SSL | imaplib.IMAP4,
        uid: bytes,
        folder: str,
        full_body: bool = False,
    ) -> EmailMessage | None:
        try:
            fetch_cmd = "(RFC822)" if full_body else "(RFC822.HEADER FLAGS BODY.PEEK[TEXT]<0.500>)"
            _, data = imap.fetch(uid, fetch_cmd)
            if not data or not data[0]:
                return None

            raw = data[0][1] if isinstance(data[0], tuple) else data[0]
            if not isinstance(raw, bytes):
                return None

            msg = email.message_from_bytes(raw)

            subject = self._decode_header(msg.get("Subject", ""))
            sender = self._decode_header(msg.get("From", ""))
            to_addr = self._decode_header(msg.get("To", ""))
            date_str = msg.get("Date", "")

            # Extract body
            body = ""
            if msg.is_multipart():
                for part in msg.walk():
                    content_type = part.get_content_type()
                    if content_type == "text/plain":
                        payload = part.get_payload(decode=True)
                        if payload:
                            body = payload.decode(part.get_content_charset() or "utf-8", errors="replace")
                            break
            else:
                payload = msg.get_payload(decode=True)
                if payload:
                    body = payload.decode(msg.get_content_charset() or "utf-8", errors="replace")

            # Check flags for read status
            flags_data = data[0][0] if isinstance(data[0], tuple) else b""
            is_read = b"\\Seen" in flags_data if isinstance(flags_data, bytes) else False

            snippet = body[:200].replace("\n", " ").strip() if body else ""

            return EmailMessage(
                uid=uid.decode() if isinstance(uid, bytes) else str(uid),
                subject=subject,
                sender=sender,
                to=to_addr,
                date=date_str,
                body=body if full_body else snippet,
                is_read=is_read,
                folder=folder,
                snippet=snippet,
            )
        except Exception:
            log.debug("Failed to parse email %s", uid, exc_info=True)
            return None

    @staticmethod
    def _decode_header(value: str) -> str:
        if not value:
            return ""
        decoded_parts = email.header.decode_header(value)
        result = []
        for part, charset in decoded_parts:
            if isinstance(part, bytes):
                result.append(part.decode(charset or "utf-8", errors="replace"))
            else:
                result.append(part)
        return " ".join(result)


class EmailRegistry:
    """Registry of multiple email accounts.

    Each account is an independent ``EmailIntegration`` keyed by a
    user-chosen label (e.g. ``"personal"``, ``"work"``).  The first
    connected account becomes the default.

    Backward compatible: the legacy single-account credential key
    ``"email"`` maps to account ``"default"`` on startup.
    """

    def __init__(self) -> None:
        self._accounts: dict[str, EmailIntegration] = {}

    # -- account lifecycle ---------------------------------------------------

    async def connect(self, account_id: str, credentials: dict) -> EmailIntegration:
        """Connect (or reconnect) an account."""
        em = self._accounts.get(account_id)
        if em and em.is_connected:
            await em.disconnect()
        em = EmailIntegration()
        await em.connect(credentials)
        self._accounts[account_id] = em
        return em

    async def disconnect(self, account_id: str) -> None:
        em = self._accounts.pop(account_id, None)
        if em:
            await em.disconnect()

    async def disconnect_all(self) -> None:
        for em in list(self._accounts.values()):
            await em.disconnect()
        self._accounts.clear()

    # -- queries -------------------------------------------------------------

    def get(self, account_id: str) -> EmailIntegration | None:
        return self._accounts.get(account_id)

    def get_default(self) -> EmailIntegration | None:
        """Return the first connected account (or None)."""
        for em in self._accounts.values():
            if em.is_connected:
                return em
        return None

    def list_accounts(self) -> list[dict]:
        """Return a list of {id, email, connected} for all known accounts."""
        out: list[dict] = []
        for aid, em in self._accounts.items():
            cfg = em._config
            out.append({
                "id": aid,
                "email": cfg.email_address if cfg else "",
                "connected": em.is_connected,
            })
        return out

    @property
    def has_connected(self) -> bool:
        return any(em.is_connected for em in self._accounts.values())

    # -- backward compat shims -----------------------------------------------

    @property
    def is_connected(self) -> bool:
        """Legacy single-account compat."""
        return self.has_connected

    async def check_status(self) -> IntegrationStatus:
        """Legacy: returns status of default account."""
        em = self.get_default()
        if em:
            return await em.check_status()
        return IntegrationStatus(name="email", connected=False)
