"""
Odds format conversion utilities.

Convert between decimal, American, probability, and fractional odds.
All ASD API odds are stored internally as decimal. These converters
transform to your platform's native format.

Usage::

    from altsportsdata.converters import OddsFormat, convert_odds

    convert_odds(2.50, "decimal", "american")     # → 150.0
    convert_odds(1.50, "decimal", "american")     # → -200.0
    convert_odds(2.50, "decimal", "probability")  # → 0.4
    convert_odds(2.50, "decimal", "fractional")   # → '3/2'

Or set it once on the client — all responses auto-convert::

    client = AltSportsData(api_key="key", odds_format="probability")
"""

from enum import Enum
from typing import Any, Union


class OddsFormat(str, Enum):
    """Supported odds display formats."""
    DECIMAL = "decimal"
    AMERICAN = "american"
    PROBABILITY = "probability"
    FRACTIONAL = "fractional"


# ── Core conversion functions ────────────────────────────────────────────────


def decimal_to_american(odds: float) -> float:
    """
    Convert decimal odds to American odds.

    - Decimal >= 2.0 → positive American (e.g. 2.50 → +150)
    - Decimal < 2.0  → negative American (e.g. 1.50 → -200)
    """
    if odds is None or odds <= 1.0:
        return 0.0
    if odds >= 2.0:
        return round((odds - 1) * 100, 2)
    return round(-100 / (odds - 1), 2)


def decimal_to_probability(odds: float) -> float:
    """
    Convert decimal odds to implied probability (0–1 scale).

    2.00 → 0.5, 4.00 → 0.25, 1.25 → 0.8
    """
    if odds is None or odds <= 0:
        return 0.0
    return round(1.0 / odds, 6)


def decimal_to_fractional(odds: float) -> str:
    """
    Convert decimal odds to fractional string.

    2.50 → '3/2', 3.00 → '2/1', 1.50 → '1/2'
    """
    if odds is None or odds <= 1.0:
        return "0/1"
    numerator = odds - 1.0
    # Try clean fractions with denominators up to 20
    for d in range(1, 21):
        n = numerator * d
        if abs(n - round(n)) < 0.01:
            return f"{int(round(n))}/{d}"
    # Fallback: 100-based
    return f"{int(round(numerator * 100))}/100"


def american_to_decimal(odds: float) -> float:
    """
    Convert American odds to decimal.

    +150 → 2.50, -200 → 1.50, +100 → 2.00
    """
    if odds is None:
        return 0.0
    if odds > 0:
        return round(1.0 + odds / 100, 4)
    elif odds < 0:
        return round(1.0 + 100 / abs(odds), 4)
    return 0.0


def probability_to_decimal(prob: float) -> float:
    """
    Convert probability to decimal odds.

    Accepts 0–1 scale. Values > 1 are treated as percentages (0–100).
    0.5 → 2.0, 0.25 → 4.0, 0.8 → 1.25
    """
    if prob is None or prob <= 0:
        return float("inf")
    if prob > 1.0:
        prob = prob / 100.0  # normalize percentage to 0-1
    return round(1.0 / prob, 4)


def probability_to_american(prob: float) -> float:
    """Convert probability (0–1) to American odds."""
    return decimal_to_american(probability_to_decimal(prob))


def fractional_to_decimal(frac: str) -> float:
    """
    Convert fractional odds string to decimal.

    '3/2' → 2.50, '2/1' → 3.00, '1/2' → 1.50
    """
    if not frac or "/" not in str(frac):
        return float(frac) if frac else 0.0
    parts = str(frac).split("/")
    return round(1.0 + float(parts[0]) / float(parts[1]), 4)


# ── Universal converter ─────────────────────────────────────────────────────


def convert_odds(
    value: Union[float, str],
    from_format: Union[str, OddsFormat] = "decimal",
    to_format: Union[str, OddsFormat] = "american",
) -> Union[float, str]:
    """
    Convert a single odds value between any two formats.

    Args:
        value: The odds value to convert
        from_format: Source format (decimal, american, probability, fractional)
        to_format: Target format

    Returns:
        Converted value. Float for most formats, str for fractional.

    Examples::

        convert_odds(2.50, "decimal", "american")     # → 150.0
        convert_odds(150, "american", "decimal")       # → 2.5
        convert_odds(2.50, "decimal", "probability")   # → 0.4
        convert_odds(0.4, "probability", "american")   # → 150.0
        convert_odds(2.50, "decimal", "fractional")    # → '3/2'
        convert_odds("3/2", "fractional", "decimal")   # → 2.5
    """
    from_f = OddsFormat(from_format)
    to_f = OddsFormat(to_format)

    if from_f == to_f:
        return value

    # Step 1: normalize to decimal
    if from_f == OddsFormat.DECIMAL:
        dec = float(value)
    elif from_f == OddsFormat.AMERICAN:
        dec = american_to_decimal(float(value))
    elif from_f == OddsFormat.PROBABILITY:
        dec = probability_to_decimal(float(value))
    elif from_f == OddsFormat.FRACTIONAL:
        dec = fractional_to_decimal(str(value))
    else:
        dec = float(value)

    # Step 2: convert decimal to target
    if to_f == OddsFormat.DECIMAL:
        return round(dec, 4)
    elif to_f == OddsFormat.AMERICAN:
        return decimal_to_american(dec)
    elif to_f == OddsFormat.PROBABILITY:
        return decimal_to_probability(dec)
    elif to_f == OddsFormat.FRACTIONAL:
        return decimal_to_fractional(dec)

    return dec


# ── Response tree converter ──────────────────────────────────────────────────


def convert_response(data: Any, odds_format: Union[str, OddsFormat]) -> Any:
    """
    Walk a response tree and convert all ``odds`` and ``probability`` fields.

    - ``odds`` fields: converted from decimal to the target format.
    - ``probability`` fields: normalized from 0–100 (API format) to 0–1 when
      target is ``probability``; left as-is for other formats.

    This is called automatically when ``odds_format`` is set on the client.
    """
    fmt = OddsFormat(odds_format)
    if fmt == OddsFormat.DECIMAL:
        return data
    return _walk(data, fmt)


def _walk(obj: Any, fmt: OddsFormat) -> Any:
    """Recursively walk and convert odds/probability fields."""
    if isinstance(obj, dict):
        result = {}
        for k, v in obj.items():
            if k == "odds" and isinstance(v, (int, float)):
                result[k] = _convert_odds_field(v, fmt)
            elif k in ("odds1", "odds2") and isinstance(v, (int, float)):
                result[k] = _convert_odds_field(v, fmt)
            elif k == "probability" and isinstance(v, (int, float)):
                if fmt == OddsFormat.PROBABILITY:
                    # API ALWAYS returns 0-100 percentages. Normalize to 0-1.
                    result[k] = round(v / 100.0, 6)
                else:
                    result[k] = v
            else:
                result[k] = _walk(v, fmt)
        return result
    elif isinstance(obj, list):
        return [_walk(item, fmt) for item in obj]
    return obj


def _convert_odds_field(decimal_odds: float, fmt: OddsFormat) -> Union[float, str]:
    """Convert a single decimal odds value to the target format."""
    if fmt == OddsFormat.AMERICAN:
        return decimal_to_american(decimal_odds)
    elif fmt == OddsFormat.PROBABILITY:
        return decimal_to_probability(decimal_odds)
    elif fmt == OddsFormat.FRACTIONAL:
        return decimal_to_fractional(decimal_odds)
    return decimal_odds
