"""
Dice Roll Type Definitions

Defines all baseball dice roll types with their structures and validation.
Supports both SBA and PD leagues with league-specific logic.
"""

from dataclasses import dataclass, field
from enum import Enum
from uuid import UUID

import pendulum


class RollType(str, Enum):
    """Types of dice rolls in baseball gameplay"""

    AB = "ab"  # At-bat roll
    JUMP = "jump"  # Baserunning jump
    FIELDING = "fielding"  # Defensive fielding check
    D20 = "d20"  # Generic d20 roll


@dataclass
class DiceRoll:
    """
    Base class for all dice rolls

    Includes auditing fields for analytics and game recovery.
    """

    roll_id: str
    roll_type: RollType
    league_id: str  # 'sba' or 'pd'
    timestamp: pendulum.DateTime
    game_id: UUID | None = field(default=None)

    # Auditing fields for analytics
    team_id: int | None = field(default=None)  # Team making the roll
    player_id: int | None = field(
        default=None
    )  # Polymorphic: Lineup.player_id (SBA) or Lineup.card_id (PD)
    context: dict | None = field(default=None)  # Additional metadata (JSONB storage)

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization"""
        return {
            "roll_id": self.roll_id,
            "roll_type": self.roll_type.value,
            "league_id": self.league_id,
            "timestamp": self.timestamp.to_iso8601_string(),
            "game_id": str(self.game_id) if self.game_id else None,
            "team_id": self.team_id,
            "player_id": self.player_id,
            "context": self.context,
        }


@dataclass(kw_only=True)
class AbRoll(DiceRoll):
    """
    At-bat roll: 1d6 + 2d6 + 2d20

    Flow:
    1. Roll chaos_d20 first
    2. If chaos_d20 == 1: 5% chance - check wild pitch using resolution_d20
    3. If chaos_d20 == 2: 5% chance - check passed ball using resolution_d20
    4. If chaos_d20 >= 3: Use chaos_d20 for at-bat result, resolution_d20 for split results
    """

    # Required fields (no defaults)
    d6_one: int  # First d6 (1-6)
    d6_two_a: int  # First die of 2d6 pair
    d6_two_b: int  # Second die of 2d6 pair
    chaos_d20: int  # First d20 - chaos die (1=WP check, 2=PB check, 3+=normal)
    resolution_d20: int  # Second d20 - for WP/PB resolution or split results

    # Derived values with defaults (calculated in __post_init__)
    d6_two_total: int = field(default=0)  # Sum of 2d6
    check_wild_pitch: bool = field(
        default=False
    )  # chaos_d20 == 1 (still needs resolution_d20 to confirm)
    check_passed_ball: bool = field(
        default=False
    )  # chaos_d20 == 2 (still needs resolution_d20 to confirm)

    # Flag to indicate chaos check was skipped (no runners on base)
    chaos_check_skipped: bool = field(default=False)

    def __post_init__(self):
        """Calculate derived values"""
        self.d6_two_total = self.d6_two_a + self.d6_two_b

        # Only check for WP/PB if chaos check wasn't skipped (runners on base)
        if self.chaos_check_skipped:
            self.check_wild_pitch = False
            self.check_passed_ball = False
        else:
            self.check_wild_pitch = self.chaos_d20 == 1
            self.check_passed_ball = self.chaos_d20 == 2

    def to_dict(self) -> dict:
        base = super().to_dict()
        base.update(
            {
                "d6_one": self.d6_one,
                "d6_two_a": self.d6_two_a,
                "d6_two_b": self.d6_two_b,
                "d6_two_total": self.d6_two_total,
                "chaos_d20": self.chaos_d20,
                "resolution_d20": self.resolution_d20,
                "check_wild_pitch": self.check_wild_pitch,
                "check_passed_ball": self.check_passed_ball,
                "chaos_check_skipped": self.chaos_check_skipped,
            }
        )
        return base

    def __str__(self) -> str:
        """String representation (max 50 chars for DB VARCHAR)"""
        if self.check_wild_pitch:
            return f"WP {self.resolution_d20}"
        if self.check_passed_ball:
            return f"PB {self.resolution_d20}"
        return f"AB {self.d6_one},{self.d6_two_total}({self.d6_two_a}+{self.d6_two_b}) d20={self.resolution_d20}"


@dataclass(kw_only=True)
class JumpRoll(DiceRoll):
    """
    Jump roll for stolen base attempts

    Flow:
    1. Roll check_roll (d20)
    2. If check_roll == 1: Pickoff attempt (roll resolution_roll d20)
    3. If check_roll == 2: Balk check (roll resolution_roll d20)
    4. If check_roll >= 3: Normal jump (roll 2d6 for jump rating)
    """

    # Required field
    check_roll: int  # Initial d20 (1=pickoff, 2=balk, else normal)

    # Optional fields with defaults
    jump_dice_a: int | None = field(default=None)  # First d6 of jump (if normal)
    jump_dice_b: int | None = field(default=None)  # Second d6 of jump (if normal)
    resolution_roll: int | None = field(default=None)  # d20 for pickoff/balk resolution

    # Derived values with defaults (calculated in __post_init__)
    jump_total: int | None = field(default=None)
    is_pickoff_check: bool = field(default=False)
    is_balk_check: bool = field(default=False)

    def __post_init__(self):
        """Calculate derived values"""
        self.is_pickoff_check = self.check_roll == 1
        self.is_balk_check = self.check_roll == 2

        if self.jump_dice_a is not None and self.jump_dice_b is not None:
            self.jump_total = self.jump_dice_a + self.jump_dice_b

    def to_dict(self) -> dict:
        base = super().to_dict()
        base.update(
            {
                "check_roll": self.check_roll,
                "jump_dice_a": self.jump_dice_a,
                "jump_dice_b": self.jump_dice_b,
                "jump_total": self.jump_total,
                "resolution_roll": self.resolution_roll,
                "is_pickoff_check": self.is_pickoff_check,
                "is_balk_check": self.is_balk_check,
            }
        )
        return base

    def __str__(self) -> str:
        if self.is_pickoff_check:
            return f"Jump Roll: Pickoff Check (resolution={self.resolution_roll})"
        if self.is_balk_check:
            return f"Jump Roll: Balk Check (resolution={self.resolution_roll})"
        return f"Jump Roll: {self.jump_total} ({self.jump_dice_a}+{self.jump_dice_b})"


@dataclass(kw_only=True)
class FieldingRoll(DiceRoll):
    """
    Fielding check roll with error dice and rare play check

    Rare Play Triggers:
    - SBA: d100 == 1 (1% chance)
    - PD: error_total == 5 (3d6 sum of 5)
    """

    # Required fields
    position: str  # P, C, 1B, 2B, 3B, SS, LF, CF, RF
    d20: int  # Range roll
    d6_one: int  # Error die 1
    d6_two: int  # Error die 2
    d6_three: int  # Error die 3
    d100: int  # Rare play check (SBA only)

    # Derived values with defaults (calculated in __post_init__)
    error_total: int = field(default=0)  # Sum of 3d6 for error chart lookup
    _is_rare_play: bool = field(default=False)  # Private, use property

    def __post_init__(self):
        """Calculate derived values based on league"""
        self.error_total = self.d6_one + self.d6_two + self.d6_three

        # League-specific rare play detection
        if self.league_id == "sba":
            self._is_rare_play = self.d100 == 1
        elif self.league_id == "pd":
            self._is_rare_play = self.error_total == 5
        else:
            raise ValueError(
                f"Unknown league_id: {self.league_id}. Must be 'sba' or 'pd'"
            )

    @property
    def is_rare_play(self) -> bool:
        """Check if this is a rare play based on league rules"""
        return self._is_rare_play

    def to_dict(self) -> dict:
        base = super().to_dict()
        base.update(
            {
                "position": self.position,
                "d20": self.d20,
                "d6_one": self.d6_one,
                "d6_two": self.d6_two,
                "d6_three": self.d6_three,
                "d100": self.d100,
                "error_total": self.error_total,
                "is_rare_play": self.is_rare_play,  # Uses property
            }
        )
        return base

    def __str__(self) -> str:
        rare = " [RARE PLAY]" if self.is_rare_play else ""
        return f"Fielding Roll ({self.position}): d20={self.d20}, error={self.error_total} ({self.d6_one}+{self.d6_two}+{self.d6_three}){rare}"


@dataclass(kw_only=True)
class D20Roll(DiceRoll):
    """
    Simple d20 roll

    Note: Modifiers in this game are applied to target numbers, not rolls.
    """

    roll: int

    def to_dict(self) -> dict:
        base = super().to_dict()
        base.update({"roll": self.roll})
        return base

    def __str__(self) -> str:
        return str(self.roll)
