# Copyright 2026 Ruihang Li.
# Licensed under the Apache License, Version 2.0.
# See LICENSE file in the project root for details.

"""Leaderboard generation module."""

from dataclasses import dataclass
from datetime import datetime
from typing import Optional

from genarena.state import ArenaState


@dataclass
class LeaderboardEntry:
    """Entry in the leaderboard."""

    rank: int
    model: str
    elo: float
    wins: int
    losses: int
    ties: int
    total_battles: int
    win_rate: float
    ci_lower: Optional[float] = None
    ci_upper: Optional[float] = None

    @property
    def ci_width(self) -> Optional[float]:
        """95% CI width, or None if CI not computed."""
        if self.ci_lower is None or self.ci_upper is None:
            return None
        return self.ci_upper - self.ci_lower

    @property
    def ci_str(self) -> str:
        """Format CI as string (e.g., '±7.5' or 'N/A')."""
        width = self.ci_width
        if width is None:
            return "N/A"
        return f"±{width / 2:.1f}"


def generate_leaderboard(
    state: ArenaState,
    title: Optional[str] = None,
    show_ci: bool = True,
) -> str:
    """
    Generate a Markdown leaderboard from arena state.

    Args:
        state: ArenaState with model statistics
        title: Optional title for the leaderboard
        show_ci: Whether to show CI column (default: True)

    Returns:
        Markdown formatted leaderboard string
    """
    # Build list of entries sorted by ELO
    entries: list[LeaderboardEntry] = []

    for model, stats in state.models.items():
        entries.append(LeaderboardEntry(
            rank=0,  # Will be set after sorting
            model=model,
            elo=stats.elo,
            wins=stats.wins,
            losses=stats.losses,
            ties=stats.ties,
            total_battles=stats.total_battles,
            win_rate=stats.win_rate,
            ci_lower=stats.ci_lower,
            ci_upper=stats.ci_upper,
        ))

    # Sort by ELO descending
    entries.sort(key=lambda e: e.elo, reverse=True)

    # Assign ranks
    for i, entry in enumerate(entries):
        entry.rank = i + 1

    # Check if any entry has CI information
    has_ci = any(e.ci_lower is not None for e in entries)
    show_ci = show_ci and has_ci

    # Generate Markdown
    lines = []

    # Title
    if title:
        lines.append(f"# {title}")
    else:
        lines.append("# ELO Leaderboard")
    lines.append("")

    # Summary
    lines.append(f"**Total Models:** {len(entries)}")
    lines.append(f"**Total Battles:** {state.total_battles}")
    if state.last_updated:
        lines.append(f"**Last Updated:** {state.last_updated}")
    lines.append("")

    # Table header
    if show_ci:
        lines.append("| Rank | Model | ELO | 95% CI | Win Rate | W/L/T | Battles |")
        lines.append("|------|-------|-----|--------|----------|-------|---------|")
    else:
        lines.append("| Rank | Model | ELO | Win Rate | W/L/T | Battles |")
        lines.append("|------|-------|-----|----------|-------|---------|")

    # Table rows
    for entry in entries:
        win_rate_pct = f"{entry.win_rate * 100:.1f}%"
        wlt = f"{entry.wins}/{entry.losses}/{entry.ties}"
        elo_str = f"{entry.elo:.0f}"

        if show_ci:
            lines.append(
                f"| {entry.rank} | {entry.model} | {elo_str} | {entry.ci_str} | "
                f"{win_rate_pct} | {wlt} | {entry.total_battles} |"
            )
        else:
            lines.append(
                f"| {entry.rank} | {entry.model} | {elo_str} | {win_rate_pct} | "
                f"{wlt} | {entry.total_battles} |"
            )

    lines.append("")

    # Footer
    lines.append("---")
    lines.append(f"*Generated by GenArena Arena Evaluation*")

    return "\n".join(lines)


def save_leaderboard(
    state: ArenaState,
    path: str,
    title: Optional[str] = None
) -> None:
    """
    Generate and save leaderboard to a file.

    Args:
        state: ArenaState with model statistics
        path: Path to save the Markdown file (typically README.md)
        title: Optional title for the leaderboard
    """
    content = generate_leaderboard(state, title)

    with open(path, "w", encoding="utf-8") as f:
        f.write(content)


def generate_experiment_readme(
    exp_name: str,
    elo: dict[str, float],
    model_count: int,
    battle_count: int,
    ci_lower: Optional[dict[str, float]] = None,
    ci_upper: Optional[dict[str, float]] = None,
) -> str:
    """
    Generate README.md content for an experiment directory.

    Shows the cumulative ELO leaderboard up to (and including) this experiment.

    Args:
        exp_name: Experiment name (e.g., "MyExp_20260128")
        elo: Dict mapping model name to ELO rating
        model_count: Number of models
        battle_count: Total battles up to this experiment
        ci_lower: Optional dict of CI lower bounds
        ci_upper: Optional dict of CI upper bounds

    Returns:
        Markdown formatted README content
    """
    lines = []

    # Title
    lines.append(f"# {exp_name}")
    lines.append("")

    # Summary
    lines.append(f"**Models:** {model_count}")
    lines.append(f"**Cumulative Battles:** {battle_count}")
    lines.append("")

    # Build sorted entries
    entries = sorted(elo.items(), key=lambda x: x[1], reverse=True)

    # Check if CI info is available
    has_ci = ci_lower is not None and ci_upper is not None and len(ci_lower) > 0

    # Table header
    if has_ci:
        lines.append("| Rank | Model | ELO | 95% CI |")
        lines.append("|------|-------|-----|--------|")
    else:
        lines.append("| Rank | Model | ELO |")
        lines.append("|------|-------|-----|")

    # Table rows
    for rank, (model, elo_val) in enumerate(entries, start=1):
        elo_str = f"{elo_val:.0f}"
        if has_ci and model in ci_lower and model in ci_upper:
            width = ci_upper[model] - ci_lower[model]
            ci_str = f"±{width / 2:.1f}"
            lines.append(f"| {rank} | {model} | {elo_str} | {ci_str} |")
        elif has_ci:
            lines.append(f"| {rank} | {model} | {elo_str} | N/A |")
        else:
            lines.append(f"| {rank} | {model} | {elo_str} |")

    lines.append("")
    lines.append("---")
    lines.append("*Generated by GenArena*")

    return "\n".join(lines)


def print_leaderboard(state: ArenaState, title: Optional[str] = None) -> None:
    """
    Print leaderboard to stdout.

    Args:
        state: ArenaState with model statistics
        title: Optional title for the leaderboard
    """
    content = generate_leaderboard(state, title)
    print(content)


def get_leaderboard_entries(state: ArenaState) -> list[LeaderboardEntry]:
    """
    Get leaderboard entries as a list.

    Args:
        state: ArenaState with model statistics

    Returns:
        List of LeaderboardEntry objects sorted by ELO
    """
    entries: list[LeaderboardEntry] = []

    for model, stats in state.models.items():
        entries.append(LeaderboardEntry(
            rank=0,
            model=model,
            elo=stats.elo,
            wins=stats.wins,
            losses=stats.losses,
            ties=stats.ties,
            total_battles=stats.total_battles,
            win_rate=stats.win_rate,
            ci_lower=stats.ci_lower,
            ci_upper=stats.ci_upper,
        ))

    entries.sort(key=lambda e: e.elo, reverse=True)

    for i, entry in enumerate(entries):
        entry.rank = i + 1

    return entries
