"""Deck ratings from Rust arena binary.

Loads and manages ELO ratings for game decks, generated by:
    cargo run --release --bin arena

Deck ratings represent the relative strength of different deck compositions
when played by the same bot type (e.g., GreedyBot vs GreedyBot).
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

from .base import BaseRating, RatingCategory, expected_score

# Default path for deck ratings
DEFAULT_DECK_RATINGS_PATH = Path("data/ratings/deck_elo.json")
DEFAULT_DECKS_DIR = Path("data/decks")


@dataclass
class RatingChange:
    """A single rating change event."""

    date: str
    opponent: str
    delta: int
    result: str  # "win", "loss", "draw"
    games: int


@dataclass
class DeckRating(BaseRating):
    """ELO rating for a single deck.

    Attributes:
        deck_id: Unique identifier (e.g., "artificer_tokens")
        rating: Current ELO rating
        games: Total games played
        wins: Number of wins
        losses: Number of losses
        draws: Number of draws
        history: List of rating changes over time
        faction: Faction name (argentum, symbiote, obsidion)
        commander_name: Human-readable commander name
    """

    deck_id: str = ""
    history: list[RatingChange] = field(default_factory=list)
    faction: str | None = None
    commander_name: str | None = None

    @property
    def display_name(self) -> str:
        """Human-readable name for display."""
        return self.commander_name or self.deck_id

    @property
    def category(self) -> RatingCategory:
        """Rating category."""
        return RatingCategory.DECK

    @property
    def identifier(self) -> str:
        """Unique identifier."""
        return self.deck_id

    def get_rating_timeline(self) -> list[tuple[str, float]]:
        """Build rating timeline from history.

        Returns:
            List of (date, rating) tuples showing rating progression.
        """
        timeline = []
        current_rating = 1500.0  # Starting rating

        for change in self.history:
            current_rating += change.delta
            timeline.append((change.date, current_rating))

        return timeline


class DeckRatings:
    """Manager for deck ratings loaded from Rust arena.

    Example:
        >>> ratings = DeckRatings.load()
        >>> print(ratings.get_ranked())
        >>> print(ratings.get_by_faction("argentum"))
        >>> print(ratings.predict_matchup("artificer_tokens", "broodmother_pack"))
    """

    def __init__(
        self,
        ratings: dict[str, DeckRating],
        version: int = 1,
        last_updated: datetime | None = None,
    ):
        """Initialize deck ratings.

        Args:
            ratings: Dictionary of deck_id -> DeckRating
            version: Data format version
            last_updated: Timestamp of last update
        """
        self.ratings = ratings
        self.version = version
        self.last_updated = last_updated or datetime.now()

    @classmethod
    def load(
        cls,
        elo_file: Path | str | None = None,
        decks_dir: Path | str | None = None,
    ) -> DeckRatings:
        """Load deck ratings from JSON file.

        Args:
            elo_file: Path to deck_elo.json. Defaults to data/ratings/deck_elo.json.
            decks_dir: Path to decks directory for faction lookup.

        Returns:
            DeckRatings instance with loaded data.

        Raises:
            FileNotFoundError: If ratings file not found.
        """
        elo_file = Path(elo_file) if elo_file else DEFAULT_DECK_RATINGS_PATH
        decks_dir = Path(decks_dir) if decks_dir else DEFAULT_DECKS_DIR

        if not elo_file.exists():
            raise FileNotFoundError(f"Deck ratings file not found: {elo_file}")

        with elo_file.open() as f:
            data = json.load(f)

        # Build deck-to-faction mapping
        deck_factions = _build_deck_faction_map(decks_dir)

        # Parse timestamp
        last_updated_str = data.get("last_updated", "")
        try:
            last_updated = datetime.fromisoformat(
                last_updated_str.replace("Z", "+00:00")
            )
        except (ValueError, AttributeError):
            last_updated = datetime.now()

        # Parse ratings
        ratings = {}
        for deck_id, rating_data in data.get("ratings", {}).items():
            history = [
                RatingChange(
                    date=h.get("date", ""),
                    opponent=h.get("opponent", ""),
                    delta=h.get("delta", 0),
                    result=h.get("result", ""),
                    games=h.get("games", 0),
                )
                for h in rating_data.get("history", [])
            ]

            commander_name = _deck_id_to_commander_name(deck_id)

            ratings[deck_id] = DeckRating(
                rating=rating_data.get("rating", 1500.0),
                games=rating_data.get("games", 0),
                wins=rating_data.get("wins", 0),
                losses=rating_data.get("losses", 0),
                draws=rating_data.get("draws", 0),
                deck_id=deck_id,
                history=history,
                faction=deck_factions.get(deck_id),
                commander_name=commander_name,
            )

        return cls(
            ratings=ratings,
            version=data.get("version", 1),
            last_updated=last_updated,
        )

    @classmethod
    def empty(cls) -> DeckRatings:
        """Create an empty DeckRatings instance."""
        return cls(ratings={})

    def get_ranked(self) -> list[DeckRating]:
        """Get all decks sorted by rating (descending)."""
        return sorted(self.ratings.values(), key=lambda d: d.rating, reverse=True)

    def get_by_faction(self, faction: str) -> list[DeckRating]:
        """Get decks for a specific faction, sorted by rating."""
        return sorted(
            [d for d in self.ratings.values() if d.faction == faction],
            key=lambda d: d.rating,
            reverse=True,
        )

    def get(self, deck_id: str) -> DeckRating | None:
        """Get rating for a specific deck."""
        return self.ratings.get(deck_id)

    def predict_matchup(self, deck1_id: str, deck2_id: str) -> float:
        """Predict win probability for deck1 vs deck2 based on ELO.

        Returns:
            Expected win probability for deck1 (0 to 1).
        """
        d1 = self.ratings.get(deck1_id)
        d2 = self.ratings.get(deck2_id)

        if not d1 or not d2:
            return 0.5

        return expected_score(d1.rating, d2.rating)

    def get_faction_standings(self) -> dict[str, dict[str, Any]]:
        """Calculate aggregate standings per faction."""
        faction_data: dict[str, dict[str, Any]] = {}

        for deck in self.ratings.values():
            faction = deck.faction or "unknown"
            if faction not in faction_data:
                faction_data[faction] = {
                    "total_rating": 0.0,
                    "count": 0,
                    "total_wins": 0,
                    "total_losses": 0,
                    "total_games": 0,
                }

            faction_data[faction]["total_rating"] += deck.rating
            faction_data[faction]["count"] += 1
            faction_data[faction]["total_wins"] += deck.wins
            faction_data[faction]["total_losses"] += deck.losses
            faction_data[faction]["total_games"] += deck.games

        # Calculate averages
        for _faction, data in faction_data.items():
            if data["count"] > 0:
                data["avg_rating"] = data["total_rating"] / data["count"]
            else:
                data["avg_rating"] = 1500.0

            if data["total_games"] > 0:
                data["win_rate"] = data["total_wins"] / data["total_games"]
            else:
                data["win_rate"] = 0.5

        return faction_data

    def __len__(self) -> int:
        return len(self.ratings)

    def __repr__(self) -> str:
        total_games = sum(d.games for d in self.ratings.values())
        return f"DeckRatings({len(self.ratings)} decks, {total_games} games)"


def _build_deck_faction_map(decks_dir: Path) -> dict[str, str]:
    """Build a mapping of deck_id to faction from directory structure."""
    mapping: dict[str, str] = {}

    if not decks_dir.exists():
        return mapping

    for faction_dir in decks_dir.iterdir():
        if not faction_dir.is_dir():
            continue

        faction = faction_dir.name
        for deck_file in faction_dir.glob("*.toml"):
            deck_id = deck_file.stem
            mapping[deck_id] = faction

    return mapping


def _deck_id_to_commander_name(deck_id: str) -> str:
    """Convert deck_id to human-readable commander name.

    Examples:
        artificer_tokens -> Artificer Tokens
        broodmother_pack -> Broodmother Pack
    """
    return " ".join(word.title() for word in deck_id.split("_"))


def deck_ratings_exist(elo_file: Path | str | None = None) -> bool:
    """Check if deck ratings file exists."""
    elo_file = Path(elo_file) if elo_file else DEFAULT_DECK_RATINGS_PATH
    return elo_file.exists()
