"""Pure-Python training-effect policy.

No Django or ORM imports are allowed here.  This is deliberately versioned so
future balancing changes can introduce v2 without rewriting historical results.
"""

from dataclasses import dataclass
from datetime import date
from decimal import Decimal, ROUND_HALF_UP

from .constants import TRAINING_POLICY_KEY, TRAINING_POLICY_VERSION

_ONE = Decimal("1")
_ZERO = Decimal("0")
_Q6 = Decimal("0.000001")


@dataclass(frozen=True)
class TrainingModifiers:
    nutrition: Decimal = _ONE
    coach: Decimal = _ONE
    fatigue: Decimal = _ONE
    injury: Decimal = _ONE

    def __post_init__(self):
        for name in ("nutrition", "coach", "fatigue", "injury"):
            value = getattr(self, name)
            if not isinstance(value, Decimal):
                raise TypeError(f"{name} multiplier must be Decimal")
            if not value.is_finite() or value < _ZERO or value > Decimal("2.000000"):
                raise ValueError(f"{name} multiplier must be between 0 and 2")


@dataclass(frozen=True)
class TrainingEffectInput:
    training_date: date
    date_of_birth: date
    overall: int
    potential: int
    base_effect: Decimal
    modifiers: TrainingModifiers = TrainingModifiers()
    age_years: int | None = None


@dataclass(frozen=True)
class TrainingEffectResult:
    age_years: int
    base_effect: Decimal
    potential_factor: Decimal
    age_factor: Decimal
    nutrition_multiplier: Decimal
    coach_multiplier: Decimal
    fatigue_multiplier: Decimal
    injury_multiplier: Decimal
    final_effect: Decimal
    policy_key: str = TRAINING_POLICY_KEY
    policy_version: str = TRAINING_POLICY_VERSION


class TrainingEffectPolicyV1:
    """Conservative v1 policy that produces development credits, not ratings.

    Important: `final_effect` is *development progress*, not a +rating amount.
    Rating materialization remains a separate future balancing policy.
    """

    key = TRAINING_POLICY_KEY
    version = TRAINING_POLICY_VERSION

    @classmethod
    def calculate(cls, data: TrainingEffectInput) -> TrainingEffectResult:
        if data.training_date < data.date_of_birth:
            raise ValueError("Training date cannot be before date of birth")
        if not (1 <= int(data.overall) <= 100):
            raise ValueError("overall must be between 1 and 100")
        if not (1 <= int(data.potential) <= 100):
            raise ValueError("potential must be between 1 and 100")
        if int(data.potential) < int(data.overall):
            raise ValueError("potential cannot be below overall")
        if not isinstance(data.base_effect, Decimal):
            raise TypeError("base_effect must be Decimal")
        if not data.base_effect.is_finite() or data.base_effect <= _ZERO:
            raise ValueError("base_effect must be positive")

        age = _age_on(data.date_of_birth, data.training_date) if data.age_years is None else data.age_years
        potential_factor = cls._potential_factor(data.overall, data.potential)
        age_factor = cls._age_factor(age)
        modifiers = data.modifiers
        final_effect = (
            data.base_effect
            * potential_factor
            * age_factor
            * modifiers.nutrition
            * modifiers.coach
            * modifiers.fatigue
            * modifiers.injury
        ).quantize(_Q6, rounding=ROUND_HALF_UP)

        return TrainingEffectResult(
            age_years=age,
            base_effect=data.base_effect.quantize(_Q6),
            potential_factor=potential_factor,
            age_factor=age_factor,
            nutrition_multiplier=modifiers.nutrition.quantize(_Q6),
            coach_multiplier=modifiers.coach.quantize(_Q6),
            fatigue_multiplier=modifiers.fatigue.quantize(_Q6),
            injury_multiplier=modifiers.injury.quantize(_Q6),
            final_effect=final_effect,
        )

    @staticmethod
    def _potential_factor(overall: int, potential: int) -> Decimal:
        # The architecture only specifies that a player closer to potential
        # develops more slowly.  v1 keeps this deliberately conservative:
        # factor 0.50 at the ceiling, linearly rising to 1.00 at a 40+ gap.
        gap = max(0, int(potential) - int(overall))
        bounded_gap = min(gap, 40)
        factor = Decimal("0.50") + (Decimal(bounded_gap) / Decimal("80"))
        return factor.quantize(_Q6, rounding=ROUND_HALF_UP)

    @staticmethod
    def _age_factor(age: int) -> Decimal:
        # Versioned baseline.  It models slower development with age without
        # introducing post-34 decline (decline is a separate future feature).
        if age <= 18:
            return Decimal("1.100000")
        if age <= 23:
            return Decimal("1.050000")
        if age <= 29:
            return Decimal("1.000000")
        if age <= 33:
            return Decimal("0.900000")
        if age <= 39:
            return Decimal("0.750000")
        return Decimal("0.500000")


def _age_on(date_of_birth: date, on_date: date) -> int:
    return (
        on_date.year
        - date_of_birth.year
        - ((on_date.month, on_date.day) < (date_of_birth.month, date_of_birth.day))
    )
