"""
Dice mechanics module for Code of Conquest.

This module provides core dice rolling functionality using a D20 + modifier vs DC system.
All game chance mechanics (searches, skill checks, etc.) use these functions to determine
outcomes before passing results to AI for narration.
"""

import random
from dataclasses import dataclass
from typing import Optional
from enum import Enum


class Difficulty(Enum):
    """Standard difficulty classes for skill checks."""
    TRIVIAL = 5
    EASY = 10
    MEDIUM = 15
    HARD = 20
    VERY_HARD = 25
    NEARLY_IMPOSSIBLE = 30


class SkillType(Enum):
    """
    Skill types and their associated base stats.

    Each skill maps to a core stat for modifier calculation.
    """
    # Wisdom-based
    PERCEPTION = "wisdom"
    INSIGHT = "wisdom"
    SURVIVAL = "wisdom"
    MEDICINE = "wisdom"

    # Dexterity-based
    STEALTH = "dexterity"
    ACROBATICS = "dexterity"
    SLEIGHT_OF_HAND = "dexterity"
    LOCKPICKING = "dexterity"

    # Charisma-based
    PERSUASION = "charisma"
    DECEPTION = "charisma"
    INTIMIDATION = "charisma"
    PERFORMANCE = "charisma"

    # Strength-based
    ATHLETICS = "strength"

    # Intelligence-based
    ARCANA = "intelligence"
    HISTORY = "intelligence"
    INVESTIGATION = "intelligence"
    NATURE = "intelligence"
    RELIGION = "intelligence"

    # Constitution-based
    ENDURANCE = "constitution"


@dataclass
class CheckResult:
    """
    Result of a dice check.

    Contains all information needed for UI display (dice roll animation)
    and game logic (success/failure determination).

    Attributes:
        roll: The natural d20 roll (1-20)
        modifier: Total modifier from stats
        total: roll + modifier
        dc: Difficulty class that was checked against
        success: Whether the check succeeded
        margin: How much the check succeeded or failed by (total - dc)
        skill_type: The skill used for this check (if applicable)
    """
    roll: int
    modifier: int
    total: int
    dc: int
    success: bool
    margin: int
    skill_type: Optional[str] = None

    @property
    def is_critical_success(self) -> bool:
        """Natural 20 - only relevant for combat."""
        return self.roll == 20

    @property
    def is_critical_failure(self) -> bool:
        """Natural 1 - only relevant for combat."""
        return self.roll == 1

    def to_dict(self) -> dict:
        """Serialize for API response."""
        return {
            "roll": self.roll,
            "modifier": self.modifier,
            "total": self.total,
            "dc": self.dc,
            "success": self.success,
            "margin": self.margin,
            "skill_type": self.skill_type,
        }


def roll_d20() -> int:
    """
    Roll a standard 20-sided die.

    Returns:
        Integer from 1 to 20 (inclusive)
    """
    return random.randint(1, 20)


def calculate_modifier(stat_value: int) -> int:
    """
    Calculate the D&D-style modifier from a stat value.

    Formula: (stat - 10) // 2

    Examples:
        - Stat 10 = +0 modifier
        - Stat 14 = +2 modifier
        - Stat 18 = +4 modifier
        - Stat 8 = -1 modifier

    Args:
        stat_value: The raw stat value (typically 1-20)

    Returns:
        The modifier value (can be negative)
    """
    return (stat_value - 10) // 2


def skill_check(
    stat_value: int,
    dc: int,
    skill_type: Optional[SkillType] = None,
    bonus: int = 0
) -> CheckResult:
    """
    Perform a skill check: d20 + modifier vs DC.

    Args:
        stat_value: The relevant stat value (e.g., character's wisdom for perception)
        dc: Difficulty class to beat
        skill_type: Optional skill type for logging/display
        bonus: Additional bonus (e.g., from equipment or proficiency)

    Returns:
        CheckResult with full details of the roll
    """
    roll = roll_d20()
    modifier = calculate_modifier(stat_value) + bonus
    total = roll + modifier
    success = total >= dc
    margin = total - dc

    return CheckResult(
        roll=roll,
        modifier=modifier,
        total=total,
        dc=dc,
        success=success,
        margin=margin,
        skill_type=skill_type.name if skill_type else None
    )


def get_stat_for_skill(skill_type: SkillType) -> str:
    """
    Get the base stat name for a skill type.

    Args:
        skill_type: The skill to look up

    Returns:
        The stat name (e.g., "wisdom", "dexterity")
    """
    return skill_type.value


def perception_check(wisdom: int, dc: int, bonus: int = 0) -> CheckResult:
    """
    Convenience function for perception checks (searching, spotting).

    Args:
        wisdom: Character's wisdom stat
        dc: Difficulty class
        bonus: Additional bonus

    Returns:
        CheckResult
    """
    return skill_check(wisdom, dc, SkillType.PERCEPTION, bonus)


def stealth_check(dexterity: int, dc: int, bonus: int = 0) -> CheckResult:
    """
    Convenience function for stealth checks (sneaking, hiding).

    Args:
        dexterity: Character's dexterity stat
        dc: Difficulty class
        bonus: Additional bonus

    Returns:
        CheckResult
    """
    return skill_check(dexterity, dc, SkillType.STEALTH, bonus)


def persuasion_check(charisma: int, dc: int, bonus: int = 0) -> CheckResult:
    """
    Convenience function for persuasion checks (convincing, negotiating).

    Args:
        charisma: Character's charisma stat
        dc: Difficulty class
        bonus: Additional bonus

    Returns:
        CheckResult
    """
    return skill_check(charisma, dc, SkillType.PERSUASION, bonus)


def lockpicking_check(dexterity: int, dc: int, bonus: int = 0) -> CheckResult:
    """
    Convenience function for lockpicking checks.

    Args:
        dexterity: Character's dexterity stat
        dc: Difficulty class
        bonus: Additional bonus

    Returns:
        CheckResult
    """
    return skill_check(dexterity, dc, SkillType.LOCKPICKING, bonus)
