"""
Ralph Wiggum Integration for Genesis Loops
==========================================
Adds self-verification and atomic task tracking to existing Genesis loops.

This integrates the Ralph methodology (Ryan Carson/Jeff Huntley) into Genesis:
- tasks.json with passes: true/false
- Self-verification against acceptance criteria
- Fresh context patterns
- Commit checkpointing

Works WITH existing loops, not replacing them:
- continuous_evolution.py uses this for task tracking
- genesis_heartbeat.py uses this for verification
- delta_01_learning_loop.py uses this for outcome feedback
"""

import json
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict, field
import subprocess


@dataclass
class AcceptanceCriterion:
    """Single acceptance criterion for a story."""
    description: str
    verification_method: str  # 'test', 'grep', 'file_exists', 'manual', 'llm'
    verification_command: Optional[str] = None
    passed: Optional[bool] = None
    verification_output: Optional[str] = None


@dataclass
class Story:
    """Atomic task unit - must be completable in ONE iteration."""
    id: str
    title: str
    description: str
    acceptance_criteria: List[AcceptanceCriterion]
    passes: bool = False
    context: Optional[str] = None
    folder_context: Optional[str] = None  # agents.md content for this folder
    started_at: Optional[str] = None
    completed_at: Optional[str] = None
    iterations: int = 0
    git_commit: Optional[str] = None
    learnings: List[str] = field(default_factory=list)


@dataclass
class TaskBoard:
    """The task board - tracks all stories and their status."""
    project: str
    created_at: str
    updated_at: str
    stories: List[Story]
    completed_count: int = 0
    failed_count: int = 0

    def get_pending(self) -> List[Story]:
        """Get stories that haven't passed yet."""
        return [s for s in self.stories if not s.passes]

    def get_next(self) -> Optional[Story]:
        """Get next story to work on."""
        pending = self.get_pending()
        return pending[0] if pending else None


class RalphTaskManager:
    """
    Manages tasks.json and integrates Ralph patterns into Genesis.

    Usage:
        manager = RalphTaskManager("E:/genesis-system/loop/tasks.json")

        # Add a story
        manager.add_story(Story(
            id="task-001",
            title="Add logging to heartbeat",
            description="Add structured logging",
            acceptance_criteria=[
                AcceptanceCriterion(
                    description="Logger imported in genesis_heartbeat.py",
                    verification_method="grep",
                    verification_command="grep -q 'import logging' genesis_heartbeat.py"
                )
            ]
        ))

        # Get next task
        story = manager.get_next_story()

        # Verify and mark complete
        if manager.verify_story(story):
            manager.mark_complete(story.id)
    """

    def __init__(self, tasks_path: str = None):
        self.tasks_path = Path(tasks_path or "E:/genesis-system/loop/tasks.json")
        self.tasks_path.parent.mkdir(parents=True, exist_ok=True)
        self.board = self._load_or_create()

    def _load_or_create(self) -> TaskBoard:
        """Load existing tasks or create new board."""
        if self.tasks_path.exists():
            try:
                with open(self.tasks_path) as f:
                    data = json.load(f)
                    stories = []
                    for s in data.get("stories", []):
                        criteria = [
                            AcceptanceCriterion(**c)
                            for c in s.get("acceptance_criteria", [])
                        ]
                        s["acceptance_criteria"] = criteria
                        stories.append(Story(**s))
                    return TaskBoard(
                        project=data.get("project", "Genesis"),
                        created_at=data.get("created_at", datetime.now().isoformat()),
                        updated_at=data.get("updated_at", datetime.now().isoformat()),
                        stories=stories,
                        completed_count=data.get("completed_count", 0),
                        failed_count=data.get("failed_count", 0)
                    )
            except Exception as e:
                print(f"Error loading tasks: {e}")

        return TaskBoard(
            project="Genesis",
            created_at=datetime.now().isoformat(),
            updated_at=datetime.now().isoformat(),
            stories=[]
        )

    def _save(self):
        """Save task board to disk."""
        self.board.updated_at = datetime.now().isoformat()

        data = {
            "project": self.board.project,
            "created_at": self.board.created_at,
            "updated_at": self.board.updated_at,
            "completed_count": self.board.completed_count,
            "failed_count": self.board.failed_count,
            "stories": []
        }

        for story in self.board.stories:
            story_dict = asdict(story)
            data["stories"].append(story_dict)

        with open(self.tasks_path, "w") as f:
            json.dump(data, f, indent=2)

    def add_story(self, story: Story):
        """Add a new story to the board."""
        story.id = story.id or f"story-{len(self.board.stories)+1:03d}"
        self.board.stories.append(story)
        self._save()

    def get_next_story(self) -> Optional[Story]:
        """Get the next pending story."""
        return self.board.get_next()

    def get_pending_count(self) -> int:
        """Get count of pending stories."""
        return len(self.board.get_pending())

    def verify_criterion(self, criterion: AcceptanceCriterion) -> bool:
        """
        Verify a single acceptance criterion.

        Supports multiple verification methods:
        - test: Run pytest or unittest
        - grep: Search for pattern in file
        - file_exists: Check if file exists
        - command: Run arbitrary command, success = exit 0
        - manual: Skip (requires human verification)
        - llm: Use LLM to verify (future)
        """
        method = criterion.verification_method
        command = criterion.verification_command

        if method == "manual":
            print(f"  [MANUAL] {criterion.description}")
            criterion.passed = True  # Assume pass for automated runs
            return True

        if method == "file_exists":
            exists = Path(command).exists() if command else False
            criterion.passed = exists
            criterion.verification_output = f"File {'exists' if exists else 'not found'}"
            return exists

        if method in ("grep", "test", "command"):
            if not command:
                criterion.passed = False
                criterion.verification_output = "No command specified"
                return False

            try:
                result = subprocess.run(
                    command,
                    shell=True,
                    capture_output=True,
                    text=True,
                    timeout=60,
                    cwd=str(Path("E:/genesis-system"))
                )
                criterion.passed = result.returncode == 0
                criterion.verification_output = result.stdout[:500] if result.stdout else result.stderr[:500]
                return criterion.passed
            except subprocess.TimeoutExpired:
                criterion.passed = False
                criterion.verification_output = "Verification timed out"
                return False
            except Exception as e:
                criterion.passed = False
                criterion.verification_output = str(e)
                return False

        # Unknown method
        criterion.passed = False
        criterion.verification_output = f"Unknown verification method: {method}"
        return False

    def verify_story(self, story: Story) -> bool:
        """
        Verify all acceptance criteria for a story.
        Returns True only if ALL criteria pass.
        """
        print(f"\n=== Verifying: {story.title} ===")

        all_passed = True
        for criterion in story.acceptance_criteria:
            passed = self.verify_criterion(criterion)
            status = "PASS" if passed else "FAIL"
            print(f"  [{status}] {criterion.description}")
            if criterion.verification_output:
                print(f"       Output: {criterion.verification_output[:100]}")
            if not passed:
                all_passed = False

        return all_passed

    def mark_complete(self, story_id: str, commit: bool = True) -> bool:
        """
        Mark a story as complete.
        Optionally create a git commit.
        """
        story = next((s for s in self.board.stories if s.id == story_id), None)
        if not story:
            return False

        story.passes = True
        story.completed_at = datetime.now().isoformat()
        story.iterations += 1
        self.board.completed_count += 1

        if commit:
            try:
                result = subprocess.run(
                    f'git add -A && git commit -m "GENESIS-RALPH: {story.title}"',
                    shell=True,
                    capture_output=True,
                    text=True,
                    cwd="E:/genesis-system"
                )
                if result.returncode == 0:
                    # Extract commit hash
                    story.git_commit = result.stdout.split()[1] if result.stdout else None
            except:
                pass

        self._save()
        return True

    def mark_failed(self, story_id: str, learning: str = None):
        """Mark a story as failed with optional learning."""
        story = next((s for s in self.board.stories if s.id == story_id), None)
        if not story:
            return

        story.iterations += 1
        self.board.failed_count += 1

        if learning:
            story.learnings.append(learning)

        self._save()

    def get_progress_summary(self) -> Dict[str, Any]:
        """Get summary of task progress."""
        total = len(self.board.stories)
        completed = len([s for s in self.board.stories if s.passes])
        pending = total - completed

        return {
            "total": total,
            "completed": completed,
            "pending": pending,
            "completion_pct": (completed / total * 100) if total > 0 else 0,
            "total_iterations": sum(s.iterations for s in self.board.stories),
            "failed_attempts": self.board.failed_count
        }


class PRDConverter:
    """
    Converts a PRD (Product Requirements Document) into atomic stories.

    The PRD can be:
    - Markdown file with requirements
    - Plain text feature description
    - Voice transcript (pre-converted to text)
    """

    def __init__(self):
        self.max_story_complexity = 3  # Max acceptance criteria per story

    def convert_markdown_prd(self, prd_content: str, project: str = "Genesis") -> List[Story]:
        """
        Convert markdown PRD to stories.

        Expected format:
        # Feature: XYZ

        ## Story 1: Do something
        - [ ] Acceptance criterion 1
        - [ ] Acceptance criterion 2

        ## Story 2: Do something else
        ...
        """
        stories = []
        current_story = None

        for line in prd_content.split("\n"):
            line = line.strip()

            # New story
            if line.startswith("## Story") or line.startswith("## "):
                if current_story:
                    stories.append(current_story)

                title = line.lstrip("#").strip()
                if title.startswith("Story"):
                    title = title.split(":", 1)[-1].strip() if ":" in title else title

                current_story = Story(
                    id=f"story-{len(stories)+1:03d}",
                    title=title,
                    description="",
                    acceptance_criteria=[]
                )

            # Acceptance criterion
            elif line.startswith("- [ ]") or line.startswith("- [x]"):
                if current_story:
                    criterion_text = line.lstrip("- []x").strip()
                    criterion = self._parse_criterion(criterion_text)
                    current_story.acceptance_criteria.append(criterion)

            # Description line
            elif current_story and not line.startswith("#"):
                if current_story.description:
                    current_story.description += " " + line
                else:
                    current_story.description = line

        # Don't forget last story
        if current_story:
            stories.append(current_story)

        return stories

    def _parse_criterion(self, text: str) -> AcceptanceCriterion:
        """
        Parse a criterion and infer verification method.

        Examples:
        - "File xyz.py exists" → file_exists
        - "Function foo() is defined" → grep
        - "Tests pass" → test
        """
        text_lower = text.lower()

        # File existence
        if "exists" in text_lower and ("file" in text_lower or ".py" in text_lower or ".json" in text_lower):
            # Extract filename
            import re
            match = re.search(r'[\w/\\]+\.\w+', text)
            filename = match.group(0) if match else None
            return AcceptanceCriterion(
                description=text,
                verification_method="file_exists",
                verification_command=filename
            )

        # Test-related
        if "test" in text_lower and ("pass" in text_lower or "run" in text_lower):
            return AcceptanceCriterion(
                description=text,
                verification_method="test",
                verification_command="python -m pytest -x"
            )

        # Grep-related (function defined, import exists, etc.)
        if any(kw in text_lower for kw in ["defined", "import", "contains", "includes"]):
            return AcceptanceCriterion(
                description=text,
                verification_method="grep",
                verification_command=None  # Will need to be filled in
            )

        # Default to manual
        return AcceptanceCriterion(
            description=text,
            verification_method="manual"
        )

    def convert_simple_description(self, description: str, project: str = "Genesis") -> List[Story]:
        """
        Convert a simple text description into stories.

        Uses heuristics to break down into atomic units.
        """
        # Simple approach: one story per sentence with action verb
        stories = []
        sentences = description.replace("\n", ". ").split(".")

        for sentence in sentences:
            sentence = sentence.strip()
            if not sentence:
                continue

            # Only create story for action sentences
            action_verbs = ["add", "create", "implement", "fix", "update", "remove", "refactor", "test"]
            if any(verb in sentence.lower() for verb in action_verbs):
                stories.append(Story(
                    id=f"story-{len(stories)+1:03d}",
                    title=sentence[:80],
                    description=sentence,
                    acceptance_criteria=[
                        AcceptanceCriterion(
                            description=f"Completed: {sentence}",
                            verification_method="manual"
                        )
                    ]
                ))

        return stories


def load_folder_context(folder_path: str) -> Optional[str]:
    """
    Load agents.md context for a folder.
    Part of Ralph's distributed context pattern.
    """
    agents_file = Path(folder_path) / "agents.md"
    if agents_file.exists():
        return agents_file.read_text()
    return None


def update_folder_context(folder_path: str, learning: str):
    """
    Update agents.md with new learning.
    Called after successful task completion.
    """
    agents_file = Path(folder_path) / "agents.md"

    if agents_file.exists():
        content = agents_file.read_text()
    else:
        content = f"# Context for {Path(folder_path).name}\n\n## Learnings\n\n"

    # Append learning with timestamp
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
    content += f"\n### {timestamp}\n{learning}\n"

    agents_file.write_text(content)


# Integration hooks for existing Genesis loops

def integrate_with_continuous_evolution(swarm, task_manager: RalphTaskManager):
    """
    Hook Ralph task tracking into continuous_evolution.py.

    Call this from the evolution cycle to track tasks.
    """
    # Get next task
    story = task_manager.get_next_story()
    if not story:
        return None

    # Mark as started
    story.started_at = datetime.now().isoformat()

    # Load folder context if applicable
    if story.folder_context:
        folder_context = load_folder_context(story.folder_context)
        story.context = (story.context or "") + f"\n\nFolder Context:\n{folder_context}"

    return story


def integrate_with_heartbeat(heartbeat, task_manager: RalphTaskManager, story: Story):
    """
    Hook Ralph verification into genesis_heartbeat.py.

    After heartbeat completes a task, verify it.
    """
    if task_manager.verify_story(story):
        task_manager.mark_complete(story.id)

        # Update folder context with learnings
        if story.learnings and story.folder_context:
            for learning in story.learnings:
                update_folder_context(story.folder_context, learning)

        return True
    else:
        task_manager.mark_failed(story.id, "Verification failed")
        return False


# CLI for testing
if __name__ == "__main__":
    import sys

    manager = RalphTaskManager()

    if len(sys.argv) < 2:
        print("Usage: python ralph_integration.py [status|add|verify|reset]")
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == "status":
        summary = manager.get_progress_summary()
        print(f"\n=== TASK BOARD STATUS ===")
        print(f"Total Stories: {summary['total']}")
        print(f"Completed: {summary['completed']} ({summary['completion_pct']:.1f}%)")
        print(f"Pending: {summary['pending']}")
        print(f"Total Iterations: {summary['total_iterations']}")
        print(f"Failed Attempts: {summary['failed_attempts']}")

        next_story = manager.get_next_story()
        if next_story:
            print(f"\nNext Story: {next_story.title}")

    elif cmd == "add":
        # Example: Add a test story
        manager.add_story(Story(
            id="test-001",
            title="Test Ralph Integration",
            description="Verify Ralph integration works",
            acceptance_criteria=[
                AcceptanceCriterion(
                    description="ralph_integration.py exists",
                    verification_method="file_exists",
                    verification_command="E:/genesis-system/core/ralph_integration.py"
                )
            ]
        ))
        print("Test story added")

    elif cmd == "verify":
        story = manager.get_next_story()
        if story:
            if manager.verify_story(story):
                manager.mark_complete(story.id, commit=False)
                print(f"\n Story PASSED: {story.title}")
            else:
                print(f"\n Story FAILED: {story.title}")
        else:
            print("No pending stories")

    elif cmd == "reset":
        manager.board = TaskBoard(
            project="Genesis",
            created_at=datetime.now().isoformat(),
            updated_at=datetime.now().isoformat(),
            stories=[]
        )
        manager._save()
        print("Task board reset")

    else:
        print(f"Unknown command: {cmd}")
