"""Agent Builder Service for programmatic agent creation and management.

This service provides comprehensive agent lifecycle management including:
- Template-based agent generation
- Agent variant creation with inheritance
- Configuration validation and sanitization
- PM instruction customization
- Integration with deployment services
"""

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

from claude_mpm.core.exceptions import AgentDeploymentError
from claude_mpm.core.logging_config import get_logger


class AgentBuilderService:
    """Service for building and managing agent configurations."""
    
    # Valid agent models
    VALID_MODELS = ["sonnet", "opus", "haiku"]
    
    # Valid tool choices
    VALID_TOOL_CHOICES = ["auto", "required", "any", "none"]
    
    # Agent categories
    AGENT_CATEGORIES = [
        "engineering", "qa", "documentation", "ops", 
        "research", "security", "system", "utility"
    ]
    
    def __init__(self, templates_dir: Optional[Path] = None):
        """Initialize the Agent Builder Service.
        
        Args:
            templates_dir: Path to agent templates directory
        """
        self.logger = get_logger(__name__)
        self.templates_dir = templates_dir or Path(__file__).parent.parent.parent / "agents" / "templates"
        self._template_cache = {}
        
    def create_agent(
        self,
        agent_id: str,
        name: str,
        description: str,
        model: str = "sonnet",
        tool_choice: str = "auto",
        instructions: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        base_template: Optional[str] = None
    ) -> Dict[str, Any]:
        """Create a new agent configuration.
        
        Args:
            agent_id: Unique identifier for the agent
            name: Display name for the agent
            description: Agent purpose and capabilities
            model: LLM model to use (sonnet/opus/haiku)
            tool_choice: Tool selection strategy
            instructions: Markdown instructions content
            metadata: Additional agent metadata
            base_template: Optional base template to extend
            
        Returns:
            Complete agent configuration dictionary
            
        Raises:
            AgentDeploymentError: If validation fails
        """
        # Validate inputs
        self._validate_agent_id(agent_id)
        self._validate_model(model)
        self._validate_tool_choice(tool_choice)
        
        # Start with base template if provided
        if base_template:
            config = self._load_template(base_template)
            config["id"] = agent_id  # Override ID
        else:
            config = {}
            
        # Build agent configuration
        config.update({
            "id": agent_id,
            "name": name,
            "prompt": f"{agent_id}.md",
            "model": model,
            "tool_choice": tool_choice
        })
        
        # Build metadata
        agent_metadata = {
            "description": description,
            "version": "1.0.0",
            "created": datetime.now().isoformat(),
            "author": "Agent Manager",
            "category": "custom"
        }
        
        if metadata:
            agent_metadata.update(metadata)
            
        config["metadata"] = agent_metadata
        
        # Generate instructions if not provided
        if instructions is None:
            instructions = self._generate_default_instructions(agent_id, name, description)
            
        return config, instructions
        
    def create_variant(
        self,
        base_agent_id: str,
        variant_id: str,
        variant_name: str,
        modifications: Dict[str, Any],
        instructions_append: Optional[str] = None
    ) -> Tuple[Dict[str, Any], str]:
        """Create an agent variant based on an existing agent.
        
        Args:
            base_agent_id: ID of the base agent to extend
            variant_id: Unique ID for the variant
            variant_name: Display name for the variant
            modifications: Configuration changes for the variant
            instructions_append: Additional instructions to append
            
        Returns:
            Tuple of (variant configuration, variant instructions)
            
        Raises:
            AgentDeploymentError: If base agent not found or validation fails
        """
        # Load base agent
        base_config = self._load_template(base_agent_id)
        base_instructions = self._load_instructions(base_agent_id)
        
        # Validate variant ID
        self._validate_agent_id(variant_id)
        
        # Create variant configuration
        variant_config = base_config.copy()
        variant_config["id"] = variant_id
        variant_config["name"] = variant_name
        variant_config["prompt"] = f"{variant_id}.md"
        
        # Apply modifications
        for key, value in modifications.items():
            if key in ["model", "tool_choice"]:
                if key == "model":
                    self._validate_model(value)
                elif key == "tool_choice":
                    self._validate_tool_choice(value)
            variant_config[key] = value
            
        # Update metadata
        if "metadata" not in variant_config:
            variant_config["metadata"] = {}
            
        variant_config["metadata"].update({
            "base_agent": base_agent_id,
            "variant": True,
            "variant_created": datetime.now().isoformat()
        })
        
        # Build variant instructions
        variant_instructions = f"# {variant_name} (Variant of {base_config.get('name', base_agent_id)})\n\n"
        variant_instructions += base_instructions
        
        if instructions_append:
            variant_instructions += f"\n\n## Variant-Specific Instructions\n\n{instructions_append}"
            
        return variant_config, variant_instructions
        
    def validate_configuration(self, config: Dict[str, Any]) -> List[str]:
        """Validate an agent configuration.
        
        Args:
            config: Agent configuration to validate
            
        Returns:
            List of validation errors (empty if valid)
        """
        errors = []
        
        # Required fields
        required_fields = ["id", "name", "prompt", "model"]
        for field in required_fields:
            if field not in config:
                errors.append(f"Missing required field: {field}")
                
        # Validate ID
        if "id" in config:
            try:
                self._validate_agent_id(config["id"])
            except AgentDeploymentError as e:
                errors.append(str(e))
                
        # Validate model
        if "model" in config:
            try:
                self._validate_model(config["model"])
            except AgentDeploymentError as e:
                errors.append(str(e))
                
        # Validate tool_choice
        if "tool_choice" in config:
            try:
                self._validate_tool_choice(config["tool_choice"])
            except AgentDeploymentError as e:
                errors.append(str(e))
                
        # Validate metadata
        if "metadata" in config:
            if not isinstance(config["metadata"], dict):
                errors.append("Metadata must be a dictionary")
                
        return errors
        
    def generate_pm_instructions(
        self,
        delegation_patterns: Optional[List[str]] = None,
        workflow_overrides: Optional[Dict[str, str]] = None,
        custom_rules: Optional[List[str]] = None
    ) -> str:
        """Generate customized PM instructions.
        
        Args:
            delegation_patterns: Custom delegation patterns
            workflow_overrides: Workflow sequence modifications
            custom_rules: Additional PM rules
            
        Returns:
            Customized PM instructions markdown
        """
        instructions = "# Custom PM Instructions\n\n"
        
        if delegation_patterns:
            instructions += "## Custom Delegation Patterns\n\n"
            for pattern in delegation_patterns:
                instructions += f"- {pattern}\n"
            instructions += "\n"
            
        if workflow_overrides:
            instructions += "## Workflow Overrides\n\n"
            for workflow, override in workflow_overrides.items():
                instructions += f"### {workflow}\n{override}\n\n"
                
        if custom_rules:
            instructions += "## Additional Rules\n\n"
            for rule in custom_rules:
                instructions += f"- {rule}\n"
                
        return instructions
        
    def list_available_templates(self) -> List[Dict[str, Any]]:
        """List all available agent templates.
        
        Returns:
            List of template metadata dictionaries
        """
        templates = []
        
        if not self.templates_dir.exists():
            return templates
            
        for template_file in self.templates_dir.glob("*.json"):
            try:
                with open(template_file, 'r') as f:
                    config = json.load(f)
                    
                # Use filename stem as ID if not specified in config
                template_id = config.get("id") or template_file.stem
                    
                templates.append({
                    "id": template_id,
                    "name": config.get("name", template_id),
                    "description": config.get("metadata", {}).get("description"),
                    "category": config.get("metadata", {}).get("category"),
                    "version": config.get("metadata", {}).get("version"),
                    "file": str(template_file)
                })
            except Exception as e:
                self.logger.warning(f"Failed to load template {template_file}: {e}")
                
        return templates
        
    def _validate_agent_id(self, agent_id: str) -> None:
        """Validate agent ID format.
        
        Args:
            agent_id: Agent ID to validate
            
        Raises:
            AgentDeploymentError: If ID is invalid
        """
        if not agent_id:
            raise AgentDeploymentError("Agent ID cannot be empty")
            
        if len(agent_id) > 50:
            raise AgentDeploymentError("Agent ID must be 50 characters or less")
            
        if not re.match(r'^[a-z0-9-]+$', agent_id):
            raise AgentDeploymentError(
                "Agent ID must contain only lowercase letters, numbers, and hyphens"
            )
            
    def _validate_model(self, model: str) -> None:
        """Validate model selection.
        
        Args:
            model: Model to validate
            
        Raises:
            AgentDeploymentError: If model is invalid
        """
        if model not in self.VALID_MODELS:
            raise AgentDeploymentError(
                f"Invalid model '{model}'. Must be one of: {', '.join(self.VALID_MODELS)}"
            )
            
    def _validate_tool_choice(self, tool_choice: str) -> None:
        """Validate tool choice setting.
        
        Args:
            tool_choice: Tool choice to validate
            
        Raises:
            AgentDeploymentError: If tool choice is invalid
        """
        if tool_choice not in self.VALID_TOOL_CHOICES:
            raise AgentDeploymentError(
                f"Invalid tool_choice '{tool_choice}'. Must be one of: {', '.join(self.VALID_TOOL_CHOICES)}"
            )
            
    def _load_template(self, template_id: str) -> Dict[str, Any]:
        """Load an agent template.
        
        Args:
            template_id: Template ID to load
            
        Returns:
            Template configuration dictionary
            
        Raises:
            AgentDeploymentError: If template not found
        """
        if template_id in self._template_cache:
            return self._template_cache[template_id].copy()
            
        template_file = self.templates_dir / f"{template_id}.json"
        
        if not template_file.exists():
            raise AgentDeploymentError(f"Template '{template_id}' not found")
            
        try:
            with open(template_file, 'r') as f:
                config = json.load(f)
                self._template_cache[template_id] = config
                return config.copy()
        except Exception as e:
            raise AgentDeploymentError(f"Failed to load template '{template_id}': {e}")
            
    def _load_instructions(self, agent_id: str) -> str:
        """Load agent instructions.
        
        Args:
            agent_id: Agent ID to load instructions for
            
        Returns:
            Instructions markdown content
            
        Raises:
            AgentDeploymentError: If instructions not found
        """
        # Try multiple possible locations
        possible_files = [
            self.templates_dir / f"{agent_id}.md",
            self.templates_dir / f"{agent_id}_instructions.md",
            self.templates_dir / f"{agent_id}-instructions.md"
        ]
        
        for instructions_file in possible_files:
            if instructions_file.exists():
                try:
                    with open(instructions_file, 'r') as f:
                        return f.read()
                except Exception as e:
                    self.logger.warning(f"Failed to read instructions from {instructions_file}: {e}")
                    
        # If no instructions found, return empty
        return ""
        
    def _generate_default_instructions(self, agent_id: str, name: str, description: str) -> str:
        """Generate default agent instructions.
        
        Args:
            agent_id: Agent identifier
            name: Agent display name
            description: Agent description
            
        Returns:
            Default instructions markdown
        """
        return f"""# {name}

## Core Identity

You are {name}, a specialized agent in the Claude MPM framework.

## Purpose

{description}

## Responsibilities

- Primary focus on your specialized domain
- Collaborate with other agents as needed
- Follow Claude MPM framework conventions
- Maintain high quality standards

## Operating Principles

1. **Expertise**: Apply deep knowledge in your domain
2. **Efficiency**: Complete tasks effectively and quickly
3. **Communication**: Provide clear, actionable responses
4. **Collaboration**: Work well with other agents
5. **Quality**: Maintain high standards in all outputs

## Output Format

Provide structured responses with:
- Clear summaries of actions taken
- Detailed results when appropriate
- Any issues or blockers encountered
- Recommendations for next steps

## Integration

- Follow framework patterns and conventions
- Use appropriate tools for the task
- Coordinate with PM for complex workflows
- Report completion status clearly

---

*Agent ID: {agent_id}*
*Generated by Agent Manager*
"""