"""
ClassLoader service for loading player class definitions from YAML files.

This service reads class configuration files and converts them into PlayerClass
dataclass instances, providing caching for performance.
"""

import yaml
from pathlib import Path
from typing import Dict, List, Optional
import structlog

from app.models.skills import PlayerClass, SkillTree, SkillNode
from app.models.stats import Stats

logger = structlog.get_logger(__name__)


class ClassLoader:
    """
    Loads player class definitions from YAML configuration files.

    This allows game designers to define classes and skill trees without touching code.
    All class definitions are stored in /app/data/classes/ as YAML files.
    """

    def __init__(self, data_dir: Optional[str] = None):
        """
        Initialize the class loader.

        Args:
            data_dir: Path to directory containing class YAML files.
                     Defaults to /app/data/classes/
        """
        if data_dir is None:
            # Default to app/data/classes relative to this file
            current_file = Path(__file__)
            app_dir = current_file.parent.parent  # Go up to /app
            data_dir = str(app_dir / "data" / "classes")

        self.data_dir = Path(data_dir)
        self._class_cache: Dict[str, PlayerClass] = {}

        logger.info("ClassLoader initialized", data_dir=str(self.data_dir))

    def load_class(self, class_id: str) -> Optional[PlayerClass]:
        """
        Load a single player class by ID.

        Args:
            class_id: Unique class identifier (e.g., "vanguard")

        Returns:
            PlayerClass instance or None if not found
        """
        # Check cache first
        if class_id in self._class_cache:
            logger.debug("Class loaded from cache", class_id=class_id)
            return self._class_cache[class_id]

        # Construct file path
        file_path = self.data_dir / f"{class_id}.yaml"

        if not file_path.exists():
            logger.warning("Class file not found", class_id=class_id, file_path=str(file_path))
            return None

        try:
            # Load YAML file
            with open(file_path, 'r') as f:
                data = yaml.safe_load(f)

            # Parse into PlayerClass
            player_class = self._parse_class_data(data)

            # Cache the result
            self._class_cache[class_id] = player_class

            logger.info("Class loaded successfully", class_id=class_id)
            return player_class

        except Exception as e:
            logger.error("Failed to load class", class_id=class_id, error=str(e))
            return None

    def load_all_classes(self) -> List[PlayerClass]:
        """
        Load all player classes from the data directory.

        Returns:
            List of PlayerClass instances
        """
        classes = []

        # Find all YAML files in the directory
        if not self.data_dir.exists():
            logger.error("Class data directory does not exist", data_dir=str(self.data_dir))
            return classes

        for file_path in self.data_dir.glob("*.yaml"):
            class_id = file_path.stem  # Get filename without extension
            player_class = self.load_class(class_id)
            if player_class:
                classes.append(player_class)

        logger.info("All classes loaded", count=len(classes))
        return classes

    def get_class_by_id(self, class_id: str) -> Optional[PlayerClass]:
        """
        Get a player class by ID (alias for load_class).

        Args:
            class_id: Unique class identifier

        Returns:
            PlayerClass instance or None if not found
        """
        return self.load_class(class_id)

    def get_all_class_ids(self) -> List[str]:
        """
        Get a list of all available class IDs.

        Returns:
            List of class IDs (e.g., ["vanguard", "assassin", "arcanist"])
        """
        if not self.data_dir.exists():
            return []

        return [file_path.stem for file_path in self.data_dir.glob("*.yaml")]

    def reload_class(self, class_id: str) -> Optional[PlayerClass]:
        """
        Force reload a class from disk, bypassing cache.

        Useful for development/testing when class definitions change.

        Args:
            class_id: Unique class identifier

        Returns:
            PlayerClass instance or None if not found
        """
        # Remove from cache if present
        if class_id in self._class_cache:
            del self._class_cache[class_id]

        return self.load_class(class_id)

    def clear_cache(self):
        """Clear the class cache. Useful for testing."""
        self._class_cache.clear()
        logger.info("Class cache cleared")

    def _parse_class_data(self, data: Dict) -> PlayerClass:
        """
        Parse YAML data into a PlayerClass dataclass.

        Args:
            data: Dictionary loaded from YAML file

        Returns:
            PlayerClass instance

        Raises:
            ValueError: If data is invalid or missing required fields
        """
        # Validate required fields
        required_fields = ["class_id", "name", "description", "base_stats", "skill_trees"]
        for field in required_fields:
            if field not in data:
                raise ValueError(f"Missing required field: {field}")

        # Parse base stats
        base_stats = Stats(**data["base_stats"])

        # Parse skill trees
        skill_trees = []
        for tree_data in data["skill_trees"]:
            skill_tree = self._parse_skill_tree(tree_data)
            skill_trees.append(skill_tree)

        # Get optional fields
        starting_equipment = data.get("starting_equipment", [])
        starting_abilities = data.get("starting_abilities", [])

        # Create PlayerClass instance
        player_class = PlayerClass(
            class_id=data["class_id"],
            name=data["name"],
            description=data["description"],
            base_stats=base_stats,
            skill_trees=skill_trees,
            starting_equipment=starting_equipment,
            starting_abilities=starting_abilities
        )

        return player_class

    def _parse_skill_tree(self, tree_data: Dict) -> SkillTree:
        """
        Parse a skill tree from YAML data.

        Args:
            tree_data: Dictionary containing skill tree data

        Returns:
            SkillTree instance
        """
        # Validate required fields
        required_fields = ["tree_id", "name", "description", "nodes"]
        for field in required_fields:
            if field not in tree_data:
                raise ValueError(f"Missing required field in skill tree: {field}")

        # Parse skill nodes
        nodes = []
        for node_data in tree_data["nodes"]:
            skill_node = self._parse_skill_node(node_data)
            nodes.append(skill_node)

        # Create SkillTree instance
        skill_tree = SkillTree(
            tree_id=tree_data["tree_id"],
            name=tree_data["name"],
            description=tree_data["description"],
            nodes=nodes
        )

        return skill_tree

    def _parse_skill_node(self, node_data: Dict) -> SkillNode:
        """
        Parse a skill node from YAML data.

        Args:
            node_data: Dictionary containing skill node data

        Returns:
            SkillNode instance
        """
        # Validate required fields
        required_fields = ["skill_id", "name", "description", "tier", "effects"]
        for field in required_fields:
            if field not in node_data:
                raise ValueError(f"Missing required field in skill node: {field}")

        # Create SkillNode instance
        skill_node = SkillNode(
            skill_id=node_data["skill_id"],
            name=node_data["name"],
            description=node_data["description"],
            tier=node_data["tier"],
            prerequisites=node_data.get("prerequisites", []),
            effects=node_data.get("effects", {}),
            unlocked=False  # Always start locked
        )

        return skill_node


# Global instance for convenience
_loader_instance: Optional[ClassLoader] = None


def get_class_loader() -> ClassLoader:
    """
    Get the global ClassLoader instance.

    Returns:
        Singleton ClassLoader instance
    """
    global _loader_instance
    if _loader_instance is None:
        _loader_instance = ClassLoader()
    return _loader_instance
