"""
Outcome determination service for Code of Conquest.

This service handles all code-determined game outcomes before they're passed
to AI for narration. It uses the dice mechanics system to determine success/failure
and selects appropriate rewards from loot tables.
"""

import random
import yaml
import structlog
from pathlib import Path
from dataclasses import dataclass
from typing import Optional, List, Dict, Any

from app.models.character import Character
from app.game_logic.dice import (
    CheckResult, SkillType, Difficulty,
    skill_check, get_stat_for_skill, perception_check
)


logger = structlog.get_logger(__name__)


@dataclass
class ItemFound:
    """
    Represents an item found during a search.

    Uses template key from generic_items.yaml.
    """
    template_key: str
    name: str
    description: str
    value: int

    def to_dict(self) -> dict:
        """Serialize for API response."""
        return {
            "template_key": self.template_key,
            "name": self.name,
            "description": self.description,
            "value": self.value,
        }


@dataclass
class SearchOutcome:
    """
    Complete result of a search action.

    Includes the dice check result and any items/gold found.
    """
    check_result: CheckResult
    items_found: List[ItemFound]
    gold_found: int

    def to_dict(self) -> dict:
        """Serialize for API response."""
        return {
            "check_result": self.check_result.to_dict(),
            "items_found": [item.to_dict() for item in self.items_found],
            "gold_found": self.gold_found,
        }


@dataclass
class SkillCheckOutcome:
    """
    Result of a generic skill check.

    Used for persuasion, lockpicking, stealth, etc.
    """
    check_result: CheckResult
    context: Dict[str, Any]  # Additional context for AI narration

    def to_dict(self) -> dict:
        """Serialize for API response."""
        return {
            "check_result": self.check_result.to_dict(),
            "context": self.context,
        }


class OutcomeService:
    """
    Service for determining game action outcomes.

    Handles all dice rolls and loot selection before passing to AI.
    """

    def __init__(self):
        """Initialize the outcome service with loot tables and item templates."""
        self._loot_tables: Dict[str, Any] = {}
        self._item_templates: Dict[str, Any] = {}
        self._load_data()

    def _load_data(self) -> None:
        """Load loot tables and item templates from YAML files."""
        data_dir = Path(__file__).parent.parent / "data"

        # Load loot tables
        loot_path = data_dir / "loot_tables.yaml"
        if loot_path.exists():
            with open(loot_path, "r") as f:
                self._loot_tables = yaml.safe_load(f)
            logger.info("loaded_loot_tables", count=len(self._loot_tables))
        else:
            logger.warning("loot_tables_not_found", path=str(loot_path))

        # Load generic item templates
        items_path = data_dir / "generic_items.yaml"
        if items_path.exists():
            with open(items_path, "r") as f:
                data = yaml.safe_load(f)
                self._item_templates = data.get("templates", {})
            logger.info("loaded_item_templates", count=len(self._item_templates))
        else:
            logger.warning("item_templates_not_found", path=str(items_path))

    def determine_search_outcome(
        self,
        character: Character,
        location_type: str,
        dc: int = 12,
        bonus: int = 0
    ) -> SearchOutcome:
        """
        Determine the outcome of a search action.

        Uses a perception check to determine success, then selects items
        from the appropriate loot table based on the roll margin.

        Args:
            character: The character performing the search
            location_type: Type of location (forest, cave, town, etc.)
            dc: Difficulty class (default 12 = easy-medium)
            bonus: Additional bonus to the check

        Returns:
            SearchOutcome with check result, items found, and gold found
        """
        # Get character's effective wisdom for perception
        effective_stats = character.get_effective_stats()
        wisdom = effective_stats.wisdom

        # Perform the perception check
        check_result = perception_check(wisdom, dc, bonus)

        # Determine loot based on result
        items_found: List[ItemFound] = []
        gold_found: int = 0

        if check_result.success:
            # Get loot table for this location (fall back to default)
            loot_table = self._loot_tables.get(
                location_type.lower(),
                self._loot_tables.get("default", {})
            )

            # Select item rarity based on margin
            if check_result.margin >= 10:
                rarity = "rare"
            elif check_result.margin >= 5:
                rarity = "uncommon"
            else:
                rarity = "common"

            # Get items for this rarity
            item_keys = loot_table.get(rarity, [])
            if item_keys:
                # Select 1-2 items based on margin
                num_items = 1 if check_result.margin < 8 else 2
                selected_keys = random.sample(
                    item_keys,
                    min(num_items, len(item_keys))
                )

                for key in selected_keys:
                    template = self._item_templates.get(key)
                    if template:
                        items_found.append(ItemFound(
                            template_key=key,
                            name=template.get("name", key.title()),
                            description=template.get("description", ""),
                            value=template.get("value", 1),
                        ))

            # Calculate gold found
            gold_config = loot_table.get("gold", {})
            if gold_config:
                min_gold = gold_config.get("min", 0)
                max_gold = gold_config.get("max", 10)
                bonus_per_margin = gold_config.get("bonus_per_margin", 0)

                base_gold = random.randint(min_gold, max_gold)
                margin_bonus = check_result.margin * bonus_per_margin
                gold_found = base_gold + margin_bonus

        logger.info(
            "search_outcome_determined",
            character_id=character.character_id,
            location_type=location_type,
            dc=dc,
            success=check_result.success,
            margin=check_result.margin,
            items_count=len(items_found),
            gold_found=gold_found
        )

        return SearchOutcome(
            check_result=check_result,
            items_found=items_found,
            gold_found=gold_found
        )

    def determine_skill_check_outcome(
        self,
        character: Character,
        skill_type: SkillType,
        dc: int,
        bonus: int = 0,
        context: Optional[Dict[str, Any]] = None
    ) -> SkillCheckOutcome:
        """
        Determine the outcome of a generic skill check.

        Args:
            character: The character performing the check
            skill_type: The type of skill check (PERSUASION, STEALTH, etc.)
            dc: Difficulty class to beat
            bonus: Additional bonus to the check
            context: Optional context for AI narration (e.g., NPC name, door type)

        Returns:
            SkillCheckOutcome with check result and context
        """
        # Get the appropriate stat for this skill
        stat_name = get_stat_for_skill(skill_type)
        effective_stats = character.get_effective_stats()
        stat_value = getattr(effective_stats, stat_name, 10)

        # Perform the check
        check_result = skill_check(stat_value, dc, skill_type, bonus)

        # Build outcome context
        outcome_context = context or {}
        outcome_context["skill_used"] = skill_type.name.lower()
        outcome_context["stat_used"] = stat_name

        logger.info(
            "skill_check_outcome_determined",
            character_id=character.character_id,
            skill=skill_type.name,
            stat=stat_name,
            dc=dc,
            success=check_result.success,
            margin=check_result.margin
        )

        return SkillCheckOutcome(
            check_result=check_result,
            context=outcome_context
        )

    def determine_persuasion_outcome(
        self,
        character: Character,
        dc: int,
        npc_name: Optional[str] = None,
        bonus: int = 0
    ) -> SkillCheckOutcome:
        """
        Convenience method for persuasion checks.

        Args:
            character: The character attempting persuasion
            dc: Difficulty class based on NPC disposition
            npc_name: Name of the NPC being persuaded
            bonus: Additional bonus

        Returns:
            SkillCheckOutcome
        """
        context = {"npc_name": npc_name} if npc_name else {}
        return self.determine_skill_check_outcome(
            character,
            SkillType.PERSUASION,
            dc,
            bonus,
            context
        )

    def determine_stealth_outcome(
        self,
        character: Character,
        dc: int,
        situation: Optional[str] = None,
        bonus: int = 0
    ) -> SkillCheckOutcome:
        """
        Convenience method for stealth checks.

        Args:
            character: The character attempting stealth
            dc: Difficulty class based on environment/observers
            situation: Description of what they're sneaking past
            bonus: Additional bonus

        Returns:
            SkillCheckOutcome
        """
        context = {"situation": situation} if situation else {}
        return self.determine_skill_check_outcome(
            character,
            SkillType.STEALTH,
            dc,
            bonus,
            context
        )

    def determine_lockpicking_outcome(
        self,
        character: Character,
        dc: int,
        lock_description: Optional[str] = None,
        bonus: int = 0
    ) -> SkillCheckOutcome:
        """
        Convenience method for lockpicking checks.

        Args:
            character: The character attempting to pick the lock
            dc: Difficulty class based on lock quality
            lock_description: Description of the lock/door
            bonus: Additional bonus (e.g., from thieves' tools)

        Returns:
            SkillCheckOutcome
        """
        context = {"lock_description": lock_description} if lock_description else {}
        return self.determine_skill_check_outcome(
            character,
            SkillType.LOCKPICKING,
            dc,
            bonus,
            context
        )

    def get_dc_for_difficulty(self, difficulty: str) -> int:
        """
        Get the DC value for a named difficulty.

        Args:
            difficulty: Difficulty name (trivial, easy, medium, hard, very_hard)

        Returns:
            DC value
        """
        difficulty_map = {
            "trivial": Difficulty.TRIVIAL.value,
            "easy": Difficulty.EASY.value,
            "medium": Difficulty.MEDIUM.value,
            "hard": Difficulty.HARD.value,
            "very_hard": Difficulty.VERY_HARD.value,
            "nearly_impossible": Difficulty.NEARLY_IMPOSSIBLE.value,
        }
        return difficulty_map.get(difficulty.lower(), Difficulty.MEDIUM.value)


# Global instance for use in API endpoints
outcome_service = OutcomeService()
