"""
AIVA Skill Registry - PM-026

Registry of skills AIVA can invoke.
Supports dynamic skill loading and permission validation.
"""

import os
import json
import logging
import importlib
import importlib.util
from datetime import datetime
from typing import Dict, List, Optional, Any, Callable, Type
from dataclasses import dataclass, asdict, field
from pathlib import Path
from enum import Enum

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


class SkillCategory(Enum):
    """Categories of skills."""
    CORE = "core"           # Core system skills
    INTEGRATION = "integration"  # External service integrations
    ANALYTICS = "analytics"     # Data analysis and reporting
    AUTOMATION = "automation"   # Task automation
    REVENUE = "revenue"         # Revenue-generating skills
    KNOWLEDGE = "knowledge"     # Knowledge management
    UTILITY = "utility"         # Utility functions


class SkillStatus(Enum):
    """Status of a skill."""
    ACTIVE = "active"
    DEPRECATED = "deprecated"
    DISABLED = "disabled"
    EXPERIMENTAL = "experimental"


@dataclass
class SkillConfig:
    """Configuration for a skill."""
    skill_id: str
    name: str
    description: str
    category: SkillCategory
    module_path: str
    class_name: str
    required_permissions: List[str] = field(default_factory=list)
    required_rank: int = 1  # Minimum rank required to use
    status: SkillStatus = SkillStatus.ACTIVE
    version: str = "1.0.0"
    author: str = "AIVA"
    created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    metadata: Dict = field(default_factory=dict)

    def to_dict(self) -> Dict:
        data = asdict(self)
        data["category"] = self.category.value
        data["status"] = self.status.value
        return data

    @classmethod
    def from_dict(cls, data: Dict) -> "SkillConfig":
        data["category"] = SkillCategory(data["category"])
        data["status"] = SkillStatus(data["status"])
        return cls(**data)


@dataclass
class SkillExecutionResult:
    """Result from skill execution."""
    skill_id: str
    success: bool
    result: Any
    error: Optional[str] = None
    execution_time_ms: float = 0.0
    timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())


# Default skills registry
DEFAULT_SKILLS: List[Dict] = [
    {
        "skill_id": "ghl_mastery",
        "name": "GoHighLevel Mastery",
        "description": "Complete GHL API integration for CRM operations",
        "category": "integration",
        "module_path": "skills.ghl_mastery_skill",
        "class_name": "GHLMasterySkill",
        "required_permissions": ["external_api_call"],
        "required_rank": 3
    },
    {
        "skill_id": "telnyx_voice",
        "name": "Telnyx Voice Manager",
        "description": "Voice call management via Telnyx API",
        "category": "integration",
        "module_path": "skills.telnyx_manager_skill",
        "class_name": "TelnyxManagerSkill",
        "required_permissions": ["external_api_call"],
        "required_rank": 4
    },
    {
        "skill_id": "instantly_campaign",
        "name": "Instantly Campaign Manager",
        "description": "Email campaign management via Instantly.ai",
        "category": "revenue",
        "module_path": "skills.instantly_skill",
        "class_name": "InstantlySkill",
        "required_permissions": ["external_api_call"],
        "required_rank": 5
    },
    {
        "skill_id": "tradie_funnel",
        "name": "Tradie Funnel Architect",
        "description": "Lead funnel management for tradie clients",
        "category": "revenue",
        "module_path": "skills.tradie_funnel_architect_skill",
        "class_name": "TradieFunnelArchitectSkill",
        "required_permissions": ["external_api_call", "write_file"],
        "required_rank": 5
    },
    {
        "skill_id": "voice_wrapper",
        "name": "Voice AI Wrapper",
        "description": "Voice AI integration with VAPI and ElevenLabs",
        "category": "integration",
        "module_path": "skills.voice_wrapper_skill",
        "class_name": "VoiceWrapperSkill",
        "required_permissions": ["external_api_call"],
        "required_rank": 4
    },
    {
        "skill_id": "knowledge_ingest",
        "name": "Knowledge Ingestion",
        "description": "Ingest and process new knowledge documents",
        "category": "knowledge",
        "module_path": "AIVA.knowledge_ingest",
        "class_name": "KnowledgeIngestionPipeline",
        "required_permissions": ["store_memory"],
        "required_rank": 3
    },
    {
        "skill_id": "search_grounding",
        "name": "Search Grounding",
        "description": "Web search for factual grounding",
        "category": "utility",
        "module_path": "skills.search_grounding",
        "class_name": "SearchGroundingSkill",
        "required_permissions": ["external_api_call"],
        "required_rank": 2
    },
    {
        "skill_id": "firebase_mastery",
        "name": "Firebase Mastery",
        "description": "Firebase database and auth operations",
        "category": "integration",
        "module_path": "skills.firebase_mastery_skill",
        "class_name": "FirebaseMasterySkill",
        "required_permissions": ["external_api_call"],
        "required_rank": 3
    }
]


class SkillRegistry:
    """
    Registry of skills AIVA can invoke.

    Usage:
        registry = SkillRegistry()
        skill_config = registry.get_skill("ghl_mastery")
        if registry.validate_skill_permission("ghl_mastery", permission_manager, rank_tracker):
            result = registry.execute_skill("ghl_mastery", {"action": "list_contacts"})
    """

    def __init__(self, config_path: Optional[str] = None, permission_manager=None, rank_tracker=None):
        """
        Initialize the skill registry.

        Args:
            config_path: Path to custom skills config JSON
            permission_manager: PermissionManager for permission checks
            rank_tracker: RankTracker for rank validation
        """
        self.config_path = config_path
        self.permission_manager = permission_manager
        self.rank_tracker = rank_tracker
        self.skills: Dict[str, SkillConfig] = {}
        self.loaded_skills: Dict[str, Any] = {}  # Cached skill instances
        self.execution_log: List[SkillExecutionResult] = []

        self._load_skills()
        logger.info(f"SkillRegistry initialized with {len(self.skills)} skills")

    def _load_skills(self) -> None:
        """Load skills from config or defaults."""
        # Load default skills
        for skill_data in DEFAULT_SKILLS:
            skill_data_copy = skill_data.copy()
            skill_data_copy["category"] = SkillCategory(skill_data_copy["category"])
            skill_data_copy["status"] = SkillStatus.ACTIVE
            config = SkillConfig(**skill_data_copy)
            self.skills[config.skill_id] = config

        # Load custom skills from config file
        if self.config_path and os.path.exists(self.config_path):
            try:
                with open(self.config_path, "r") as f:
                    custom_skills = json.load(f)
                for skill_data in custom_skills.get("skills", []):
                    config = SkillConfig.from_dict(skill_data)
                    self.skills[config.skill_id] = config
                logger.info(f"Loaded {len(custom_skills.get('skills', []))} custom skills")
            except Exception as e:
                logger.error(f"Failed to load custom skills: {e}")

    def get_skill(self, skill_id: str) -> Optional[SkillConfig]:
        """
        Get skill configuration by ID.

        Args:
            skill_id: The skill identifier

        Returns:
            SkillConfig or None if not found
        """
        return self.skills.get(skill_id)

    def list_skills(
        self,
        category: Optional[SkillCategory] = None,
        status: Optional[SkillStatus] = None
    ) -> List[SkillConfig]:
        """
        List all registered skills.

        Args:
            category: Filter by category
            status: Filter by status

        Returns:
            List of matching SkillConfig objects
        """
        skills = list(self.skills.values())

        if category:
            skills = [s for s in skills if s.category == category]

        if status:
            skills = [s for s in skills if s.status == status]

        return skills

    def register_skill(self, config: SkillConfig) -> bool:
        """
        Register a new skill.

        Args:
            config: SkillConfig for the skill

        Returns:
            True if registered successfully
        """
        if config.skill_id in self.skills:
            logger.warning(f"Skill {config.skill_id} already exists, updating")

        self.skills[config.skill_id] = config
        logger.info(f"Registered skill: {config.skill_id}")
        return True

    def unregister_skill(self, skill_id: str) -> bool:
        """
        Unregister a skill.

        Args:
            skill_id: ID of skill to remove

        Returns:
            True if removed
        """
        if skill_id in self.skills:
            del self.skills[skill_id]
            if skill_id in self.loaded_skills:
                del self.loaded_skills[skill_id]
            logger.info(f"Unregistered skill: {skill_id}")
            return True
        return False

    def validate_skill_permission(
        self,
        skill_id: str,
        context: Optional[Dict] = None
    ) -> tuple:
        """
        Validate if AIVA can use a skill.

        Args:
            skill_id: Skill to validate
            context: Additional context

        Returns:
            Tuple of (allowed: bool, reason: str)
        """
        config = self.get_skill(skill_id)
        if not config:
            return False, f"Skill '{skill_id}' not found"

        if config.status != SkillStatus.ACTIVE:
            return False, f"Skill '{skill_id}' is {config.status.value}"

        # Check rank requirement
        if self.rank_tracker:
            current_rank = self.rank_tracker.current_rank.value
            if current_rank < config.required_rank:
                return False, f"Rank {current_rank} insufficient, need {config.required_rank}"

        # Check permissions
        if self.permission_manager:
            for perm in config.required_permissions:
                from .permission_manager import PermissionResult
                result, msg = self.permission_manager.check_permission(perm, context)
                if result not in [PermissionResult.APPROVED, PermissionResult.NOTIFY]:
                    return False, f"Permission denied: {perm}"

        return True, "Skill access permitted"

    def load_skill_class(self, skill_id: str) -> Optional[Type]:
        """
        Dynamically load a skill class.

        Args:
            skill_id: Skill to load

        Returns:
            Skill class or None
        """
        config = self.get_skill(skill_id)
        if not config:
            return None

        try:
            # Try to import the module
            module = importlib.import_module(config.module_path)
            skill_class = getattr(module, config.class_name)
            logger.debug(f"Loaded skill class: {config.class_name}")
            return skill_class
        except ImportError as e:
            logger.warning(f"Failed to import skill module {config.module_path}: {e}")
            return None
        except AttributeError as e:
            logger.warning(f"Skill class {config.class_name} not found: {e}")
            return None

    def get_skill_instance(self, skill_id: str, **kwargs) -> Optional[Any]:
        """
        Get or create a skill instance.

        Args:
            skill_id: Skill to instantiate
            **kwargs: Arguments to pass to constructor

        Returns:
            Skill instance or None
        """
        # Return cached instance if available
        if skill_id in self.loaded_skills:
            return self.loaded_skills[skill_id]

        # Load and instantiate
        skill_class = self.load_skill_class(skill_id)
        if skill_class:
            try:
                instance = skill_class(**kwargs)
                self.loaded_skills[skill_id] = instance
                return instance
            except Exception as e:
                logger.error(f"Failed to instantiate skill {skill_id}: {e}")

        return None

    def execute_skill(
        self,
        skill_id: str,
        params: Dict,
        context: Optional[Dict] = None
    ) -> SkillExecutionResult:
        """
        Execute a skill.

        Args:
            skill_id: Skill to execute
            params: Parameters for the skill
            context: Execution context

        Returns:
            SkillExecutionResult
        """
        import time
        start_time = time.time()

        # Validate permission first
        allowed, reason = self.validate_skill_permission(skill_id, context)
        if not allowed:
            result = SkillExecutionResult(
                skill_id=skill_id,
                success=False,
                result=None,
                error=f"Permission denied: {reason}"
            )
            self.execution_log.append(result)
            return result

        # Get skill instance
        skill_instance = self.get_skill_instance(skill_id)
        if not skill_instance:
            result = SkillExecutionResult(
                skill_id=skill_id,
                success=False,
                result=None,
                error="Failed to load skill"
            )
            self.execution_log.append(result)
            return result

        # Execute the skill
        try:
            # Most skills have an execute() method
            if hasattr(skill_instance, 'execute'):
                output = skill_instance.execute(**params)
            elif hasattr(skill_instance, 'run'):
                output = skill_instance.run(**params)
            else:
                # Try calling the instance directly
                output = skill_instance(**params)

            execution_time = (time.time() - start_time) * 1000

            result = SkillExecutionResult(
                skill_id=skill_id,
                success=True,
                result=output,
                execution_time_ms=execution_time
            )

        except Exception as e:
            execution_time = (time.time() - start_time) * 1000
            result = SkillExecutionResult(
                skill_id=skill_id,
                success=False,
                result=None,
                error=str(e),
                execution_time_ms=execution_time
            )

        self.execution_log.append(result)
        logger.info(f"Skill {skill_id} executed: success={result.success}, time={result.execution_time_ms:.1f}ms")

        return result

    def get_available_skills(self, current_rank: Optional[int] = None) -> List[SkillConfig]:
        """
        Get skills available at the current rank.

        Args:
            current_rank: Current AIVA rank (or get from rank_tracker)

        Returns:
            List of available skills
        """
        if current_rank is None and self.rank_tracker:
            current_rank = self.rank_tracker.current_rank.value
        elif current_rank is None:
            current_rank = 1

        return [
            s for s in self.skills.values()
            if s.status == SkillStatus.ACTIVE and s.required_rank <= current_rank
        ]

    def get_execution_stats(self) -> Dict:
        """Get execution statistics."""
        if not self.execution_log:
            return {"total": 0, "success": 0, "failure": 0, "success_rate": 0.0}

        total = len(self.execution_log)
        success = sum(1 for r in self.execution_log if r.success)
        avg_time = sum(r.execution_time_ms for r in self.execution_log) / total

        return {
            "total": total,
            "success": success,
            "failure": total - success,
            "success_rate": success / total,
            "avg_execution_time_ms": avg_time
        }

    def save_registry(self, path: str) -> bool:
        """Save registry to file."""
        try:
            data = {
                "saved_at": datetime.utcnow().isoformat(),
                "skills": [s.to_dict() for s in self.skills.values()]
            }
            with open(path, "w") as f:
                json.dump(data, f, indent=2)
            return True
        except Exception as e:
            logger.error(f"Failed to save registry: {e}")
            return False


# Singleton instance
_registry: Optional[SkillRegistry] = None


def get_skill_registry() -> SkillRegistry:
    """Get or create singleton SkillRegistry."""
    global _registry
    if _registry is None:
        _registry = SkillRegistry()
    return _registry


if __name__ == "__main__":
    # Example usage
    registry = SkillRegistry()

    print("\n=== AIVA Skill Registry ===\n")

    # List all skills
    for category in SkillCategory:
        skills = registry.list_skills(category=category)
        if skills:
            print(f"\n{category.value.upper()} Skills:")
            for skill in skills:
                print(f"  [{skill.skill_id}] {skill.name}")
                print(f"      Rank: {skill.required_rank} | Permissions: {skill.required_permissions}")

    # Get a specific skill
    skill = registry.get_skill("ghl_mastery")
    if skill:
        print(f"\nSkill Details: {skill.name}")
        print(f"  Description: {skill.description}")
        print(f"  Module: {skill.module_path}.{skill.class_name}")

    # Check available skills at rank 3
    available = registry.get_available_skills(current_rank=3)
    print(f"\nSkills available at Rank 3: {len(available)}")
    for s in available:
        print(f"  - {s.name}")
