"""Bradley-Terry model for ranking conditions from pairwise preferences."""

from __future__ import annotations

import math
from typing import Dict, List

import numpy as np


def fit_bradley_terry(
    pairwise_results: List[Dict],
    conditions: List[str],
    max_iter: int = 1000,
    tol: float = 1e-6,
) -> Dict[str, float]:
    """Fit Bradley-Terry model using the MM algorithm (Hunter 2004).

    *pairwise_results*: list of ``{"winner": str, "loser": str, "cond_a": str, "cond_b": str}``
    TIEs are counted as 0.5 win for each side.

    Returns ``{condition_id: strength}`` (higher = better), normalised to sum 1.
    """
    n = len(conditions)
    idx = {c: i for i, c in enumerate(conditions)}

    wins = np.zeros((n, n))
    for r in pairwise_results:
        if r.get("winner") == "TIE":
            i, j = idx[r["cond_a"]], idx[r["cond_b"]]
            wins[i, j] += 0.5
            wins[j, i] += 0.5
        else:
            w = idx[r["winner"]]
            l_idx = idx[r["loser"]]
            wins[w, l_idx] += 1.0

    p = np.ones(n) / n
    for _ in range(max_iter):
        p_old = p.copy()
        for i in range(n):
            num = sum(wins[i, j] for j in range(n) if j != i)
            denom = sum(
                (wins[i, j] + wins[j, i]) / (p[i] + p[j])
                for j in range(n)
                if j != i
            )
            if denom > 0:
                p[i] = num / denom
        total = p.sum()
        if total > 0:
            p /= total
        if np.max(np.abs(p - p_old)) < tol:
            break

    return {c: float(p[idx[c]]) for c in conditions}


def strengths_to_elo(
    strengths: Dict[str, float], base: float = 1500
) -> Dict[str, float]:
    """Convert Bradley-Terry strengths to Elo-like ratings."""
    mean_s = sum(strengths.values()) / len(strengths)
    return {
        c: base + 400 * math.log10(max(s / mean_s, 1e-10))
        for c, s in strengths.items()
    }
