#!/usr/bin/env python3
import argparse
import email
import imaplib
import json
import os
import re
import sys
from email.header import decode_header, make_header
from email.utils import parseaddr
from pathlib import Path

ENV_PATH = Path.home() / ".config/openclaw/notion-imap.env"
SEEN_PATH = Path("/Users/openclaw/.openclaw/workspace/tmp/notion-mentions-seen.json")
ALERTS_PATH = Path("/Users/openclaw/.openclaw/workspace/tmp/notion-mentions-alerts.json")

NOTION_LINK_RE = re.compile(r"https?://[^\s>\"']*notion\.so[^\s>\"']*", re.I)


def load_env(path: Path):
    if not path.exists():
        raise RuntimeError("env_file_missing")
    env = {}
    for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if line.startswith("export "):
            line = line[len("export "):]
        if "=" not in line:
            continue
        k, v = line.split("=", 1)
        env[k.strip()] = v.strip().strip('"').strip("'")
    user = env.get("GMAIL_USER") or env.get("GMAIL_READ_USER")
    pwd = env.get("GMAIL_APP_PASSWORD")
    label = env.get("GMAIL_LABEL", "notion_mentions_dorian")
    if not user or not pwd:
        raise RuntimeError("missing_credentials")
    return user, pwd, label


def load_seen(path: Path):
    if not path.exists():
        return set()
    try:
        data = json.loads(path.read_text())
        return set(data.get("seen_uids", []))
    except Exception:
        return set()


def save_seen(path: Path, seen):
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps({"seen_uids": sorted(seen)}, indent=2))


def decode_header_text(v):
    if not v:
        return ""
    try:
        return str(make_header(decode_header(v)))
    except Exception:
        return v


def extract_body(msg):
    parts = []
    html_parts = []
    if msg.is_multipart():
        walk = msg.walk()
    else:
        walk = [msg]
    for part in walk:
        ctype = (part.get_content_type() or "").lower()
        disp = (part.get("Content-Disposition") or "").lower()
        if "attachment" in disp:
            continue
        payload = part.get_payload(decode=True)
        if payload is None:
            continue
        cs = part.get_content_charset() or "utf-8"
        try:
            text = payload.decode(cs, errors="replace")
        except Exception:
            text = payload.decode("utf-8", errors="replace")
        if ctype == "text/plain":
            parts.append(text)
        elif ctype == "text/html":
            html_parts.append(text)
    body = "\n".join(parts).strip()
    if not body and html_parts:
        h = "\n".join(html_parts)
        h = re.sub(r"<(script|style)[^>]*>.*?</\\1>", " ", h, flags=re.I | re.S)
        h = re.sub(r"<br\\s*/?>", "\n", h, flags=re.I)
        h = re.sub(r"</p\\s*>", "\n", h, flags=re.I)
        h = re.sub(r"<[^>]+>", " ", h)
        body = re.sub(r"\s+", " ", h)
    return body.strip()


def relevant(subject, frm, body):
    sender = parseaddr(frm)[1].lower()
    hay = f"{subject}\n{frm}\n{body}".lower()
    return ("notion.so" in sender or "notify@mail.notion.so" in hay) and any(
        k in hay for k in ["mentioned you", "commented", "replied", "@dorian", "notion"]
    )


def ensure_alerts_file(path: Path):
    path.parent.mkdir(parents=True, exist_ok=True)
    if not path.exists():
        path.write_text("[]\n")


def run_once():
    user, pwd, label = load_env(ENV_PATH)
    seen = load_seen(SEEN_PATH)
    ensure_alerts_file(ALERTS_PATH)

    m = imaplib.IMAP4_SSL("imap.gmail.com", 993)
    try:
        m.login(user, pwd)
    except Exception as e:
        raise RuntimeError(f"imap_login_failed:{type(e).__name__}")

    typ, _ = m.select(f'"{label}"')
    if typ != "OK":
        m.logout()
        raise RuntimeError("label_select_failed")

    typ, data = m.uid("search", None, "ALL")
    if typ != "OK":
        m.logout()
        raise RuntimeError("uid_search_failed")

    uids = [u for u in (data[0] or b"").split() if u]
    processed = 0
    relabel_failed = 0
    alerts = []

    for uid_b in uids:
        uid = uid_b.decode(errors="ignore")
        if uid in seen:
            continue

        typ, msg_data = m.uid("fetch", uid_b, "(RFC822)")
        if typ != "OK" or not msg_data or not isinstance(msg_data[0], tuple):
            continue

        msg = email.message_from_bytes(msg_data[0][1])
        subject = decode_header_text(msg.get("Subject", "")).strip()
        frm = decode_header_text(msg.get("From", "")).strip()
        body = extract_body(msg)

        if not relevant(subject, frm, body):
            continue

        link_m = NOTION_LINK_RE.search(body) or NOTION_LINK_RE.search(subject)
        link = link_m.group(0) if link_m else "N/A"
        snippet = re.sub(r"\s+", " ", body)[:240].strip() or "(no preview)"
        alerts.append({
            "uid": uid,
            "subject": subject or "(no subject)",
            "from": frm or "unknown",
            "snippet": snippet,
            "link": link,
        })

        # mark seen
        m.uid("store", uid_b, "+FLAGS", "(\\Seen)")

        # relabel via X-GM-LABELS
        ok_add = m.uid("store", uid_b, "+X-GM-LABELS", '("notion_mentions_processed")')[0] == "OK"
        ok_rm = m.uid("store", uid_b, "-X-GM-LABELS", f'("{label}")')[0] == "OK"
        if not (ok_add and ok_rm):
            relabel_failed += 1

        seen.add(uid)
        processed += 1

    m.close()
    m.logout()

    save_seen(SEEN_PATH, seen)
    ALERTS_PATH.write_text(json.dumps(alerts, indent=2))

    print(json.dumps({
        "processed": processed,
        "relabel_failed": relabel_failed,
        "alerts": len(alerts),
    }))

    if relabel_failed > 0:
        return 5
    return 0


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--once", action="store_true")
    _ = parser.parse_args()
    try:
        rc = run_once()
        sys.exit(rc)
    except RuntimeError as e:
        print(json.dumps({"error": str(e)}))
        sys.exit(2)
