"""
IMAP / email source connector — extracts file attachments from email.

Auth (env vars):
    EXTRACTOR_IMAP_HOST      (e.g. imap.gmail.com)
    EXTRACTOR_IMAP_PORT      (default: 993)
    EXTRACTOR_IMAP_USERNAME
    EXTRACTOR_IMAP_PASSWORD  (use app password for Gmail/Outlook)
    EXTRACTOR_IMAP_FOLDER    (default: INBOX)
    EXTRACTOR_IMAP_SEARCH    (IMAP search string, default: UNSEEN)

URI format:
    imap://inbox           (uses env vars for connection)
    imaps://inbox          (TLS, same as imap but explicit)
    email://inbox          (alias)

Requires: pip install 'coresdk-extractor[email]'  (imaplib is stdlib)
"""

from __future__ import annotations

import contextlib
import email as _email_lib
import imaplib
import os
import tempfile
from collections.abc import Iterator
from email.message import Message
from pathlib import Path

from .base import SourceFile
from .registry import register_source

_ATTACHMENT_EXTS = frozenset(
    {
        ".pdf",
        ".docx",
        ".doc",
        ".pptx",
        ".ppt",
        ".xlsx",
        ".xls",
        ".txt",
        ".md",
        ".html",
        ".htm",
        ".epub",
        ".csv",
    }
)


def _is_wanted_attachment(part: Message) -> bool:
    filename = part.get_filename() or ""
    return Path(filename).suffix.lower() in _ATTACHMENT_EXTS


class ImapSource:
    scheme = "imap"

    def _connect(self) -> imaplib.IMAP4_SSL:
        host = os.environ.get("EXTRACTOR_IMAP_HOST", "")
        port = int(os.environ.get("EXTRACTOR_IMAP_PORT", "993"))
        username = os.environ.get("EXTRACTOR_IMAP_USERNAME", "")
        password = os.environ.get("EXTRACTOR_IMAP_PASSWORD", "")
        if not host or not username or not password:
            raise ValueError(
                "Set EXTRACTOR_IMAP_HOST, EXTRACTOR_IMAP_USERNAME, "
                "EXTRACTOR_IMAP_PASSWORD to use the email connector."
            )
        mail = imaplib.IMAP4_SSL(host, port)
        mail.login(username, password)
        return mail

    def list(self, uri: str, glob_pattern: str | None = None) -> Iterator[SourceFile]:
        folder = os.environ.get("EXTRACTOR_IMAP_FOLDER", "INBOX")
        search = os.environ.get("EXTRACTOR_IMAP_SEARCH", "UNSEEN")
        mail = self._connect()
        try:
            mail.select(folder)
            _, msg_ids_raw = mail.search(None, search)
            msg_ids = (msg_ids_raw[0] or b"").split()
            for msg_id in msg_ids:
                _, msg_data = mail.fetch(msg_id, "(RFC822.HEADER)")
                raw_header = msg_data[0][1] if msg_data and msg_data[0] else b""
                msg = _email_lib.message_from_bytes(raw_header)
                message_id = msg.get("Message-ID", f"msg_{msg_id.decode()}")
                subject = msg.get("Subject", "(no subject)")
                date = msg.get("Date", "")

                # Fetch full message to enumerate attachments
                _, full_data = mail.fetch(msg_id, "(RFC822)")
                full_raw = full_data[0][1] if full_data and full_data[0] else b""
                full_msg = _email_lib.message_from_bytes(full_raw)

                for part in full_msg.walk():
                    if part.get_content_disposition() != "attachment":
                        continue
                    filename = part.get_filename() or "attachment"
                    if not _is_wanted_attachment(part):
                        continue
                    if glob_pattern:
                        import fnmatch

                        if not fnmatch.fnmatch(filename, glob_pattern):
                            continue
                    yield SourceFile(
                        uri=f"imap://{folder}/{message_id}/{filename}",
                        filename=filename,
                        etag=message_id,
                        last_modified=date,
                        extra={"msg_id": msg_id.decode(), "subject": subject},
                    )
        finally:
            with contextlib.suppress(Exception):
                mail.logout()

    @contextlib.contextmanager
    def fetch(self, source_file: SourceFile, tmp_dir: Path | None = None) -> Iterator[Path]:
        folder = os.environ.get("EXTRACTOR_IMAP_FOLDER", "INBOX")
        mail = self._connect()
        suffix = Path(source_file.filename).suffix or ".bin"
        tmp_path = Path(tempfile.mktemp(suffix=suffix, dir=tmp_dir))
        try:
            mail.select(folder)
            msg_id_str = source_file.extra.get("msg_id", "")
            _, full_data = mail.fetch(msg_id_str.encode(), "(RFC822)")
            full_raw = full_data[0][1] if full_data and full_data[0] else b""
            full_msg = _email_lib.message_from_bytes(full_raw)
            for part in full_msg.walk():
                if part.get_filename() == source_file.filename:
                    payload = part.get_payload(decode=True)
                    if payload:
                        tmp_path.write_bytes(payload)
                    break
            yield tmp_path
        finally:
            with contextlib.suppress(Exception):
                mail.logout()
            with contextlib.suppress(FileNotFoundError):
                tmp_path.unlink()


class ImapsSource(ImapSource):
    scheme = "imaps"


class EmailSource(ImapSource):
    scheme = "email"


register_source(ImapSource())
register_source(ImapsSource())
register_source(EmailSource())
