"""
Core arb math.

For a 3-outcome market (home/draw/away), an arb exists when:

    (1/best_home_odds) + (1/best_draw_odds) + (1/best_away_odds) < 1.0

The amount under 1.0 is your guaranteed profit margin, and the stake split
that locks in equal profit regardless of outcome is proportional to each
outcome's implied probability.
"""

from dataclasses import dataclass
from typing import List, Dict
from scrapers.base import OddsEntry


@dataclass
class ArbOpportunity:
    home_team: str
    away_team: str
    market: str
    profit_margin_percent: float
    legs: List[dict]  # each: {outcome, bookmaker, odds, stake_percent}


def find_best_odds_per_outcome(entries: List[OddsEntry]) -> Dict[str, OddsEntry]:
    """For one event+market, find which bookmaker offers the best price per outcome."""
    best: Dict[str, OddsEntry] = {}
    for entry in entries:
        current = best.get(entry.outcome)
        if current is None or entry.odds > current.odds:
            best[entry.outcome] = entry
    return best


def detect_arb(entries: List[OddsEntry], min_profit_percent: float = 0.0) -> ArbOpportunity | None:
    """
    entries: all OddsEntry rows for ONE event (may span multiple books/markets).
    Only compares entries sharing the same `market` value.
    Returns an ArbOpportunity if profitable, else None.
    """
    # group entries by market (e.g. "match_winner") since you can only arb within one market
    markets: Dict[str, List[OddsEntry]] = {}
    for e in entries:
        markets.setdefault(e.market, []).append(e)

    best_opportunity = None

    for market, market_entries in markets.items():
        best_per_outcome = find_best_odds_per_outcome(market_entries)

        # need at least 2 different outcomes and (ideally) from 2+ different books
        if len(best_per_outcome) < 2:
            continue

        implied_prob_sum = sum(1 / e.odds for e in best_per_outcome.values())

        if implied_prob_sum >= 1.0:
            continue  # no arb here

        profit_margin_percent = (1 - implied_prob_sum) * 100

        if profit_margin_percent < min_profit_percent:
            continue

        legs = []
        for outcome, entry in best_per_outcome.items():
            stake_percent = (1 / entry.odds) / implied_prob_sum * 100
            legs.append({
                "outcome": outcome,
                "bookmaker": entry.bookmaker,
                "odds": entry.odds,
                "stake_percent": round(stake_percent, 2),
            })

        candidate = ArbOpportunity(
            home_team=market_entries[0].home_team,
            away_team=market_entries[0].away_team,
            market=market,
            profit_margin_percent=round(profit_margin_percent, 3),
            legs=legs,
        )

        if best_opportunity is None or candidate.profit_margin_percent > best_opportunity.profit_margin_percent:
            best_opportunity = candidate

    return best_opportunity


def stake_amounts(opportunity: ArbOpportunity, total_stake: float) -> List[dict]:
    """Convert an ArbOpportunity's stake percentages into actual currency amounts."""
    result = []
    for leg in opportunity.legs:
        amount = total_stake * (leg["stake_percent"] / 100)
        result.append({**leg, "stake_amount": round(amount, 2)})
    return result
