"""
NPC data models for persistent non-player characters.

This module defines NPC and related dataclasses that represent structured
NPC definitions loaded from YAML files. NPCs have rich personality, knowledge,
and interaction data that the AI uses for dialogue generation.
"""

from dataclasses import dataclass, field
from typing import Dict, List, Any, Optional


@dataclass
class NPCPersonality:
    """
    NPC personality definition for AI dialogue generation.

    Provides the AI with guidance on how to roleplay the NPC's character,
    including their general traits, speaking patterns, and distinctive behaviors.

    Attributes:
        traits: List of personality descriptors (e.g., "gruff", "kind", "suspicious")
        speech_style: Description of how the NPC speaks (accent, vocabulary, patterns)
        quirks: List of distinctive behaviors or habits
    """

    traits: List[str]
    speech_style: str
    quirks: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Serialize personality to dictionary."""
        return {
            "traits": self.traits,
            "speech_style": self.speech_style,
            "quirks": self.quirks,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCPersonality':
        """Deserialize personality from dictionary."""
        return cls(
            traits=data.get("traits", []),
            speech_style=data.get("speech_style", ""),
            quirks=data.get("quirks", []),
        )


@dataclass
class NPCAppearance:
    """
    NPC physical description.

    Provides visual context for AI narration and player information.

    Attributes:
        brief: Short one-line description for lists and quick reference
        detailed: Optional longer description for detailed encounters
    """

    brief: str
    detailed: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize appearance to dictionary."""
        return {
            "brief": self.brief,
            "detailed": self.detailed,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCAppearance':
        """Deserialize appearance from dictionary."""
        if isinstance(data, str):
            # Handle simple string format
            return cls(brief=data)
        return cls(
            brief=data.get("brief", ""),
            detailed=data.get("detailed"),
        )


@dataclass
class NPCKnowledgeCondition:
    """
    Condition for NPC to reveal secret knowledge.

    Defines when and how an NPC will share information they normally keep hidden.
    Conditions are evaluated against the character's interaction state.

    Attributes:
        condition: Expression describing what triggers the reveal
                  (e.g., "interaction_count >= 3", "relationship_level >= 75")
        reveals: The information that gets revealed when condition is met
    """

    condition: str
    reveals: str

    def to_dict(self) -> Dict[str, Any]:
        """Serialize condition to dictionary."""
        return {
            "condition": self.condition,
            "reveals": self.reveals,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCKnowledgeCondition':
        """Deserialize condition from dictionary."""
        return cls(
            condition=data.get("condition", ""),
            reveals=data.get("reveals", ""),
        )


@dataclass
class NPCKnowledge:
    """
    Knowledge an NPC possesses - public and secret.

    Organizes what information an NPC knows and under what circumstances
    they will share it with players.

    Attributes:
        public: Knowledge the NPC will freely share with anyone
        secret: Knowledge the NPC keeps hidden (for AI reference only)
        will_share_if: Conditional reveals based on character interaction state
    """

    public: List[str] = field(default_factory=list)
    secret: List[str] = field(default_factory=list)
    will_share_if: List[NPCKnowledgeCondition] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Serialize knowledge to dictionary."""
        return {
            "public": self.public,
            "secret": self.secret,
            "will_share_if": [c.to_dict() for c in self.will_share_if],
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCKnowledge':
        """Deserialize knowledge from dictionary."""
        conditions = [
            NPCKnowledgeCondition.from_dict(c)
            for c in data.get("will_share_if", [])
        ]
        return cls(
            public=data.get("public", []),
            secret=data.get("secret", []),
            will_share_if=conditions,
        )


@dataclass
class NPCRelationship:
    """
    NPC's relationship with another NPC.

    Defines how this NPC feels about other NPCs in the world,
    providing context for dialogue and interactions.

    Attributes:
        npc_id: The other NPC's identifier
        attitude: How this NPC feels (e.g., "friendly", "distrustful", "romantic")
        reason: Optional explanation for the attitude
    """

    npc_id: str
    attitude: str
    reason: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize relationship to dictionary."""
        return {
            "npc_id": self.npc_id,
            "attitude": self.attitude,
            "reason": self.reason,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCRelationship':
        """Deserialize relationship from dictionary."""
        return cls(
            npc_id=data["npc_id"],
            attitude=data["attitude"],
            reason=data.get("reason"),
        )


@dataclass
class NPCInventoryItem:
    """
    Item an NPC has for sale.

    Defines items available for purchase from merchant NPCs.

    Attributes:
        item_id: Reference to item definition
        price: Cost in gold
        quantity: Stock count (None = unlimited)
    """

    item_id: str
    price: int
    quantity: Optional[int] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize inventory item to dictionary."""
        return {
            "item_id": self.item_id,
            "price": self.price,
            "quantity": self.quantity,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCInventoryItem':
        """Deserialize inventory item from dictionary."""
        # Handle shorthand format: { item: "ale", price: 2 }
        item_id = data.get("item_id") or data.get("item", "")
        return cls(
            item_id=item_id,
            price=data.get("price", 0),
            quantity=data.get("quantity"),
        )


@dataclass
class NPCDialogueHooks:
    """
    Pre-defined dialogue snippets for AI context.

    Provides example phrases the AI can use or adapt to maintain
    consistent NPC voice across conversations.

    Attributes:
        greeting: What NPC says when first addressed
        farewell: What NPC says when conversation ends
        busy: What NPC says when occupied or dismissive
        quest_complete: What NPC says when player completes their quest
    """

    greeting: Optional[str] = None
    farewell: Optional[str] = None
    busy: Optional[str] = None
    quest_complete: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize dialogue hooks to dictionary."""
        return {
            "greeting": self.greeting,
            "farewell": self.farewell,
            "busy": self.busy,
            "quest_complete": self.quest_complete,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCDialogueHooks':
        """Deserialize dialogue hooks from dictionary."""
        return cls(
            greeting=data.get("greeting"),
            farewell=data.get("farewell"),
            busy=data.get("busy"),
            quest_complete=data.get("quest_complete"),
        )


@dataclass
class NPC:
    """
    Persistent NPC definition.

    NPCs are fixed to locations and have rich personality, knowledge,
    and interaction data used by the AI for dialogue generation.

    Attributes:
        npc_id: Unique identifier (e.g., "npc_grom_001")
        name: Display name (e.g., "Grom Ironbeard")
        role: NPC's job/title (e.g., "bartender", "blacksmith")
        location_id: ID of location where this NPC resides
        personality: Personality traits and speech patterns
        appearance: Physical description
        knowledge: What the NPC knows (public and secret)
        relationships: How NPC feels about other NPCs
        inventory_for_sale: Items NPC sells (if merchant)
        dialogue_hooks: Pre-defined dialogue snippets
        quest_giver_for: Quest IDs this NPC can give
        reveals_locations: Location IDs this NPC can unlock through conversation
        tags: Metadata tags for filtering (e.g., "merchant", "quest_giver")
    """

    npc_id: str
    name: str
    role: str
    location_id: str
    personality: NPCPersonality
    appearance: NPCAppearance
    knowledge: Optional[NPCKnowledge] = None
    relationships: List[NPCRelationship] = field(default_factory=list)
    inventory_for_sale: List[NPCInventoryItem] = field(default_factory=list)
    dialogue_hooks: Optional[NPCDialogueHooks] = None
    quest_giver_for: List[str] = field(default_factory=list)
    reveals_locations: List[str] = field(default_factory=list)
    tags: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """
        Serialize NPC to dictionary for JSON responses.

        Returns:
            Dictionary containing all NPC data
        """
        return {
            "npc_id": self.npc_id,
            "name": self.name,
            "role": self.role,
            "location_id": self.location_id,
            "personality": self.personality.to_dict(),
            "appearance": self.appearance.to_dict(),
            "knowledge": self.knowledge.to_dict() if self.knowledge else None,
            "relationships": [r.to_dict() for r in self.relationships],
            "inventory_for_sale": [i.to_dict() for i in self.inventory_for_sale],
            "dialogue_hooks": self.dialogue_hooks.to_dict() if self.dialogue_hooks else None,
            "quest_giver_for": self.quest_giver_for,
            "reveals_locations": self.reveals_locations,
            "tags": self.tags,
        }

    def to_story_dict(self) -> Dict[str, Any]:
        """
        Serialize NPC for AI dialogue context.

        Returns a trimmed version focused on roleplay-relevant data
        to reduce token usage in AI prompts.

        Returns:
            Dictionary containing story-relevant NPC data
        """
        result = {
            "name": self.name,
            "role": self.role,
            "personality": {
                "traits": self.personality.traits,
                "speech_style": self.personality.speech_style,
                "quirks": self.personality.quirks,
            },
            "appearance": self.appearance.brief,
        }

        # Include dialogue hooks if available
        if self.dialogue_hooks:
            result["dialogue_hooks"] = self.dialogue_hooks.to_dict()

        return result

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPC':
        """
        Deserialize NPC from dictionary.

        Args:
            data: Dictionary containing NPC data (from YAML or JSON)

        Returns:
            NPC instance
        """
        # Parse personality
        personality_data = data.get("personality", {})
        personality = NPCPersonality.from_dict(personality_data)

        # Parse appearance
        appearance_data = data.get("appearance", {"brief": ""})
        appearance = NPCAppearance.from_dict(appearance_data)

        # Parse knowledge (optional)
        knowledge = None
        if data.get("knowledge"):
            knowledge = NPCKnowledge.from_dict(data["knowledge"])

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

        # Parse inventory
        inventory = [
            NPCInventoryItem.from_dict(i)
            for i in data.get("inventory_for_sale", [])
        ]

        # Parse dialogue hooks (optional)
        dialogue_hooks = None
        if data.get("dialogue_hooks"):
            dialogue_hooks = NPCDialogueHooks.from_dict(data["dialogue_hooks"])

        return cls(
            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 __repr__(self) -> str:
        """String representation of the NPC."""
        return f"NPC({self.npc_id}, {self.name}, {self.role})"


@dataclass
class NPCInteractionState:
    """
    Tracks a character's interaction history with an NPC.

    Stored on the Character record to persist relationship data
    across multiple game sessions.

    Attributes:
        npc_id: The NPC this state tracks
        first_met: ISO timestamp of first interaction
        last_interaction: ISO timestamp of most recent interaction
        interaction_count: Total number of conversations
        revealed_secrets: Indices of secrets that have been revealed
        relationship_level: 0-100 scale (50 is neutral)
        custom_flags: Arbitrary flags for special conditions
                     (e.g., {"helped_with_rats": true})
    """

    npc_id: str
    first_met: str
    last_interaction: str
    interaction_count: int = 0
    revealed_secrets: List[int] = field(default_factory=list)
    relationship_level: int = 50
    custom_flags: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Serialize interaction state to dictionary."""
        return {
            "npc_id": self.npc_id,
            "first_met": self.first_met,
            "last_interaction": self.last_interaction,
            "interaction_count": self.interaction_count,
            "revealed_secrets": self.revealed_secrets,
            "relationship_level": self.relationship_level,
            "custom_flags": self.custom_flags,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NPCInteractionState':
        """Deserialize interaction state from dictionary."""
        return cls(
            npc_id=data["npc_id"],
            first_met=data["first_met"],
            last_interaction=data["last_interaction"],
            interaction_count=data.get("interaction_count", 0),
            revealed_secrets=data.get("revealed_secrets", []),
            relationship_level=data.get("relationship_level", 50),
            custom_flags=data.get("custom_flags", {}),
        )

    def __repr__(self) -> str:
        """String representation of the interaction state."""
        return (
            f"NPCInteractionState({self.npc_id}, "
            f"interactions={self.interaction_count}, "
            f"relationship={self.relationship_level})"
        )
