"""
Coder Agent - Implements tasks by generating code.
"""

import json
import re
import logging
from pathlib import Path
from typing import Optional, Dict, Any, List

from ..base import Agent, AgentContext, AgentResult
from ..registry import agent
from ..task import Task, TaskStatus
from ..events import EventEmitter, Event


logger = logging.getLogger(__name__)


@agent(name="coder", role="Software Developer and Code Generator")
class Coder(Agent):
    """
    Agent that implements coding tasks by generating complete, runnable code.
    
    The Coder:
    - Takes a task specification
    - Generates complete implementation
    - Creates/modifies files as needed
    - Ensures code is immediately runnable
    """
    
    name = "coder"
    role = "Software Developer and Code Generator"
    
    system_prompt = '''You are the Coder Agent, an expert software developer who writes COMPLETE, PRODUCTION-READY code.

CRITICAL RULES - NEVER VIOLATE THESE:
1. EVERY Python file that can be executed MUST have `if __name__ == '__main__':` at the end
2. NEVER write partial, skeleton, or placeholder code - write FULL implementations
3. ALL imports must be at the top of the file
4. ALL functions/methods must have complete implementations - no `pass` or `TODO` or `...`
5. Include ALL necessary error handling
6. Code must be IMMEDIATELY RUNNABLE without any modifications

OUTPUT FORMAT - Respond with ONLY valid JSON:
{
    "analysis": "Brief analysis of what needs to be built",
    "files": [
        {
            "path": "relative/path/to/file.py",
            "action": "create",
            "content": "COMPLETE file content here",
            "description": "What this file does"
        }
    ],
    "summary": "Summary of implementation",
    "verification": {
        "command": "python main.py",
        "expected": "Expected output or behavior"
    },
    "success": true
}

FILE ACTIONS:
- "create": Create a new file
- "modify": Replace file content entirely  
- "delete": Delete the file

CODE REQUIREMENTS:
1. Python files:
   - All imports at top
   - Proper docstrings for modules/classes/functions
   - `if __name__ == '__main__':` block for executables
   - Complete error handling
   
2. For GUI/Game applications:
   - Complete window/app setup
   - Proper event handling
   - Full rendering logic
   - All game mechanics implemented

3. requirements.txt:
   - Only valid pip package names
   - One package per line

NEVER DO THESE:
- Never write `# TODO: implement this`
- Never use `pass` as placeholder
- Never use `...` as implementation
- Never create incomplete files

Respond with ONLY valid JSON, no markdown code blocks.'''

    def __init__(
        self,
        llm=None,
        model: Optional[str] = None,
        events: Optional[EventEmitter] = None,
        workspace_dir: str = "./output",
        **kwargs
    ):
        super().__init__(llm=llm, model=model, **kwargs)
        self.events = events
        self.workspace_dir = Path(workspace_dir)
        self.workspace_dir.mkdir(parents=True, exist_ok=True)
    
    def execute(self, context: AgentContext) -> AgentResult:
        """Implement a coding task."""
        task_data = context.get("task")
        
        if not task_data:
            return AgentResult.fail("No task provided")
        
        # Convert dict to Task if needed
        if isinstance(task_data, dict):
            task = Task.from_dict(task_data)
        else:
            task = task_data
        
        self._logger.info(f"Implementing task: {task.title}")
        
        if self.events:
            self.events.emit(Event.TASK_START, task=task.title, task_id=task.id)
        
        try:
            # Get existing code context
            existing_code = self._get_existing_code(task)
            
            # Get retry context if present
            retry_info = self._get_retry_context(context)
            
            # Query LLM
            prompt = self._build_prompt(task, existing_code, retry_info)
            response = self.query_llm(prompt)
            
            # Parse response
            implementation = self._parse_response(response)
            
            if not implementation:
                return AgentResult.fail("Failed to parse implementation from LLM response")
            
            if not implementation.get("success", False):
                return AgentResult.fail(implementation.get("summary", "Implementation failed"))
            
            # Apply file changes
            applied_files = self._apply_files(implementation.get("files", []))
            
            self._logger.info(f"Applied {len(applied_files)} file changes")
            
            if self.events:
                for f in applied_files:
                    self.events.emit(
                        Event.FILE_CREATED if f["action"] == "create" else Event.FILE_MODIFIED,
                        path=f["path"]
                    )
            
            return AgentResult.ok({
                "files": applied_files,
                "summary": implementation.get("summary", ""),
                "verification": implementation.get("verification", {}),
            })
            
        except Exception as e:
            self._logger.error(f"Task implementation failed: {e}")
            if self.events:
                self.events.emit(Event.TASK_FAILED, task=task.title, error=str(e))
            return AgentResult.fail(str(e))
    
    def _build_prompt(
        self,
        task: Task,
        existing_code: Dict[str, str],
        retry_info: Optional[Dict[str, Any]] = None
    ) -> str:
        """Build the implementation prompt."""
        parts = []
        
        # Add retry context if present
        if retry_info:
            parts.append("=" * 50)
            parts.append(f"⚠️ RETRY ATTEMPT - FIX THE PREVIOUS ERROR")
            parts.append(f"Previous error: {retry_info.get('error', 'Unknown')}")
            if retry_info.get("instructions"):
                parts.append(f"Fix instructions: {retry_info['instructions']}")
            parts.append("=" * 50 + "\n")
        
        parts.append("## Task to Implement")
        parts.append(f"**Title:** {task.title}")
        parts.append(f"**Description:** {task.description}")
        
        if task.files_to_create:
            parts.append(f"\n**Files to Create:** {', '.join(task.files_to_create)}")
        
        if task.files_to_modify:
            parts.append(f"**Files to Modify:** {', '.join(task.files_to_modify)}")
        
        criteria = task.metadata.get("acceptance_criteria", [])
        if criteria:
            parts.append("\n**Acceptance Criteria:**")
            for c in criteria:
                parts.append(f"- {c}")
        
        if existing_code:
            parts.append("\n## Existing Code")
            for path, content in existing_code.items():
                parts.append(f"\n### {path}")
                parts.append(f"```\n{content[:2000]}\n```")
        
        parts.append("\n## Instructions")
        parts.append("Implement the task completely. Generate all necessary code.")
        parts.append("Respond with ONLY valid JSON as specified.")
        
        return "\n".join(parts)
    
    def _get_existing_code(self, task: Task) -> Dict[str, str]:
        """Get content of existing files relevant to this task."""
        code = {}
        
        # Check files to modify
        for filepath in task.files_to_modify:
            full_path = self.workspace_dir / filepath
            if full_path.exists():
                try:
                    code[filepath] = full_path.read_text()
                except Exception:
                    pass
        
        # Also check files to create (might exist from previous tasks)
        for filepath in task.files_to_create:
            full_path = self.workspace_dir / filepath
            if full_path.exists():
                try:
                    code[filepath] = full_path.read_text()
                except Exception:
                    pass
        
        return code
    
    def _get_retry_context(self, context: AgentContext) -> Optional[Dict[str, Any]]:
        """Extract retry context if this is a retry."""
        retry_attempt = context.get("retry_attempt", 0)
        if retry_attempt > 0:
            return {
                "attempt": retry_attempt,
                "error": context.get("previous_error", ""),
                "instructions": context.get("retry_instructions", ""),
            }
        return None
    
    def _parse_response(self, response: str) -> Optional[Dict[str, Any]]:
        """Parse JSON from LLM response."""
        try:
            return json.loads(response)
        except json.JSONDecodeError:
            # Try to extract JSON
            json_match = re.search(r'\{[\s\S]*\}', response)
            if json_match:
                try:
                    return json.loads(json_match.group())
                except json.JSONDecodeError:
                    pass
            
            self._logger.error("Failed to parse implementation JSON")
            return None
    
    def _apply_files(self, files: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Apply file changes to workspace."""
        applied = []
        
        for file_spec in files:
            path = file_spec.get("path")
            action = file_spec.get("action", "create")
            content = file_spec.get("content", "")
            
            if not path:
                continue
            
            full_path = self.workspace_dir / path
            
            try:
                if action == "delete":
                    if full_path.exists():
                        full_path.unlink()
                        self._logger.info(f"Deleted: {path}")
                else:
                    # Create or modify
                    full_path.parent.mkdir(parents=True, exist_ok=True)
                    full_path.write_text(content)
                    self._logger.info(f"{'Created' if action == 'create' else 'Modified'}: {path}")
                
                applied.append({
                    "path": path,
                    "action": action,
                    "success": True,
                })
                
            except Exception as e:
                self._logger.error(f"Failed to apply {path}: {e}")
                applied.append({
                    "path": path,
                    "action": action,
                    "success": False,
                    "error": str(e),
                })
        
        return applied
