#!/usr/bin/env python3
"""
RALPH WIGGUM LOOP (RWL) - Anti-Stall Autonomous Execution Engine
================================================================
Prevents development stalls by automatically:
1. Detecting blocked tasks
2. Trying alternative approaches
3. Escalating only when all options exhausted
4. Continuing execution without human intervention

Based on Ralph Wiggum methodology from Genesis-Ralph Synthesis.
"""

import json
import time
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional, Callable
from dataclasses import dataclass, field
from enum import Enum
import traceback

class TaskStatus(Enum):
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    BLOCKED = "blocked"
    COMPLETED = "completed"
    FAILED = "failed"
    SKIPPED = "skipped"

class EscalationLevel(Enum):
    NONE = 0           # No escalation needed
    RETRY = 1          # Retry with same approach
    ALTERNATIVE = 2    # Try alternative approach
    WORKAROUND = 3     # Use workaround
    SKIP = 4           # Skip and continue
    ESCALATE = 5       # Escalate to human

@dataclass
class Task:
    id: str
    description: str
    status: TaskStatus = TaskStatus.PENDING
    attempts: int = 0
    max_attempts: int = 3
    error: Optional[str] = None
    result: Optional[Any] = None
    alternatives: List[Callable] = field(default_factory=list)
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    completed_at: Optional[str] = None

@dataclass
class RalphState:
    """Persistent state for the Ralph Loop."""
    session_id: str
    tasks: List[Task] = field(default_factory=list)
    completed: int = 0
    failed: int = 0
    skipped: int = 0
    escalations: List[Dict] = field(default_factory=list)
    started_at: str = field(default_factory=lambda: datetime.now().isoformat())

    def to_dict(self) -> Dict:
        return {
            "session_id": self.session_id,
            "completed": self.completed,
            "failed": self.failed,
            "skipped": self.skipped,
            "total_tasks": len(self.tasks),
            "escalations": len(self.escalations),
            "started_at": self.started_at
        }

class RalphLoop:
    """
    The Ralph Wiggum Loop - Autonomous Anti-Stall Engine

    Principles:
    1. NEVER ask for permission on already-approved tasks
    2. Try alternatives before failing
    3. Skip and continue rather than blocking
    4. Report after, not before
    5. Self-heal when possible
    """

    def __init__(self, session_name: str = "default"):
        self.state = RalphState(session_id=f"rwl_{session_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
        self.state_file = Path("/mnt/e/genesis-system/data/ralph_state.json")
        self.log_file = Path("/mnt/e/genesis-system/data/ralph_log.jsonl")
        self.max_consecutive_failures = 3
        self.consecutive_failures = 0

    def add_task(self, task_id: str, description: str,
                 primary: Callable,
                 alternatives: List[Callable] = None) -> None:
        """Add a task with optional alternative approaches."""
        task = Task(
            id=task_id,
            description=description,
            alternatives=alternatives or []
        )
        self.state.tasks.append(task)
        self._log("task_added", {"task_id": task_id, "description": description})

    def run(self) -> Dict[str, Any]:
        """
        Execute the Ralph Loop.
        Returns summary of execution.
        """
        self._log("loop_started", {"total_tasks": len(self.state.tasks)})

        for task in self.state.tasks:
            if task.status == TaskStatus.COMPLETED:
                continue

            result = self._execute_task(task)

            if result["success"]:
                self.consecutive_failures = 0
            else:
                self.consecutive_failures += 1

            # Circuit breaker - too many consecutive failures
            if self.consecutive_failures >= self.max_consecutive_failures:
                self._log("circuit_breaker_triggered", {
                    "consecutive_failures": self.consecutive_failures
                })
                break

        summary = self._generate_summary()
        self._save_state()
        self._log("loop_completed", summary)

        return summary

    def _execute_task(self, task: Task) -> Dict[str, Any]:
        """Execute a single task with retry and alternative logic."""
        task.status = TaskStatus.IN_PROGRESS
        task.attempts += 1

        self._log("task_started", {
            "task_id": task.id,
            "attempt": task.attempts
        })

        # Try primary approach
        try:
            # Tasks are defined as callables - execute them
            if task.alternatives:
                # Try primary first (index 0)
                result = task.alternatives[0]()
                task.result = result
                task.status = TaskStatus.COMPLETED
                task.completed_at = datetime.now().isoformat()
                self.state.completed += 1

                self._log("task_completed", {
                    "task_id": task.id,
                    "result": str(result)[:200]
                })

                return {"success": True, "result": result}

        except Exception as e:
            error_msg = str(e)
            task.error = error_msg

            self._log("task_error", {
                "task_id": task.id,
                "error": error_msg[:200],
                "attempt": task.attempts
            })

            # Try alternatives
            if len(task.alternatives) > 1:
                return self._try_alternatives(task)

            # Decide escalation level
            escalation = self._determine_escalation(task)
            return self._handle_escalation(task, escalation)

        return {"success": False, "error": "Unknown error"}

    def _try_alternatives(self, task: Task) -> Dict[str, Any]:
        """Try alternative approaches for a blocked task."""
        for i, alternative in enumerate(task.alternatives[1:], start=2):
            self._log("trying_alternative", {
                "task_id": task.id,
                "alternative_number": i
            })

            try:
                result = alternative()
                task.result = result
                task.status = TaskStatus.COMPLETED
                task.completed_at = datetime.now().isoformat()
                self.state.completed += 1

                self._log("alternative_succeeded", {
                    "task_id": task.id,
                    "alternative_number": i
                })

                return {"success": True, "result": result, "used_alternative": i}

            except Exception as e:
                self._log("alternative_failed", {
                    "task_id": task.id,
                    "alternative_number": i,
                    "error": str(e)[:100]
                })
                continue

        # All alternatives failed
        return self._handle_escalation(task, EscalationLevel.SKIP)

    def _determine_escalation(self, task: Task) -> EscalationLevel:
        """Determine appropriate escalation level based on task state."""
        if task.attempts < task.max_attempts:
            return EscalationLevel.RETRY

        if task.alternatives and len(task.alternatives) > 1:
            return EscalationLevel.ALTERNATIVE

        # Default: skip and continue (anti-stall principle)
        return EscalationLevel.SKIP

    def _handle_escalation(self, task: Task, level: EscalationLevel) -> Dict[str, Any]:
        """Handle task escalation based on level."""

        if level == EscalationLevel.RETRY:
            # Will retry on next loop iteration
            task.status = TaskStatus.PENDING
            return {"success": False, "action": "retry_scheduled"}

        elif level == EscalationLevel.SKIP:
            # Skip and continue - core anti-stall behavior
            task.status = TaskStatus.SKIPPED
            self.state.skipped += 1

            self._log("task_skipped", {
                "task_id": task.id,
                "reason": "All approaches exhausted - continuing execution"
            })

            return {"success": False, "action": "skipped", "reason": task.error}

        elif level == EscalationLevel.ESCALATE:
            # Only escalate for truly critical failures
            task.status = TaskStatus.FAILED
            self.state.failed += 1

            self.state.escalations.append({
                "task_id": task.id,
                "error": task.error,
                "timestamp": datetime.now().isoformat()
            })

            return {"success": False, "action": "escalated", "error": task.error}

        return {"success": False, "action": "unknown"}

    def _generate_summary(self) -> Dict[str, Any]:
        """Generate execution summary."""
        return {
            "session_id": self.state.session_id,
            "total_tasks": len(self.state.tasks),
            "completed": self.state.completed,
            "failed": self.state.failed,
            "skipped": self.state.skipped,
            "success_rate": self.state.completed / max(len(self.state.tasks), 1) * 100,
            "escalations": len(self.state.escalations),
            "timestamp": datetime.now().isoformat()
        }

    def _save_state(self) -> None:
        """Save state to disk for persistence."""
        try:
            state_data = {
                "session_id": self.state.session_id,
                "completed": self.state.completed,
                "failed": self.state.failed,
                "skipped": self.state.skipped,
                "started_at": self.state.started_at,
                "tasks": [
                    {
                        "id": t.id,
                        "description": t.description,
                        "status": t.status.value,
                        "attempts": t.attempts,
                        "error": t.error
                    }
                    for t in self.state.tasks
                ]
            }

            self.state_file.parent.mkdir(parents=True, exist_ok=True)
            with open(self.state_file, 'w') as f:
                json.dump(state_data, f, indent=2)
        except Exception as e:
            self._log("state_save_error", {"error": str(e)})

    def _log(self, event: str, data: Dict) -> None:
        """Log event to JSONL file."""
        try:
            log_entry = {
                "timestamp": datetime.now().isoformat(),
                "session_id": self.state.session_id,
                "event": event,
                "data": data
            }

            self.log_file.parent.mkdir(parents=True, exist_ok=True)
            with open(self.log_file, 'a') as f:
                f.write(json.dumps(log_entry) + "\n")
        except Exception:
            pass  # Logging should never block execution


class YouTubeTranscriptTask:
    """Example task implementation for YouTube transcript extraction."""

    def __init__(self, video_ids: List[str], output_dir: Path):
        self.video_ids = video_ids
        self.output_dir = output_dir

    def approach_api(self) -> Dict:
        """Primary approach: Use youtube-transcript-api."""
        from youtube_transcript_api import YouTubeTranscriptApi
        api = YouTubeTranscriptApi()

        results = {"success": [], "failed": []}
        for vid in self.video_ids:
            try:
                transcript = api.fetch(vid)
                results["success"].append(vid)
            except:
                results["failed"].append(vid)

        return results

    def approach_ytdlp(self) -> Dict:
        """Alternative: Use yt-dlp for subtitle extraction."""
        results = {"success": [], "failed": []}

        for vid in self.video_ids:
            try:
                cmd = [
                    "yt-dlp", "--skip-download", "--write-auto-sub",
                    "--sub-lang", "en", "-o", str(self.output_dir / vid),
                    f"https://youtube.com/watch?v={vid}"
                ]
                subprocess.run(cmd, capture_output=True, timeout=60)
                results["success"].append(vid)
            except:
                results["failed"].append(vid)

        return results

    def approach_whisper(self) -> Dict:
        """Fallback: Download audio and use Whisper for transcription."""
        # This would download audio and transcribe with Whisper
        # Placeholder for now
        return {"success": [], "failed": self.video_ids, "reason": "whisper_not_implemented"}


def demo():
    """Demonstrate the Ralph Loop."""
    print("=" * 60)
    print("RALPH WIGGUM LOOP - Anti-Stall Demo")
    print("=" * 60)

    ralph = RalphLoop("demo_session")

    # Add sample tasks with alternatives
    def task_success():
        return "Task 1 completed successfully"

    def task_fail_then_succeed():
        raise Exception("Primary failed")

    def task_alternative():
        return "Alternative approach worked!"

    ralph.add_task(
        "task_1",
        "Successful task",
        primary=task_success,
        alternatives=[task_success]
    )

    ralph.add_task(
        "task_2",
        "Task with alternative",
        primary=task_fail_then_succeed,
        alternatives=[task_fail_then_succeed, task_alternative]
    )

    summary = ralph.run()

    print("\nExecution Summary:")
    print(json.dumps(summary, indent=2))

    return summary


if __name__ == "__main__":
    demo()
