"""Fetch emails via Gmail IMAP4_SSL."""

import asyncio
import email
import imaplib
from email.header import decode_header
from html.parser import HTMLParser
from io import StringIO

from fliiq.runtime.google_auth import resolve_gmail_creds

IMAP_HOST = "imap.gmail.com"
IMAP_PORT = 993
MAX_BODY_CHARS = 4000

_FILTER_MAP = {
    "all": "ALL",
    "unread": "UNSEEN",
    "read": "SEEN",
}


class _HTMLStripper(HTMLParser):
    """Strip HTML tags, return plain text."""

    def __init__(self):
        super().__init__()
        self._text = StringIO()

    def handle_data(self, data):
        self._text.write(data)

    def get_text(self):
        return self._text.getvalue()


def _strip_html(html: str) -> str:
    stripper = _HTMLStripper()
    stripper.feed(html)
    return stripper.get_text()


def _decode_header_value(value: str | None) -> str:
    if not value:
        return ""
    parts = decode_header(value)
    decoded = []
    for part, charset in parts:
        if isinstance(part, bytes):
            decoded.append(part.decode(charset or "utf-8", errors="replace"))
        else:
            decoded.append(part)
    return " ".join(decoded)


def _extract_body(msg: email.message.Message) -> str:
    """Extract body: prefer text/plain, fallback to text/html stripped."""
    if msg.is_multipart():
        plain = None
        html = None
        for part in msg.walk():
            ct = part.get_content_type()
            if ct == "text/plain" and plain is None:
                payload = part.get_payload(decode=True)
                if payload:
                    charset = part.get_content_charset() or "utf-8"
                    plain = payload.decode(charset, errors="replace")
            elif ct == "text/html" and html is None:
                payload = part.get_payload(decode=True)
                if payload:
                    charset = part.get_content_charset() or "utf-8"
                    html = payload.decode(charset, errors="replace")
        if plain:
            return plain[:MAX_BODY_CHARS]
        if html:
            return _strip_html(html)[:MAX_BODY_CHARS]
        return ""
    else:
        payload = msg.get_payload(decode=True)
        if not payload:
            return ""
        charset = msg.get_content_charset() or "utf-8"
        text = payload.decode(charset, errors="replace")
        if msg.get_content_type() == "text/html":
            text = _strip_html(text)
        return text[:MAX_BODY_CHARS]


def _fetch_emails(
    address: str, auth_method: str, credential: str, max_results: int, imap_filter: str
) -> dict:
    """Blocking IMAP fetch — runs in threadpool."""
    try:
        conn = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT, timeout=30)
    except Exception as e:
        raise ValueError(f"IMAP connection failed: {e}")

    try:
        if auth_method == "oauth2":
            auth_string = f"user={address}\x01auth=Bearer {credential}\x01\x01"
            conn.authenticate("XOAUTH2", lambda _: auth_string.encode())
        else:
            conn.login(address, credential)
    except imaplib.IMAP4.error as e:
        conn.logout()
        if auth_method == "oauth2":
            raise ValueError(
                f"Gmail IMAP OAuth failed for {address}: {e}. "
                "Try 'fliiq google auth' to re-authorize."
            )
        raise ValueError(
            "Gmail IMAP authentication failed. Check GMAIL_ADDRESS and GMAIL_APP_PASSWORD. "
            "Ensure IMAP is enabled in Gmail settings."
        )

    try:
        conn.select("INBOX")
        _status, data = conn.uid("search", None, imap_filter)
        uid_list = data[0].split() if data[0] else []

        # Newest first (highest UIDs = most recent)
        uid_list = list(reversed(uid_list))

        has_more = len(uid_list) > max_results
        uids_to_fetch = uid_list[:max_results]

        emails = []
        for uid_bytes in uids_to_fetch:
            uid_str = uid_bytes.decode()
            _status, msg_data = conn.uid("fetch", uid_str, "(RFC822 FLAGS)")
            if not msg_data or not msg_data[0]:
                continue
            # Parse FLAGS from the response
            flags_line = msg_data[0][0] if isinstance(msg_data[0][0], bytes) else b""
            is_read = b"\\Seen" in flags_line
            raw = msg_data[0][1]
            msg = email.message_from_bytes(raw)
            sender = _decode_header_value(msg.get("From"))
            subject = _decode_header_value(msg.get("Subject"))
            body = _extract_body(msg)
            tagged_body = (
                f"<external_message source=\"email\" sender=\"{sender}\">\n"
                f"{body}\n"
                f"</external_message>"
            )
            emails.append({
                "id": uid_str,
                "from": sender,
                "subject": subject,
                "date": msg.get("Date", ""),
                "body": tagged_body,
                "read": is_read,
            })

        return {"emails": emails, "has_more": has_more}
    finally:
        try:
            conn.logout()
        except Exception:
            pass


async def handler(params: dict) -> dict:
    """Fetch emails via Gmail IMAP."""
    address, auth_method, credential = await resolve_gmail_creds()

    max_results = params.get("max_results", 10)
    filter_param = params.get("filter", "all")
    imap_filter = _FILTER_MAP.get(filter_param)
    if not imap_filter:
        raise ValueError(f"Invalid filter: '{filter_param}'. Use 'all', 'unread', or 'read'.")

    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(
        None, _fetch_emails, address, auth_method, credential, max_results, imap_filter
    )
