"""Time-weighted Elo rating adjustments.

Foundational stub for a time-weighted K-factor that increases rating updates
for recent races (where the entity's form may have changed) and decreases
them for stale data.

The core idea:
  - If a horse/driver/trainer raced recently, their rating is likely still
    accurate, so we use the **base K-factor**.
  - If they have **not raced recently**, their true ability may have drifted.
    We **increase K** to let new race results have a larger impact, allowing
    ratings to adapt faster.
  - A **decay function** maps ``days_since_last_race`` to a multiplier on the
    base K-factor.

This module is **not yet integrated** into the main ``RatingEngine``.
Integration requires calling ``compute_effective_k`` from
``RatingEngine.get_effective_k_factor`` in ``engine.py``.
"""

from __future__ import annotations

import math
from datetime import date


class TimeWeightedElo:
    """Adjust K-factor based on time since last race.

    Entities that have not raced recently receive a higher effective K-factor,
    allowing their ratings to converge faster toward their current ability.

    Two decay strategies are provided:

    **Linear decay** (``mode="linear"``):
        ``multiplier = 1 + slope × days_since_last_race``
        Increases linearly with inactivity, bounded by ``max_multiplier``.

    **Exponential decay** (``mode="exp"``):
        ``multiplier = 1 + (max_multiplier - 1) × (1 - exp(-rate × days))``
        Approaches ``max_multiplier`` asymptotically — more gradual.
    """

    def __init__(
        self,
        mode: str = "linear",
        slope: float = 0.01,
        rate: float = 0.005,
        max_multiplier: float = 3.0,
        min_days: int = 0,
    ) -> None:
        """Initialise time-weighted K-factor configuration.

        Args:
            mode: Decay function — ``"linear"`` or ``"exp"``.
            slope: Slope for linear mode (K increase per inactive day).
            rate: Rate constant for exponential mode.
            max_multiplier: Upper bound on the K multiplier.
            min_days: Minimum inactive days before multiplier > 1.
        """
        self.mode = mode
        self.slope = slope
        self.rate = rate
        self.max_multiplier = max_multiplier
        self.min_days = min_days

    def compute_multiplier(self, days_since_last_race: int) -> float:
        """Compute K-factor multiplier based on inactive days.

        Args:
            days_since_last_race: Number of days since the entity last raced.

        Returns:
            A multiplier >= 1.0 to apply to the base K-factor.
        """
        days = max(0, days_since_last_race - self.min_days)
        if days <= 0:
            return 1.0

        if self.mode == "linear":
            mult = 1.0 + self.slope * days
        elif self.mode == "exp":
            mult = 1.0 + (self.max_multiplier - 1.0) * (
                1.0 - math.exp(-self.rate * days)
            )
        else:
            raise ValueError(f"Unknown mode: {self.mode}")

        return min(mult, self.max_multiplier)

    def compute_effective_k(
        self,
        race_date: date | None,
        last_race_date: date | None,
        base_k: float,
    ) -> float:
        """Compute effective K-factor for an entity.

        Args:
            race_date: Date of the current race being processed.
            last_race_date: Date of the entity's most recent prior race
                (or *None* for first-time starters).
            base_k: Base K-factor from settings (``elo_k_base``).

        Returns:
            Adjusted effective K-factor.
        """
        if race_date is None or last_race_date is None:
            # First race or missing dates — use max multiplier
            return base_k * self.max_multiplier

        days_since = (race_date - last_race_date).days
        multiplier = self.compute_multiplier(max(0, days_since))
        return base_k * multiplier


# ── Example / test usage ─────────────────────────────────────────────────


def _demo() -> None:
    """Demonstrate time-weighted K-factor in action."""
    tw = TimeWeightedElo(mode="linear", slope=0.02, max_multiplier=4.0)

    base_k = 32.0
    today = date(2026, 5, 8)

    scenarios = [
        ("Raced yesterday", date(2026, 5, 7)),
        ("Raced 1 week ago", date(2026, 5, 1)),
        ("Raced 1 month ago", date(2026, 4, 8)),
        ("Raced 3 months ago", date(2026, 2, 8)),
        ("First race (no last date)", None),
    ]

    print("Time-Weighted Elo — K-factor adjustment demo")
    print(
        f"  Base K = {base_k}, mode = {tw.mode}, slope = {tw.slope}, max_mult = {tw.max_multiplier}"
    )
    print()

    for label, last_date in scenarios:
        eff_k = tw.compute_effective_k(today, last_date, base_k)
        days = (today - last_date).days if last_date else "—"
        print(f"  {label:25s}  days={str(days):>5s}  K_eff={eff_k:.1f}")


if __name__ == "__main__":
    _demo()
