"""Value bet finder — identifies betting opportunities where model
probabilities exceed market-implied probabilities.

Uses Kelly criterion sizing and confidence filtering to surface
actionable betting recommendations.
"""

from __future__ import annotations

from decimal import Decimal
from typing import Any


class ValueBetFinder:
    """Identifies value bets by comparing model vs market probabilities.

    A "value bet" exists when the model's estimated win/place probability
    exceeds the market-implied probability by a configurable threshold.
    """

    def __init__(
        self,
        min_edge: float = 0.05,
        min_confidence: float = 0.3,
        max_kelly_fraction: float = 0.25,
    ) -> None:
        """Initialize the value bet finder.

        Args:
            min_edge: Minimum edge (model_prob - market_prob) to
                consider a value bet. Default 0.05 (5%).
            min_confidence: Minimum model confidence (probability) to
                consider. Filters out very low-probability bets.
                Default 0.3.
            max_kelly_fraction: Maximum fraction of bankroll to
                stake per bet (Kelly cap). Default 0.25 (25%).
        """
        self.min_edge = min_edge
        self.min_confidence = min_confidence
        self.max_kelly_fraction = max_kelly_fraction

    def find_value_bets(
        self,
        comparisons: list[dict[str, Any]],
        bankroll: Decimal = Decimal("1000"),
    ) -> list[dict[str, Any]]:
        """Find value bets from model-vs-market comparisons.

        Args:
            comparisons: List of comparison dicts from
                OddsComparisonClient.compare_to_model().
            bankroll: Current bankroll for Kelly stake sizing.

        Returns:
            List of value bet dicts, sorted by edge descending, with keys:
            - runner_name: str
            - starter_id: int | None
            - model_prob: float
            - market_prob: float
            - edge: float
            - kelly_stake: float (fraction of bankroll)
            - recommended_stake: Decimal (in bankroll units)
            - confidence: str ("high", "medium", "low")
        """
        raise NotImplementedError

    def kelly_criterion(
        self,
        model_prob: float,
        market_prob: float,
    ) -> float:
        """Calculate Kelly stake fraction.

        f* = (p * b - q) / b
        where p = model_prob, q = 1-p, b = (1/market_prob) - 1

        Args:
            model_prob: Model's estimated win probability.
            market_prob: Market-implied win probability.

        Returns:
            Kelly fraction (0 to max_kelly_fraction).
        """
        if market_prob <= 0 or market_prob >= 1:
            return 0.0
        if model_prob <= market_prob:
            return 0.0

        b = (Decimal("1") / Decimal(str(market_prob))) - Decimal("1")
        f_star = (
            Decimal(str(model_prob)) * b - (Decimal("1") - Decimal(str(model_prob)))
        ) / b
        return min(float(f_star), self.max_kelly_fraction)

    def filter_by_confidence(
        self,
        bets: list[dict[str, Any]],
        min_edge: float | None = None,
        min_prob: float | None = None,
    ) -> list[dict[str, Any]]:
        """Filter value bets by minimum edge and probability thresholds.

        Args:
            bets: List of value bet dicts.
            min_edge: Override default min_edge.
            min_prob: Override default min_confidence.

        Returns:
            Filtered list of value bet dicts.
        """
        edge = min_edge if min_edge is not None else self.min_edge
        prob = min_prob if min_prob is not None else self.min_confidence
        return [
            b
            for b in bets
            if b.get("edge", 0) >= edge and b.get("model_prob", 0) >= prob
        ]

    def summarize(
        self,
        bets: list[dict[str, Any]],
    ) -> dict[str, Any]:
        """Generate a summary of value bet opportunities.

        Args:
            bets: List of value bet dicts.

        Returns:
            Summary dict with count, total stake, average edge, etc.
        """
        if not bets:
            return {
                "total_bets": 0,
                "total_stake": Decimal("0"),
                "avg_edge": 0.0,
                "high_confidence": 0,
                "medium_confidence": 0,
                "low_confidence": 0,
            }

        return {
            "total_bets": len(bets),
            "total_stake": sum(b.get("recommended_stake", Decimal("0")) for b in bets),
            "avg_edge": sum(b.get("edge", 0) for b in bets) / len(bets),
            "high_confidence": sum(1 for b in bets if b.get("confidence") == "high"),
            "medium_confidence": sum(
                1 for b in bets if b.get("confidence") == "medium"
            ),
            "low_confidence": sum(1 for b in bets if b.get("confidence") == "low"),
        }
