import math
from dataclasses import dataclass
from .base_calculator import BaseCalculator, CalculationContext
from .confidence import calc_confidence_2way

@dataclass
class OverUnderPrediction:
    over_15_prob: float
    under_15_prob: float
    ou15_pick: str
    ou15_confidence: float
    
    over_25_prob: float
    under_25_prob: float
    ou25_pick: str
    ou25_confidence: float
    
    over_35_prob: float
    under_35_prob: float
    ou35_pick: str
    ou35_confidence: float
    
    btts_yes_prob: float
    btts_no_prob: float
    btts_pick: str
    btts_confidence: float


class OverUnderCalculator(BaseCalculator):
    
    def _poisson_pmf(self, k: int, lam: float) -> float:
        if lam <= 0:
            return 1.0 if k == 0 else 0.0
        return (lam ** k) * math.exp(-lam) / math.factorial(k)
    
    def _poisson_ou_probs(self, home_xg: float, away_xg: float, grid_max: int = 6):
        """Bivariate Poisson grid → O/U probabilities."""
        total_goals_prob = {}  # total_goals → cumulative probability
        
        for i in range(grid_max):
            for j in range(grid_max):
                p = self._poisson_pmf(i, home_xg) * self._poisson_pmf(j, away_xg)
                total = i + j
                total_goals_prob[total] = total_goals_prob.get(total, 0.0) + p
        
        # Cumulative
        over_15 = sum(p for g, p in total_goals_prob.items() if g >= 2)
        over_25 = sum(p for g, p in total_goals_prob.items() if g >= 3)
        over_35 = sum(p for g, p in total_goals_prob.items() if g >= 4)
        
        # BTTS: P(home >= 1) * P(away >= 1)
        p_home_0 = self._poisson_pmf(0, home_xg)
        p_away_0 = self._poisson_pmf(0, away_xg)
        btts_yes = (1 - p_home_0) * (1 - p_away_0)
        
        return over_15, over_25, over_35, btts_yes
    
    def calculate(self, ctx: CalculationContext) -> OverUnderPrediction:  # type: ignore[override]
        odds_pred = ctx.odds_pred
        referee_mods = ctx.referee_mods
        
        # Config
        prob_min = self.config.get("over_under.prob_min", 0.02)
        prob_max = self.config.get("over_under.prob_max", 0.98)
        blend_w = self.config.get("over_under.poisson_blend_weight", 0.4)
        grid_max = self.config.get("over_under.poisson_grid_max", 6)
        
        ou15_thr = self.config.get("over_under.ou15_threshold", 0.55)
        ou25_thr = self.config.get("over_under.ou25_threshold", 0.52)
        ou35_thr = self.config.get("over_under.ou35_threshold", 0.48)
        btts_thr = self.config.get("over_under.btts_threshold", 0.58)
        
        # 1. Poisson-based O/U from context xG (team + odds average)
        p_over_15, p_over_25, p_over_35, p_btts = self._poisson_ou_probs(
            ctx.home_xg, ctx.away_xg, int(grid_max)
        )
        
        # 2. Odds-based O/U (from odds engine Poisson)
        o_over_15 = odds_pred.over_15_prob
        o_over_25 = odds_pred.over_25_prob
        o_over_35 = odds_pred.over_35_prob
        o_btts = odds_pred.btts_yes_prob
        
        # 3. Blend: poisson xG + odds Poisson
        # Odds engine already uses Poisson internally, so keep blend weight low
        # to avoid double-counting. Use majority odds weight for established markets.
        over_15 = p_over_15 * blend_w + o_over_15 * (1 - blend_w)
        over_25 = p_over_25 * blend_w + o_over_25 * (1 - blend_w)
        over_35 = p_over_35 * blend_w + o_over_35 * (1 - blend_w)
        
        # BTTS: keep primarily from odds engine (it was 63.6% accurate before)
        # Only a small Poisson contribution to cross-validate
        btts_blend = min(blend_w, 0.2)
        btts_yes = p_btts * btts_blend + o_btts * (1 - btts_blend)
        
        # XGBoost Integration (High Weight)
        w_xgb = self.config.get("xgboost.weight_ou", 0.70)
        
        if "ou25" in ctx.xgboost_preds:
            over_25 = over_25 * (1 - w_xgb) + ctx.xgboost_preds["ou25"] * w_xgb
            
        if "ou15" in ctx.xgboost_preds:
            over_15 = over_15 * (1 - w_xgb) + ctx.xgboost_preds["ou15"] * w_xgb
            
        if "ou35" in ctx.xgboost_preds:
            over_35 = over_35 * (1 - w_xgb) + ctx.xgboost_preds["ou35"] * w_xgb
            
        # BTTS: lower XGBoost weight (was 0.70) — Poisson/odds fundamentals matter more
        w_xgb_btts = self.config.get("xgboost.weight_btts", 0.45)
        if "btts" in ctx.xgboost_preds:
            btts_yes = btts_yes * (1 - w_xgb_btts) + ctx.xgboost_preds["btts"] * w_xgb_btts
        
        # 4. Referee modifier (only applied to goal totals, not BTTS)
        ou_mod = referee_mods.get("over_25_modifier", 1.0)
        over_15 *= ou_mod
        over_25 *= ou_mod
        over_35 *= ou_mod
        
        # 5. Clamp
        over_15 = max(prob_min, min(prob_max, over_15))
        over_25 = max(prob_min, min(prob_max, over_25))
        over_35 = max(prob_min, min(prob_max, over_35))
        btts_yes = max(prob_min, min(prob_max, btts_yes))
        
        # Picks & Confidence
        ou15_pick = "Üst 1.5" if over_15 > ou15_thr else "Alt 1.5"
        ou15_conf = calc_confidence_2way(over_15)
        
        ou25_pick = "Üst 2.5" if over_25 > ou25_thr else "Alt 2.5"
        ou25_conf = calc_confidence_2way(over_25)
        
        ou35_pick = "Üst 3.5" if over_35 > ou35_thr else "Alt 3.5"
        ou35_conf = calc_confidence_2way(over_35)
        
        btts_pick = "KG Var" if btts_yes > btts_thr else "KG Yok"
        btts_conf = calc_confidence_2way(btts_yes)
        
        # --- SAFE BTTS PENALTY (v2 — tighter thresholds) ---
        # Penalize BTTS confidence when fundamentals don't strongly support the pick.
        try:
            home_conceded = ctx.team_pred.raw_features.get("home_conceded_avg", 1.0)
            away_conceded = ctx.team_pred.raw_features.get("away_conceded_avg", 1.0)
            
            if btts_pick == "KG Var":
                # "Var" needs BOTH teams to score → requires strong attack OR leaky defense
                # Penalty if either xG is low AND defenses are solid
                weak_attack = ctx.home_xg < 1.30 or ctx.away_xg < 1.15
                solid_defense = home_conceded < 1.15 or away_conceded < 1.15
                if weak_attack and solid_defense:
                    btts_conf *= 0.3
            else:  # KG Yok
                # "Yok" needs at least one team to fail scoring
                # Penalty if both have good xG AND both defenses are leaky
                if ctx.home_xg >= 1.30 and ctx.away_xg >= 1.15 and home_conceded >= 1.20 and away_conceded >= 1.20:
                    btts_conf *= 0.3
                    
        except Exception as e:
            print(f"⚠️ Safe BTTS Check Error: {e}")
            pass

        
        return OverUnderPrediction(
            over_15_prob=over_15, under_15_prob=1-over_15,
            ou15_pick=ou15_pick, ou15_confidence=ou15_conf,
            
            over_25_prob=over_25, under_25_prob=1-over_25,
            ou25_pick=ou25_pick, ou25_confidence=ou25_conf,
            
            over_35_prob=over_35, under_35_prob=1-over_35,
            ou35_pick=ou35_pick, ou35_confidence=ou35_conf,
            
            btts_yes_prob=btts_yes, btts_no_prob=1-btts_yes,
            btts_pick=btts_pick, btts_confidence=btts_conf
        )
