"""
NPCLoader service for loading NPC definitions from YAML files.

This service reads NPC configuration files and converts them into NPC
dataclass instances, providing caching for performance. NPCs are organized
by region subdirectories.
"""

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

from app.models.npc import (
    NPC,
    NPCPersonality,
    NPCAppearance,
    NPCKnowledge,
    NPCKnowledgeCondition,
    NPCRelationship,
    NPCInventoryItem,
    NPCDialogueHooks,
)

logger = structlog.get_logger(__name__)


class NPCLoader:
    """
    Loads NPC definitions from YAML configuration files.

    NPCs are organized in region subdirectories:
    /app/data/npcs/
      crossville/
        npc_grom_001.yaml
        npc_mira_001.yaml

    This allows game designers to define NPCs without touching code.
    """

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

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

        self.data_dir = Path(data_dir)
        self._npc_cache: Dict[str, NPC] = {}
        self._location_npc_cache: Dict[str, List[str]] = {}

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

    def load_npc(self, npc_id: str) -> Optional[NPC]:
        """
        Load a single NPC by ID.

        Searches all region subdirectories for the NPC file.

        Args:
            npc_id: Unique NPC identifier (e.g., "npc_grom_001")

        Returns:
            NPC instance or None if not found
        """
        # Check cache first
        if npc_id in self._npc_cache:
            logger.debug("NPC loaded from cache", npc_id=npc_id)
            return self._npc_cache[npc_id]

        # Search in region subdirectories
        if not self.data_dir.exists():
            logger.error("NPC data directory does not exist", data_dir=str(self.data_dir))
            return None

        for region_dir in self.data_dir.iterdir():
            if not region_dir.is_dir():
                continue

            file_path = region_dir / f"{npc_id}.yaml"
            if file_path.exists():
                return self._load_npc_file(file_path)

        logger.warning("NPC not found", npc_id=npc_id)
        return None

    def _load_npc_file(self, file_path: Path) -> Optional[NPC]:
        """
        Load an NPC from a specific file.

        Args:
            file_path: Path to the YAML file

        Returns:
            NPC instance or None if loading fails
        """
        try:
            with open(file_path, 'r') as f:
                data = yaml.safe_load(f)

            npc = self._parse_npc_data(data)
            self._npc_cache[npc.npc_id] = npc

            # Update location cache
            if npc.location_id not in self._location_npc_cache:
                self._location_npc_cache[npc.location_id] = []
            if npc.npc_id not in self._location_npc_cache[npc.location_id]:
                self._location_npc_cache[npc.location_id].append(npc.npc_id)

            logger.info("NPC loaded successfully", npc_id=npc.npc_id)
            return npc

        except Exception as e:
            logger.error("Failed to load NPC", file=str(file_path), error=str(e))
            return None

    def _parse_npc_data(self, data: Dict) -> NPC:
        """
        Parse YAML data into an NPC dataclass.

        Args:
            data: Dictionary loaded from YAML file

        Returns:
            NPC instance

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

        # Parse personality
        personality_data = data.get("personality", {})
        personality = NPCPersonality(
            traits=personality_data.get("traits", []),
            speech_style=personality_data.get("speech_style", ""),
            quirks=personality_data.get("quirks", []),
        )

        # Parse appearance
        appearance_data = data.get("appearance", {})
        if isinstance(appearance_data, str):
            appearance = NPCAppearance(brief=appearance_data)
        else:
            appearance = NPCAppearance(
                brief=appearance_data.get("brief", ""),
                detailed=appearance_data.get("detailed"),
            )

        # Parse knowledge (optional)
        knowledge = None
        if data.get("knowledge"):
            knowledge_data = data["knowledge"]
            conditions = [
                NPCKnowledgeCondition(
                    condition=c.get("condition", ""),
                    reveals=c.get("reveals", ""),
                )
                for c in knowledge_data.get("will_share_if", [])
            ]
            knowledge = NPCKnowledge(
                public=knowledge_data.get("public", []),
                secret=knowledge_data.get("secret", []),
                will_share_if=conditions,
            )

        # Parse relationships
        relationships = [
            NPCRelationship(
                npc_id=r["npc_id"],
                attitude=r["attitude"],
                reason=r.get("reason"),
            )
            for r in data.get("relationships", [])
        ]

        # Parse inventory
        inventory = []
        for item_data in data.get("inventory_for_sale", []):
            # Handle shorthand format: { item: "ale", price: 2 }
            item_id = item_data.get("item_id") or item_data.get("item", "")
            inventory.append(NPCInventoryItem(
                item_id=item_id,
                price=item_data.get("price", 0),
                quantity=item_data.get("quantity"),
            ))

        # Parse dialogue hooks (optional)
        dialogue_hooks = None
        if data.get("dialogue_hooks"):
            hooks_data = data["dialogue_hooks"]
            dialogue_hooks = NPCDialogueHooks(
                greeting=hooks_data.get("greeting"),
                farewell=hooks_data.get("farewell"),
                busy=hooks_data.get("busy"),
                quest_complete=hooks_data.get("quest_complete"),
            )

        return NPC(
            npc_id=data["npc_id"],
            name=data["name"],
            role=data["role"],
            location_id=data["location_id"],
            personality=personality,
            appearance=appearance,
            knowledge=knowledge,
            relationships=relationships,
            inventory_for_sale=inventory,
            dialogue_hooks=dialogue_hooks,
            quest_giver_for=data.get("quest_giver_for", []),
            reveals_locations=data.get("reveals_locations", []),
            tags=data.get("tags", []),
        )

    def load_all_npcs(self) -> List[NPC]:
        """
        Load all NPCs from all region directories.

        Returns:
            List of NPC instances
        """
        npcs = []

        if not self.data_dir.exists():
            logger.error("NPC data directory does not exist", data_dir=str(self.data_dir))
            return npcs

        for region_dir in self.data_dir.iterdir():
            if not region_dir.is_dir():
                continue

            for file_path in region_dir.glob("*.yaml"):
                npc = self._load_npc_file(file_path)
                if npc:
                    npcs.append(npc)

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

    def get_npcs_at_location(self, location_id: str) -> List[NPC]:
        """
        Get all NPCs at a specific location.

        Args:
            location_id: Location identifier

        Returns:
            List of NPC instances at this location
        """
        # Ensure all NPCs are loaded
        if not self._npc_cache:
            self.load_all_npcs()

        npc_ids = self._location_npc_cache.get(location_id, [])
        return [
            self._npc_cache[npc_id]
            for npc_id in npc_ids
            if npc_id in self._npc_cache
        ]

    def get_npc_ids_at_location(self, location_id: str) -> List[str]:
        """
        Get NPC IDs at a specific location.

        Args:
            location_id: Location identifier

        Returns:
            List of NPC IDs at this location
        """
        # Ensure all NPCs are loaded
        if not self._npc_cache:
            self.load_all_npcs()

        return self._location_npc_cache.get(location_id, [])

    def get_npcs_by_tag(self, tag: str) -> List[NPC]:
        """
        Get all NPCs with a specific tag.

        Args:
            tag: Tag to filter by (e.g., "merchant", "quest_giver")

        Returns:
            List of NPC instances with this tag
        """
        # Ensure all NPCs are loaded
        if not self._npc_cache:
            self.load_all_npcs()

        return [
            npc for npc in self._npc_cache.values()
            if tag in npc.tags
        ]

    def get_quest_givers(self, quest_id: str) -> List[NPC]:
        """
        Get all NPCs that can give a specific quest.

        Args:
            quest_id: Quest identifier

        Returns:
            List of NPC instances that give this quest
        """
        # Ensure all NPCs are loaded
        if not self._npc_cache:
            self.load_all_npcs()

        return [
            npc for npc in self._npc_cache.values()
            if quest_id in npc.quest_giver_for
        ]

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

        Returns:
            List of NPC IDs
        """
        # Ensure all NPCs are loaded
        if not self._npc_cache:
            self.load_all_npcs()

        return list(self._npc_cache.keys())

    def reload_npc(self, npc_id: str) -> Optional[NPC]:
        """
        Force reload an NPC from disk, bypassing cache.

        Useful for development/testing when NPC definitions change.

        Args:
            npc_id: Unique NPC identifier

        Returns:
            NPC instance or None if not found
        """
        # Remove from caches if present
        if npc_id in self._npc_cache:
            old_npc = self._npc_cache[npc_id]
            # Remove from location cache
            if old_npc.location_id in self._location_npc_cache:
                self._location_npc_cache[old_npc.location_id] = [
                    n for n in self._location_npc_cache[old_npc.location_id]
                    if n != npc_id
                ]
            del self._npc_cache[npc_id]

        return self.load_npc(npc_id)

    def clear_cache(self) -> None:
        """Clear all cached data. Useful for testing."""
        self._npc_cache.clear()
        self._location_npc_cache.clear()
        logger.info("NPC cache cleared")


# Global singleton instance
_loader_instance: Optional[NPCLoader] = None


def get_npc_loader() -> NPCLoader:
    """
    Get the global NPCLoader instance.

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