#!/usr/bin/env python3
"""
Extract lean conversation transcripts from session .jsonl files.
Only processes sessions with real human interactions — skips cron jobs,
sub-agent dispatches, and heartbeat sessions.

Session format: each line is {"type": "message", "message": {"role": ..., "content": ...}}
"""

import json, os, glob, time, argparse, re
from pathlib import Path
from datetime import datetime, timezone, timedelta

SESSIONS_DIR = os.path.expanduser("~/.openclaw/agents/main/sessions")
DEFAULT_OUTPUT_DIR = "/tmp/daily-transcripts"

# Skip sessions whose first user message matches these patterns
# Truncate very large sessions to this many chars (take the tail — recent activity)
MAX_OUTPUT_CHARS = 40000

# Skip cron sessions whose first user message matches these patterns
CRON_PATTERNS = [
    r'^\[cron:',                         # Cron job dispatches
    r'^Run the nightly',                 # Sleep cycle itself
    r'^Read HEARTBEAT\.md',              # Heartbeat polls
    r'^\[System Message\]',             # Internal system messages
    r'^\[.*Subagent.*\]',               # Sub-agent context headers
    r'^\[.*subagent.*\]',
    r'You are running as a subagent',    # Sub-agent boot messages
]

# Skip sessions smaller than this (bytes) — sub-agents doing one task
MIN_FILE_BYTES = 5000


def extract_text_from_content(content):
    if isinstance(content, str):
        return content.strip()
    if isinstance(content, list):
        texts = []
        for block in content:
            if isinstance(block, dict) and block.get("type") == "text":
                texts.append(block.get("text", ""))
            elif isinstance(block, str):
                texts.append(block)
        return "\n".join(texts).strip()
    return ""


def clean_user_message(text):
    """Strip OpenClaw metadata envelope, keep actual content."""
    text = re.sub(r'Conversation info \(untrusted metadata\):\s*```json.*?```\s*', '', text, flags=re.DOTALL)
    text = re.sub(r'Sender \(untrusted metadata\):\s*```json.*?```\s*', '', text, flags=re.DOTALL)
    text = re.sub(r'Replied message \(untrusted[^)]*\):\s*```json.*?```\s*', '', text, flags=re.DOTALL)
    text = re.sub(r'\[Queued messages while agent was busy\]\s*---\s*', '', text)
    text = re.sub(r'Queued #\d+\s*', '', text)
    text = re.sub(r'\[Queue overflow\][^\n]*\n?', '', text)
    text = re.sub(r'System: \[[^\]]+\][^\n]*\n?', '', text)
    text = re.sub(r'\n{3,}', '\n\n', text)
    return text.strip()


def extract_sender_from_content(content):
    """Try to pull sender label from OpenClaw metadata block."""
    text = extract_text_from_content(content)
    match = re.search(r'"label":\s*"([^"]+)"', text)
    return match.group(1) if match else None


def is_cron_or_agent_session(first_user_text):
    """Return True if this looks like a cron dispatch or sub-agent task."""
    for pat in CRON_PATTERNS:
        if re.match(pat, first_user_text, re.IGNORECASE):
            return True
    return False


def has_human_sender(content):
    """Return True if message has a real human sender in metadata."""
    text = extract_text_from_content(content)
    sender_match = re.search(r'"sender_id":\s*"([^"]+)"', text)
    return bool(sender_match)


def process_session(jsonl_path, output_dir, cutoff_ts):
    """Process a single session file. Returns (output_path | None, reason)."""
    if os.path.getsize(jsonl_path) < MIN_FILE_BYTES:
        return None, "tiny"

    messages = []
    session_last_ts = None
    first_user_text = None
    has_human = False

    with open(jsonl_path, 'r', errors='replace') as f:
        for raw in f:
            raw = raw.strip()
            if not raw:
                continue
            try:
                entry = json.loads(raw)
            except json.JSONDecodeError:
                continue

            # Track last timestamp in session
            ts_str = entry.get("timestamp")
            if ts_str:
                try:
                    ts = datetime.fromisoformat(ts_str.replace('Z', '+00:00'))
                    if session_last_ts is None or ts > session_last_ts:
                        session_last_ts = ts
                except Exception:
                    pass

            if entry.get("type") != "message":
                continue

            msg = entry.get("message", {})
            role = msg.get("role", "")
            if role not in ("user", "assistant"):
                continue

            content = msg.get("content", "")
            text = extract_text_from_content(content)
            if not text:
                continue

            if role == "user":
                if has_human_sender(content):
                    has_human = True
                if first_user_text is None:
                    first_user_text = text
                sender = extract_sender_from_content(content) or "User"
                text = clean_user_message(text)
                if not text or text in ("HEARTBEAT_OK", "NO_REPLY"):
                    continue
                messages.append(f"[{sender}]: {text}")
            else:
                if text in ("HEARTBEAT_OK", "NO_REPLY"):
                    continue
                messages.append(f"[Assistant]: {text}")

    # Skip if no activity in the target window
    if session_last_ts is None or session_last_ts < cutoff_ts:
        return None, "old"

    # Skip cron/sub-agent sessions
    if first_user_text and is_cron_or_agent_session(first_user_text):
        return None, "cron"

    # Must have a human sender OR be a large file (main session catchall)
    file_size = os.path.getsize(jsonl_path)
    if not has_human and file_size < 50000:
        return None, "no-human"

    if not messages:
        return None, "empty"

    session_name = Path(jsonl_path).stem
    output_path = os.path.join(output_dir, f"{session_name}.txt")
    full_text = "\n\n".join(messages)
    # Truncate large sessions — take the tail (most recent activity)
    if len(full_text) > MAX_OUTPUT_CHARS:
        full_text = "[...truncated — showing most recent activity only...]\n\n" + full_text[-MAX_OUTPUT_CHARS:]
    with open(output_path, 'w') as f:
        f.write(full_text)
    return output_path, "ok"


def main():
    parser = argparse.ArgumentParser(description="Extract daily conversation transcripts")
    parser.add_argument("--hours", type=int, default=24, help="Hours to look back")
    parser.add_argument("--output-dir", default=DEFAULT_OUTPUT_DIR)
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)
    cutoff_ts = datetime.now(timezone.utc) - timedelta(hours=args.hours)

    jsonl_files = glob.glob(os.path.join(SESSIONS_DIR, "*.jsonl"))

    stats = {"ok": 0, "tiny": 0, "old": 0, "cron": 0, "no-human": 0, "empty": 0}

    for path in jsonl_files:
        if '.deleted.' in path or path.endswith('.lock'):
            continue
        _, reason = process_session(path, args.output_dir, cutoff_ts)
        stats[reason] = stats.get(reason, 0) + 1

    print(f"Extracted {stats['ok']} sessions to {args.output_dir}")
    print(f"Skipped: {stats['old']} old, {stats['tiny']} tiny, {stats['cron']} cron/agent, "
          f"{stats['no-human']} no-human, {stats['empty']} empty")
    return stats['ok']


if __name__ == "__main__":
    main()
