#!/usr/bin/env python3
"""
Genesis Session Recovery Hook — SessionStart Hook
===================================================
Injects handoff context on session start to enable autonomous respawn.

Fires on: startup, resume, compact (all SessionStart events)

This is the CRITICAL MISSING PIECE of the auto-respawn system. It reads
the latest handoff files written by auto_respawn.py (at 65%/70% context)
and injects them as additionalContext so Claude immediately knows:
- What mission was active
- What agents were running
- Key decisions made
- Blockers and next actions

This enables the "zero re-orientation" respawn experience where Claude
resumes work autonomously without asking "what was I doing?"

Architecture:
  - Reads hive/progress/session_*_handoff.md (latest by mtime)
  - Reads hive/session_recovery/LATEST.md (heartbeat state)
  - Reads data/context_state/current.json (final session state)
  - Reads MEMORY.md war room status section (active mission)
  - Injects all as additionalContext
  - Clears respawn_requested.flag if present
  - Logs recovery event to observability

Author: Genesis Auto-Respawn System
"""

import sys
import json
import os
import re
from datetime import datetime, timezone
from pathlib import Path

# ============================================================================
# CONFIGURATION
# ============================================================================

GENESIS_ROOT = Path("/mnt/e/genesis-system")
HANDOFF_DIR = GENESIS_ROOT / "hive" / "progress"
RECOVERY_DIR = GENESIS_ROOT / "hive" / "session_recovery"
STATE_DIR = GENESIS_ROOT / "data" / "context_state"
EVENTS_DIR = GENESIS_ROOT / "data" / "observability"
RESPAWN_FLAG = STATE_DIR / "respawn_requested.flag"

# MEMORY.md — try multiple candidate paths (WSL username varies)
_MEMORY_CANDIDATES = [
    # Primary: in-repo boot context (always exists, maintained manually)
    GENESIS_ROOT / "data" / "context_state" / "BOOT_CONTEXT.md",
    # Claude Code project memory (path depends on WSL username)
    Path("/home/authentic88/.claude/projects/-mnt-e-genesis-system/memory/MEMORY.md"),
    Path("/home/p3/.claude/projects/-mnt-e-genesis-system/memory/MEMORY.md"),
    Path("/root/.claude/projects/-mnt-e-genesis-system/memory/MEMORY.md"),
    # Windows-side cache-redirect
    GENESIS_ROOT / ".cache-redirect" / ".claude" / "projects" / "E--genesis-system" / "memory" / "MEMORY.md",
]
MEMORY_FILE = next((p for p in _MEMORY_CANDIDATES if p.exists()), None)

# Limits for injected context (don't overwhelm Claude on startup)
MAX_HANDOFF_CHARS = 4000
MAX_RECOVERY_CHARS = 2000
MAX_WAR_ROOM_CHARS = 3000


# ============================================================================
# STATE READING
# ============================================================================

def get_latest_handoff() -> dict:
    """Read the most recent handoff file."""
    try:
        HANDOFF_DIR.mkdir(parents=True, exist_ok=True)
        handoffs = sorted(
            HANDOFF_DIR.glob("session_*_handoff.md"),
            key=lambda f: f.stat().st_mtime,
            reverse=True
        )
        if handoffs:
            latest = handoffs[0]
            content = latest.read_text(encoding="utf-8", errors="replace")
            session_match = re.search(r"session_(\d+)_handoff\.md", latest.name)
            session_num = session_match.group(1) if session_match else "?"
            return {
                "file": str(latest),
                "session_number": session_num,
                "content": content[:MAX_HANDOFF_CHARS],
                "truncated": len(content) > MAX_HANDOFF_CHARS,
                "found": True
            }
    except Exception:
        pass
    return {"found": False}


def get_recovery_state() -> dict:
    """Read LATEST.md recovery document (session heartbeat)."""
    try:
        latest = RECOVERY_DIR / "LATEST.md"
        if latest.exists():
            content = latest.read_text(encoding="utf-8", errors="replace")
            return {
                "file": str(latest),
                "content": content[:MAX_RECOVERY_CHARS],
                "truncated": len(content) > MAX_RECOVERY_CHARS,
                "found": True
            }
    except Exception:
        pass
    return {"found": False}


def get_previous_context_state() -> dict:
    """Read previous session's final context state."""
    try:
        state_file = STATE_DIR / "current.json"
        if state_file.exists():
            with open(state_file, "r") as f:
                state = json.load(f)
            return {
                "used_pct": state.get("used_percentage", "?"),
                "cost": state.get("cost_usd", 0),
                "session_id": state.get("session_id", "?"),
                "timestamp": state.get("timestamp", "?"),
                "found": True
            }
    except Exception:
        pass
    return {"found": False}


def extract_war_room_status() -> dict:
    """Extract WAR ROOM STATUS section from MEMORY.md / BOOT_CONTEXT.md."""
    try:
        if MEMORY_FILE is None or not MEMORY_FILE.exists():
            return {"found": False}

        content = MEMORY_FILE.read_text(encoding="utf-8", errors="replace")

        # Find WAR ROOM STATUS section
        match = re.search(
            r"## WAR ROOM STATUS.*?(?=\n## [A-Z]|\Z)",
            content,
            re.DOTALL | re.IGNORECASE
        )

        if match:
            war_room = match.group(0)
            return {
                "content": war_room[:MAX_WAR_ROOM_CHARS],
                "truncated": len(war_room) > MAX_WAR_ROOM_CHARS,
                "found": True
            }
    except Exception:
        pass
    return {"found": False}


def get_active_agents_from_observability() -> list:
    """Read recent agent spawn events from observability."""
    agents = []
    try:
        events_file = EVENTS_DIR / "events.jsonl"
        if events_file.exists():
            with open(events_file, "rb") as f:
                # Read last 50KB for recent events
                f.seek(0, 2)
                file_size = f.tell()
                read_size = min(file_size, 50000)
                f.seek(max(0, file_size - read_size))
                lines = f.read().decode("utf-8", errors="replace").strip().split("\n")

            # Find SubagentStart events
            for line in lines[-100:]:
                try:
                    event = json.loads(line.strip())
                    if event.get("event_type") == "SubagentStart":
                        agent_id = event.get("agent_id", "?")
                        task_id = event.get("task_id", "?")
                        timestamp = event.get("timestamp", "?")
                        agents.append({
                            "agent_id": agent_id,
                            "task_id": task_id,
                            "timestamp": timestamp
                        })
                except Exception:
                    continue
    except Exception:
        pass
    return agents[-5:]  # Last 5 agents


def clear_respawn_flag():
    """Remove respawn flag if it exists."""
    try:
        if RESPAWN_FLAG.exists():
            RESPAWN_FLAG.unlink()
            return True
    except Exception:
        pass
    return False


def log_recovery_event(source: str, handoff_found: bool, recovery_found: bool, war_room_found: bool):
    """Log recovery event to observability."""
    try:
        EVENTS_DIR.mkdir(parents=True, exist_ok=True)
        event = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "event_type": "session_recovery",
            "source": source,
            "handoff_found": handoff_found,
            "recovery_found": recovery_found,
            "war_room_found": war_room_found,
        }
        events_file = EVENTS_DIR / "events.jsonl"
        with open(events_file, "a") as f:
            f.write(json.dumps(event) + "\n")
    except Exception:
        pass


# ============================================================================
# RECOVERY CONTEXT BUILDER
# ============================================================================

def build_recovery_context(hook_input: dict) -> dict:
    """
    Build the recovery context to inject into Claude's session.

    Returns a dict with:
      - additionalContext: The recovery context string
      - metadata: Details about what was found
    """
    source = hook_input.get("source", "unknown")
    session_id = hook_input.get("session_id", "?")

    # Gather all recovery data
    handoff = get_latest_handoff()
    recovery = get_recovery_state()
    prev_ctx = get_previous_context_state()
    war_room = extract_war_room_status()
    agents = get_active_agents_from_observability()

    # Check if we have any recovery data at all
    has_data = (handoff.get("found") or recovery.get("found") or
                war_room.get("found") or len(agents) > 0)

    if not has_data:
        # No recovery data available - session is likely brand new
        return {
            "additionalContext": None,
            "metadata": {
                "has_recovery_data": False,
                "source": source
            }
        }

    # Build context parts
    parts = []

    # Header
    parts.append(f"═══════════════════════════════════════════════════════════════")
    parts.append(f"SESSION RECOVERY ACTIVATED")
    parts.append(f"═══════════════════════════════════════════════════════════════")
    parts.append(f"Source: {source}")
    parts.append(f"Session ID: {session_id}")
    parts.append("")

    # Previous session context state
    if prev_ctx.get("found"):
        parts.append(f"Previous session: {prev_ctx['used_pct']}% context used, ${prev_ctx['cost']:.2f} cost")
        parts.append("")

    # WAR ROOM STATUS (highest priority - active mission)
    if war_room.get("found"):
        parts.append(f"═══ WAR ROOM STATUS (from MEMORY.md) ═══")
        parts.append(war_room["content"])
        if war_room.get("truncated"):
            parts.append("(... truncated for brevity ...)")
        parts.append("")

    # Latest handoff file
    if handoff.get("found"):
        parts.append(f"═══ LATEST HANDOFF (session {handoff['session_number']}) ═══")
        parts.append(handoff["content"])
        if handoff.get("truncated"):
            parts.append("(... truncated for brevity ...)")
        parts.append("")

    # Recovery heartbeat state
    if recovery.get("found"):
        parts.append(f"═══ RECOVERY STATE (heartbeat) ═══")
        parts.append(recovery["content"])
        if recovery.get("truncated"):
            parts.append("(... truncated for brevity ...)")
        parts.append("")

    # Active agents
    if agents:
        parts.append(f"═══ RECENTLY SPAWNED AGENTS ═══")
        for agent in agents:
            parts.append(f"- Agent {agent['agent_id'][:8]} (task {agent['task_id'][:8]}) @ {agent['timestamp']}")
        parts.append("")
        parts.append("Check agent output files: /tmp/claude-1000/-mnt-e-genesis-system/tasks/<agent_id>.output")
        parts.append("")

    # Instructions
    parts.append(f"═══════════════════════════════════════════════════════════════")
    parts.append(f"RECOVERY INSTRUCTIONS")
    parts.append(f"═══════════════════════════════════════════════════════════════")
    parts.append("")
    parts.append("YOU HAVE RESPAWNED. This is AUTONOMOUS RECOVERY.")
    parts.append("")
    parts.append("DO NOT ask 'what's the mission?' or 'did you retain memory?'")
    parts.append("DO NOT request re-orientation from Kinan.")
    parts.append("")
    parts.append("IMMEDIATELY:")
    parts.append("1. Resume the active mission from WAR ROOM STATUS above")
    parts.append("2. Check on any running agents (if listed)")
    parts.append("3. Continue the highest-priority work")
    parts.append("4. THEN greet Kinan with a status update of what you are ALREADY doing")
    parts.append("")
    parts.append("CORRECT greeting example:")
    parts.append(f'  "Session 9 online. Resumed building Memory Vault backend.')
    parts.append(f'   Agent {agents[0]["agent_id"][:8] if agents else "acd355e"} is writing auth.py and billing.py.')
    parts.append(f'   Next: deploy landing page with new pricing."')
    parts.append("")
    parts.append("WRONG greeting example:")
    parts.append('  "What should I work on?" or "Do you have memory of our last session?"')
    parts.append("")
    parts.append("ZERO RE-ORIENTATION. PURE CONTINUITY.")
    parts.append("═══════════════════════════════════════════════════════════════")

    context = "\n".join(parts)

    return {
        "additionalContext": context,
        "metadata": {
            "has_recovery_data": True,
            "source": source,
            "handoff_found": handoff.get("found"),
            "recovery_found": recovery.get("found"),
            "war_room_found": war_room.get("found"),
            "agents_count": len(agents),
        }
    }


# ============================================================================
# MAIN HOOK LOGIC
# ============================================================================

def main():
    try:
        hook_input = json.loads(sys.stdin.read())
    except (json.JSONDecodeError, Exception):
        hook_input = {}

    source = hook_input.get("source", "unknown")

    # Build recovery context
    recovery = build_recovery_context(hook_input)

    # Clear respawn flag if present
    flag_cleared = clear_respawn_flag()

    # Log recovery event
    metadata = recovery.get("metadata", {})
    log_recovery_event(
        source=source,
        handoff_found=metadata.get("handoff_found", False),
        recovery_found=metadata.get("recovery_found", False),
        war_room_found=metadata.get("war_room_found", False),
    )

    # If no recovery data, return empty response
    if not metadata.get("has_recovery_data"):
        print(json.dumps({}))
        return

    # Return recovery context with correct hookSpecificOutput wrapper
    result = {
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": recovery["additionalContext"]
        }
    }

    print(json.dumps(result))


if __name__ == "__main__":
    main()
