#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from dataclasses import asdict, dataclass
from datetime import date, datetime
from pathlib import Path
from typing import Dict, List, Optional
from urllib import error, request

NOTION_VERSION = "2025-09-03"
DEFAULT_NOTION_KEY_PATH = "/Users/openclaw/.config/notion/api_key"
PIPELINE_DATA_SOURCE_ID = "cbde3032-5e2c-4557-8a00-cd2d14d4ab83"

STATUS_OUTREACH_SENT = "Outreach Sent"
STATUS_APPLIED = "Applied"
STATUS_FOLLOW_UP_DUE = "Follow-Up Due"


@dataclass
class FollowupRecord:
    page_id: str
    page_url: str
    name: str
    role_title: str
    company: str
    status: Optional[str]
    strategy: Optional[str]
    follow_up_due: Optional[str]
    last_action_date: Optional[str]
    connection_draft: str
    cover_draft: str
    notes: str
    recruiter_name: str
    source: Optional[str]


@dataclass
class FollowupPlan:
    page_id: str
    role_title: str
    company: str
    status: Optional[str]
    strategy: Optional[str]
    follow_up_due: Optional[str]
    days_since_last_action: Optional[int]
    action_taken: str
    suggested_follow_up: str
    draft_preview: str
    digest_line: str


def notion_request(method: str, url: str, payload: Optional[dict] = None) -> dict:
    notion_key = Path(DEFAULT_NOTION_KEY_PATH).read_text(encoding="utf-8").strip()
    headers = {
        "Authorization": f"Bearer {notion_key}",
        "Notion-Version": NOTION_VERSION,
        "Content-Type": "application/json",
    }
    body = None if payload is None else json.dumps(payload).encode("utf-8")
    req = request.Request(url, data=body, headers=headers, method=method)
    try:
        with request.urlopen(req, timeout=20) as resp:
            text = resp.read().decode("utf-8")
            return json.loads(text) if text else {}
    except error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"notion_http_error:{exc.code}:{detail}") from exc


def _plain_text(items: List[dict]) -> str:
    return "".join(item.get("plain_text", "") for item in items or [])


def _date_start(prop: dict) -> Optional[str]:
    value = prop.get("date")
    if not value:
        return None
    return value.get("start")


def page_to_record(page: dict) -> FollowupRecord:
    props = page["properties"]
    return FollowupRecord(
        page_id=page["id"],
        page_url=page["url"],
        name=_plain_text(props["Name"].get("title", [])),
        role_title=_plain_text(props["Role Title"].get("rich_text", [])),
        company=_plain_text(props["Company"].get("rich_text", [])),
        status=(props["Status"].get("select") or {}).get("name"),
        strategy=(props["Strategy"].get("select") or {}).get("name"),
        follow_up_due=_date_start(props["Follow-Up Due"]),
        last_action_date=_date_start(props["Last Action Date"]),
        connection_draft=_plain_text(props["Connection Draft"].get("rich_text", [])),
        cover_draft=_plain_text(props["Cover Draft"].get("rich_text", [])),
        notes=_plain_text(props["Notes"].get("rich_text", [])),
        recruiter_name=_plain_text(props["Recruiter Name"].get("rich_text", [])),
        source=(props["Source"].get("select") or {}).get("name"),
    )


def fetch_page(page_id: str) -> FollowupRecord:
    page = notion_request("GET", f"https://api.notion.com/v1/pages/{page_id}")
    return page_to_record(page)


def query_due_records(today: str) -> List[FollowupRecord]:
    records: List[FollowupRecord] = []
    cursor: Optional[str] = None
    while True:
        payload: Dict[str, object] = {
            "page_size": 100,
            "filter": {
                "and": [
                    {
                        "or": [
                            {"property": "Status", "select": {"equals": STATUS_OUTREACH_SENT}},
                            {"property": "Status", "select": {"equals": STATUS_APPLIED}},
                        ]
                    },
                    {"property": "Follow-Up Due", "date": {"on_or_before": today}},
                ]
            },
        }
        if cursor:
            payload["start_cursor"] = cursor
        data = notion_request(
            "POST",
            f"https://api.notion.com/v1/data_sources/{PIPELINE_DATA_SOURCE_ID}/query",
            payload,
        )
        records.extend(page_to_record(page) for page in data.get("results", []))
        if not data.get("has_more"):
            return records
        cursor = data.get("next_cursor")


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


def _days_since(last_action_date: Optional[str], today: date) -> Optional[int]:
    last_action = _parse_iso_date(last_action_date)
    if not last_action:
        return None
    return (today - last_action).days


def _action_taken(record: FollowupRecord) -> str:
    if record.strategy == "Connect First":
        return "Connection request sent"
    if record.strategy == "Recruiter Response":
        return "Recruiter reply sent"
    if record.strategy == "Direct Apply":
        return "Application submitted"
    return f"Outreach action logged under strategy {record.strategy or 'unknown'}"


def _suggested_follow_up(record: FollowupRecord) -> str:
    if record.strategy == "Connect First":
        return "Check whether the request was accepted; if yes, send the next-stage message rather than a repeat connection note."
    if record.strategy == "Recruiter Response":
        return "Send a concise recruiter follow-up that reaffirms interest and asks whether the role is still active."
    if record.strategy == "Direct Apply":
        return "Send a short follow-up note that references the submitted application and reiterates the operational fit."
    return "Manual review recommended before any automated follow-up."


def _draft_preview(record: FollowupRecord) -> str:
    role = record.role_title or record.name
    company = record.company or "the company"
    if record.strategy == "Recruiter Response":
        recipient = record.recruiter_name or "there"
        return (
            f"Hi {recipient}, I wanted to follow up on the {role} opportunity at {company}. "
            "I'm still interested and would welcome any update on scope, timeline, or next steps."
        )
    return (
        f"Following up on my prior outreach regarding the {role} role at {company}. "
        "The opportunity still looks closely aligned with my background, and I'd be glad to continue the conversation."
    )


def build_plan(record: FollowupRecord, today: date) -> FollowupPlan:
    role = record.role_title or record.name
    company = record.company or "Unknown company"
    days_since_last_action = _days_since(record.last_action_date, today)
    action_taken = _action_taken(record)
    suggested_follow_up = _suggested_follow_up(record)
    draft_preview = _draft_preview(record)
    digest_line = (
        f"{role} | {company} | status {record.status} | due {record.follow_up_due} | "
        f"{days_since_last_action if days_since_last_action is not None else 'n/a'} days since last action"
    )
    return FollowupPlan(
        page_id=record.page_id,
        role_title=role,
        company=company,
        status=record.status,
        strategy=record.strategy,
        follow_up_due=record.follow_up_due,
        days_since_last_action=days_since_last_action,
        action_taken=action_taken,
        suggested_follow_up=suggested_follow_up,
        draft_preview=draft_preview,
        digest_line=digest_line,
    )


def is_due_record(record: FollowupRecord, today: date) -> bool:
    if record.status not in {STATUS_OUTREACH_SENT, STATUS_APPLIED}:
        return False
    due = _parse_iso_date(record.follow_up_due)
    if not due:
        return False
    return due <= today


def build_digest(plans: List[FollowupPlan]) -> str:
    lines = ["Follow-up items ready for review:"]
    for idx, plan in enumerate(plans, start=1):
        lines.append(f"{idx}. {plan.digest_line}")
        lines.append(f"   Suggested follow-up: {plan.suggested_follow_up}")
    lines.append("Reply in natural language with which follow-ups to draft or hold.")
    return "\n".join(lines)


def mark_due_status(page_id: str, today: str) -> None:
    notion_request(
        "PATCH",
        f"https://api.notion.com/v1/pages/{page_id}",
        {
            "properties": {
                "Status": {"select": {"name": STATUS_FOLLOW_UP_DUE}},
                "Last Updated": {"date": {"start": today}},
            }
        },
    )


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--once", action="store_true", help="Run one follow-up scan.")
    parser.add_argument("--page-id", help="Limit to a single page for validation.")
    parser.add_argument("--mark-due", action="store_true", help="Mark due records as Follow-Up Due after detection.")
    args = parser.parse_args()

    if not args.once:
        raise SystemExit("Use --once for a one-shot follow-up scan.")

    today = date.today()
    if args.page_id:
        candidate = fetch_page(args.page_id)
        records = [candidate] if is_due_record(candidate, today) else []
    else:
        records = query_due_records(today.isoformat())
    plans = [build_plan(record, today) for record in records]

    if args.mark_due:
        for record in records:
            if record.status in {STATUS_OUTREACH_SENT, STATUS_APPLIED} and record.follow_up_due and record.follow_up_due <= today.isoformat():
                mark_due_status(record.page_id, today.isoformat())

    output = {
        "mode": "mark-due" if args.mark_due else "dry-run",
        "eligible_count": len(plans),
        "plans": [asdict(plan) for plan in plans],
        "digest_preview": build_digest(plans) if plans else "No follow-up items are due today.",
    }
    print(json.dumps(output, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
