#!/usr/bin/env python3
import json
import os
import re
import subprocess
import sys
from datetime import datetime, date
from typing import Dict, List, Optional, Tuple

import requests

# ---------------- Config ----------------
GOG_ACCOUNT = "ahmad@bigalc.com"
CLICKUP_LIST_ID = "186337367"
CLICKUP_CREDENTIALS_FILE = "/Users/ahmad/.openclaw/workspace/clickup_credentials.md"
CLICKUP_BASE = "https://api.clickup.com/api/v2"
MAX_EMAIL_CONTEXT = 3


# ---------------- Utils -----------------
def eprint(*args):
    print(*args, file=sys.stderr)


def run(cmd: List[str]) -> str:
    res = subprocess.run(cmd, capture_output=True, text=True)
    if res.returncode != 0:
        raise RuntimeError(f"Command failed: {' '.join(cmd)}\n{res.stderr.strip()}")
    return res.stdout


def iso_to_dt(value: str) -> Optional[datetime]:
    if not value:
        return None
    try:
        return datetime.fromisoformat(value.replace("Z", "+00:00"))
    except Exception:
        return None


def get_event_start_end(event: Dict) -> Tuple[Optional[datetime], Optional[datetime], bool]:
    """Returns start_dt, end_dt, is_all_day"""
    s = event.get("start", {})
    e = event.get("end", {})

    if "dateTime" in s:
        start_dt = iso_to_dt(s.get("dateTime", ""))
        end_dt = iso_to_dt(e.get("dateTime", ""))
        return start_dt, end_dt, False

    if "date" in s:
        # all-day event date is YYYY-MM-DD
        start_dt = datetime.fromisoformat(s["date"] + "T00:00:00")
        end_dt = datetime.fromisoformat((e.get("date", s["date"])) + "T00:00:00")
        return start_dt, end_dt, True

    return None, None, False


def format_when(event: Dict) -> str:
    s = event.get("start", {})
    e = event.get("end", {})
    if "dateTime" in s:
        return f"{s.get('dateTime','?')} → {e.get('dateTime','?')}"
    return f"{s.get('date','?')} (all-day)"


def load_clickup_token() -> str:
    env_token = os.getenv("CLICKUP_API_TOKEN", "").strip()
    if env_token:
        return env_token

    if not os.path.exists(CLICKUP_CREDENTIALS_FILE):
        raise FileNotFoundError(f"Missing credentials file: {CLICKUP_CREDENTIALS_FILE}")

    content = open(CLICKUP_CREDENTIALS_FILE, "r", encoding="utf-8").read()
    m = re.search(r"\bpk_[A-Za-z0-9_]+\b", content)
    if not m:
        raise ValueError("No ClickUp token pattern found in credentials file")
    return m.group(0)


def clickup_request(method: str, endpoint: str, token: str, payload: Optional[Dict] = None, params: Optional[Dict] = None) -> Dict:
    url = f"{CLICKUP_BASE}/{endpoint.lstrip('/')}"
    headers = {"Authorization": token, "Content-Type": "application/json"}
    resp = requests.request(method, url, headers=headers, json=payload, params=params, timeout=30)
    if resp.status_code >= 400:
        raise RuntimeError(f"ClickUp {method} {endpoint} -> {resp.status_code}: {resp.text[:500]}")
    return resp.json() if resp.text.strip() else {}


def clickup_task_link(task_id: str) -> str:
    return f"https://app.clickup.com/t/{task_id}"


def get_existing_meeting_prep_tasks(token: str) -> Dict[str, Dict]:
    page = 0
    existing: Dict[str, Dict] = {}
    while True:
        data = clickup_request(
            "GET",
            f"list/{CLICKUP_LIST_ID}/task",
            token,
            params={"include_closed": "true", "page": str(page)},
        )
        tasks = data.get("tasks", [])
        if not tasks:
            break
        for t in tasks:
            name = t.get("name", "")
            if name.startswith("Meeting Prep:"):
                existing[name] = t
        page += 1
        if page > 20:
            break
    return existing


def fetch_todays_events() -> List[Dict]:
    raw = run(["gog", "calendar", "list", "--json"])
    data = json.loads(raw)
    events = data.get("events", [])

    today = date.today()
    out = []
    for ev in events:
        if ev.get("status") == "cancelled":
            continue

        start_dt, end_dt, _ = get_event_start_end(ev)
        if not start_dt:
            continue

        # Include if today is within event span
        end_cmp = end_dt or start_dt
        if start_dt.date() <= today <= end_cmp.date():
            out.append(ev)

    # Sort by start time
    out.sort(key=lambda ev: (get_event_start_end(ev)[0] or datetime.max))
    return out


def extract_keywords(summary: str) -> str:
    tokens = re.findall(r"[A-Za-z0-9]+", summary.lower())
    stop = {"the", "and", "for", "with", "from", "this", "that", "your", "are", "you", "weekly", "meeting"}
    tokens = [t for t in tokens if t not in stop and len(t) >= 4]
    # unique keep order
    uniq = []
    seen = set()
    for t in tokens:
        if t not in seen:
            uniq.append(t)
            seen.add(t)
    return " ".join(uniq[:4])


def fetch_email_context(summary: str) -> List[Dict]:
    keywords = extract_keywords(summary)
    if not keywords:
        return []

    query = f"{keywords} newer_than:21d"
    try:
        raw = run(["gog", "gmail", "search", "--account", GOG_ACCOUNT, query, "--json"])
        data = json.loads(raw)
        threads = data.get("threads", []) or []
        return threads[:MAX_EMAIL_CONTEXT]
    except Exception:
        return []


def priority_score(event: Dict, email_hits: int) -> Tuple[str, int]:
    summary = (event.get("summary") or "").lower()
    attendees = event.get("attendees") or []
    loc = event.get("location") or ""

    score = 40
    if any(k in summary for k in ["client", "review", "strategy", "proposal", "pitch", "fouad", "koala", "cryo"]):
        score += 25
    if attendees:
        score += min(20, len(attendees) * 5)
    if loc:
        score += 5
    score += min(10, email_hits * 3)

    if score >= 75:
        return "High", score
    if score >= 55:
        return "Medium", score
    return "Low", score


def build_prep_brief(event: Dict, email_ctx: List[Dict]) -> str:
    summary = event.get("summary", "(no title)")
    when = format_when(event)
    location = event.get("location") or "(not set)"
    organizer = (event.get("organizer") or {}).get("email", "(unknown)")
    attendees = event.get("attendees") or []

    attendee_lines = []
    for a in attendees[:8]:
        name = a.get("displayName") or a.get("email") or "Unknown"
        attendee_lines.append(f"- {name}")
    if not attendee_lines:
        attendee_lines = ["- (No attendee list available)"]

    prio_label, prio_score = priority_score(event, len(email_ctx))

    email_lines = []
    if email_ctx:
        for t in email_ctx:
            subj = t.get("subject", "(no subject)")
            sender = t.get("from", "(unknown)")
            d = t.get("date", "")
            email_lines.append(f"- {d} | {sender} | {subj}")
    else:
        email_lines = ["- No relevant recent inbox threads found."]

    desc = event.get("description") or ""
    desc_snippet = desc.strip().split("\n")[0][:220] if desc else "(no description)"

    checklist = [
        "- [ ] Define target outcome for this meeting",
        "- [ ] Review prior communication/context",
        "- [ ] Prepare 3 key talking points",
        "- [ ] Prepare 2 decisions/questions to close",
        "- [ ] Add post-meeting follow-up task",
    ]

    brief = [
        "## Meeting Prep Brief",
        f"**Priority:** {prio_label} ({prio_score}/100)",
        f"**When:** {when}",
        f"**Location:** {location}",
        f"**Organizer:** {organizer}",
        "",
        "### Objective (inferred)",
        f"- {summary}",
        f"- Context hint: {desc_snippet}",
        "",
        "### Attendees",
        *attendee_lines,
        "",
        "### Recent Related Emails (last ~21d)",
        *email_lines,
        "",
        "### Prep Checklist",
        *checklist,
    ]
    return "\n".join(brief)


def create_or_get_task(token: str, event: Dict, existing: Dict[str, Dict]) -> Tuple[str, str, bool]:
    name = f"Meeting Prep: {event.get('summary','(no title)')}"

    if name in existing:
        t = existing[name]
        return t.get("id", ""), clickup_task_link(t.get("id", "")), False

    email_ctx = fetch_email_context(event.get("summary", ""))
    desc = build_prep_brief(event, email_ctx)

    payload = {
        "name": name,
        "description": desc,
        "tags": ["Meeting Prep", "OpenClaw-Auto", "AI-Prep"],
    }
    created = clickup_request("POST", f"list/{CLICKUP_LIST_ID}/task", token, payload)
    task_id = created.get("id", "")
    return task_id, clickup_task_link(task_id), True


def main() -> int:
    print("Running Meeting Prep v2...")

    try:
        token = load_clickup_token()
        team_data = clickup_request("GET", "team", token)
        print(f"ClickUp auth OK (teams: {len(team_data.get('teams', []))}).")
    except Exception as e:
        eprint(f"Auth failed: {e}")
        return 1

    try:
        events = fetch_todays_events()
    except Exception as e:
        eprint(f"Failed to fetch calendar events: {e}")
        return 2

    if not events:
        print("No meetings found for today.")
        return 0

    try:
        existing = get_existing_meeting_prep_tasks(token)
    except Exception as e:
        eprint(f"Could not read existing ClickUp tasks: {e}")
        existing = {}

    created_count = 0
    reused_count = 0
    links: List[str] = []

    for ev in events:
        summary = ev.get("summary", "(no title)")
        if "cancelled" in summary.lower() or "[cancelled]" in summary.lower():
            continue
        try:
            task_id, link, created = create_or_get_task(token, ev, existing)
            if created:
                created_count += 1
                print(f"✅ Created: {summary} -> {link}")
            else:
                reused_count += 1
                print(f"↩️ Exists: {summary} -> {link}")
            if link:
                links.append(link)
        except Exception as e:
            eprint(f"❌ Failed for '{summary}': {e}")

    print("\nDaily Meeting Prep Summary")
    print(f"- meetings_today: {len(events)}")
    print(f"- created: {created_count}")
    print(f"- existing: {reused_count}")
    if links:
        print("- links:")
        for l in links:
            print(f"  - {l}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
