from dataclasses import dataclass, field
from typing import Dict, Any, List, Tuple
from .base_calculator import BaseCalculator, CalculationContext
from .odds_anomaly_detector import OddsAnomalyDetector

@dataclass
class RiskAnalysis:
    risk_score: float
    risk_level: str
    is_surprise_risk: bool
    reasons: List[str] = field(default_factory=list)
    surprise_type: str = ""
    risk_warnings: List[str] = field(default_factory=list)

class RiskAssessor(BaseCalculator):
    """
    Assesses risk level of the match based on context and predictions.
    """
    
    def __init__(self, config: Dict):
        super().__init__(config)
        self.anomaly_detector = OddsAnomalyDetector(config)

    @staticmethod
    def _safe_odd(value: Any) -> float:
        try:
            odd = float(value)
            return odd if odd > 1.01 else 0.0
        except (TypeError, ValueError):
            return 0.0

    def _favorite_profile_from_odds(self, odds_data: Dict[str, float]) -> Tuple[str, float]:
        """
        Returns (favorite_side, gap_to_second_favorite).
        favorite_side: H, A, D, or U (unknown)
        """
        ms_h = self._safe_odd((odds_data or {}).get("ms_h"))
        ms_d = self._safe_odd((odds_data or {}).get("ms_d"))
        ms_a = self._safe_odd((odds_data or {}).get("ms_a"))

        candidates = [(side, odd) for side, odd in (("H", ms_h), ("D", ms_d), ("A", ms_a)) if odd > 0.0]
        if len(candidates) < 2:
            return "U", 0.0

        candidates.sort(key=lambda item: item[1])
        favorite_side, favorite_odd = candidates[0]
        second_odd = candidates[1][1]
        return favorite_side, max(0.0, second_odd - favorite_odd)

    def _dynamic_reversal_threshold(
        self,
        ctx: CalculationContext,
        top_label: str,
    ) -> float:
        """
        Dynamic threshold for reversal surprise flags.
        Lower threshold => easier to trigger surprise.
        """
        base_threshold = float(self.config.get("risk.surprise_threshold", 0.20))
        sport_key = (ctx.sport or "football").lower().strip()
        is_top_league = bool(getattr(ctx, "is_top_league", False))

        if not is_top_league:
            base_threshold = float(
                self.config.get("risk.surprise_threshold_non_top", base_threshold + 0.04),
            )

        if sport_key == "basketball":
            if is_top_league:
                top_val = self.config.get("risk.surprise_threshold_basketball_top")
                if top_val is not None:
                    return float(top_val)
                base_val = self.config.get("risk.surprise_threshold_basketball")
                return float(base_val) if base_val is not None else 0.30
            
            non_top_val = self.config.get("risk.surprise_threshold_basketball_non_top")
            return float(non_top_val) if non_top_val is not None else 0.34

        if top_label not in ("1/2", "2/1"):
            return base_threshold

        winner_side = "A" if top_label == "1/2" else "H"
        favorite_side, gap = self._favorite_profile_from_odds(ctx.odds_data)

        if is_top_league:
            top_fav = self.config.get("risk.surprise_threshold_favorite_reversal_top")
            if top_fav is not None:
                favorite_winner_threshold = float(top_fav)
            else:
                base_fav = self.config.get("risk.surprise_threshold_favorite_reversal")
                favorite_winner_threshold = float(base_fav) if base_fav is not None else 0.26
                
            top_ud = self.config.get("risk.surprise_threshold_underdog_reversal_top")
            if top_ud is not None:
                underdog_winner_threshold = float(top_ud)
            else:
                base_ud = self.config.get("risk.surprise_threshold_underdog_reversal")
                underdog_winner_threshold = float(base_ud) if base_ud is not None else 0.20
        else:
            nt_fav = self.config.get("risk.surprise_threshold_favorite_reversal_non_top")
            favorite_winner_threshold = float(nt_fav) if nt_fav is not None else 0.30
            nt_ud = self.config.get("risk.surprise_threshold_underdog_reversal_non_top")
            underdog_winner_threshold = float(nt_ud) if nt_ud is not None else 0.24
            
        gm = self.config.get("risk.htft_reversal_gap_medium")
        gap_medium = float(gm) if gm is not None else 0.50
        
        gs = self.config.get("risk.htft_reversal_gap_strong")
        gap_strong = float(gs) if gs is not None else 1.00

        if favorite_side in ("H", "A"):
            threshold = (
                favorite_winner_threshold
                if winner_side == favorite_side
                else underdog_winner_threshold
            )
            if winner_side != favorite_side and gap >= gap_strong:
                threshold += 0.03
            elif winner_side != favorite_side and gap >= gap_medium:
                threshold += 0.015
            return threshold

        return base_threshold
    
    def calculate(self, ctx: CalculationContext, ms_result: Any = None) -> RiskAnalysis:  # type: ignore[override]
        """
        Wrapper for assess_risk to match BaseCalculator interface but with extra arg.
        """
        return self.assess_risk(ctx)

    def assess_risk(self, ctx: CalculationContext) -> RiskAnalysis:
        """
        Calculate risk score and level.
        Returns RiskAnalysis object.
        """
        score = 5.0
        reasons = []
        is_surprise = ctx.is_surprise
        surprise_type = ""
        
        # 1. League deviation (from UpsetEngine)
        if ctx.is_surprise:
            score += 2.0
            reasons.append("High Upset Potential detected by UpsetEngine")
            
        # 1.5 Odds Anomaly Detection
        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)
            
            has_anomaly, anomaly_res = self.anomaly_detector.detect_trap(
                ctx.odds_data, 
                ctx.home_xg, 
                ctx.away_xg, 
                home_conceded, 
                away_conceded
            )
            
            if has_anomaly:
                is_surprise = True
                score += anomaly_res.severity + 2.0
                surprise_type = "Bookmaker Trap"
                reasons.append(anomaly_res.reason)
        except Exception as e:
            print(f"⚠️ Odds Anomaly Detection Error: {e}")
            pass
        
        # 2. HT/FT Surprise Hunter (XGBoost)
        # We look for 1/2 (idx 2) and 2/1 (idx 6) from the V20 HT/FT model
        if "ht_ft" in ctx.xgboost_preds:
            ht_ft = ctx.xgboost_preds["ht_ft"]
            valid_items = [(k, float(v)) for k, v in ht_ft.items() if isinstance(v, (int, float))]
            if valid_items:
                ranked = sorted(valid_items, key=lambda item: item[1], reverse=True)
                top_label, top_prob = ranked[0]
                second_prob = ranked[1][1] if len(ranked) > 1 else 0.0
                top_gap = top_prob - second_prob

                threshold = self._dynamic_reversal_threshold(ctx, top_label)
                if getattr(ctx, "is_top_league", False):
                    top_gap_val = self.config.get("risk.surprise_min_top_gap_top")
                    if top_gap_val is not None:
                        min_gap = float(top_gap_val)
                    else:
                        base_gap_val = self.config.get("risk.surprise_min_top_gap")
                        min_gap = float(base_gap_val) if base_gap_val is not None else 0.02
                else:
                    non_top_gap_val = self.config.get("risk.surprise_min_top_gap_non_top")
                    min_gap = float(non_top_gap_val) if non_top_gap_val is not None else 0.03

                # Trigger surprise only when reversal class is:
                # - top HT/FT outcome
                # - above dynamic threshold
                # - separated from second class with a minimum gap
                if top_label in ("1/2", "2/1") and top_prob > threshold and top_gap > min_gap:
                    is_surprise = True
                    score += 3.0
                    surprise_type = f"{top_label} Reversal"
                    reasons.append(
                        f"🔥 Surprise Hunter: {top_label} potential ({round(top_prob*100, 1)}%, gap {round(top_gap*100, 1)}pp)"
                    )
                
                # NEW: Potential Upset Alert - even if reversal is not the top prediction
                # This catches cases like Bayern vs Augsburg where 1/2 was only 2% but it happened
                favorite_side, gap = self._favorite_profile_from_odds(ctx.odds_data)
                
                # Get reversal probabilities
                prob_12 = float(ht_ft.get("1/2", 0))
                prob_21 = float(ht_ft.get("2/1", 0))
                
                # DYNAMIC threshold based on odds - stronger favorite = lower threshold
                # When home odds are 1.30, even 1% reversal probability is significant
                base_threshold = float(self.config.get("risk.upset_alert_threshold", 0.05))
                
                # Calculate dynamic threshold based on favorite strength
                if favorite_side == "H":
                    home_odds = float(ctx.odds_data.get("ms_h", 2.0))
                    # Stronger favorite (lower odds) = lower threshold
                    # 1.20 odds -> 0.01 threshold, 1.50 odds -> 0.03 threshold, 2.0+ odds -> base threshold
                    if home_odds <= 1.25:
                        dynamic_threshold = 0.01  # 1% - extremely strong favorite
                    elif home_odds <= 1.40:
                        dynamic_threshold = 0.015  # 1.5% - very strong favorite
                    elif home_odds <= 1.60:
                        dynamic_threshold = 0.02  # 2% - strong favorite
                    elif home_odds < 2.00:
                        dynamic_threshold = 0.03  # 3% - moderate favorite
                    else:
                        dynamic_threshold = base_threshold
                elif favorite_side == "A":
                    away_odds = float(ctx.odds_data.get("ms_a", 2.0))
                    if away_odds <= 1.25:
                        dynamic_threshold = 0.01
                    elif away_odds <= 1.40:
                        dynamic_threshold = 0.015
                    elif away_odds <= 1.60:
                        dynamic_threshold = 0.02
                    elif away_odds < 2.00:
                        dynamic_threshold = 0.03
                    else:
                        dynamic_threshold = base_threshold
                else:
                    dynamic_threshold = base_threshold
                
                # Check for potential upset based on favorite
                if favorite_side == "H" and prob_12 > dynamic_threshold:
                    # Home favorite, but 1/2 (home leads HT, away wins FT) has potential
                    is_surprise = True
                    score += 2.0
                    surprise_type = "1/2 Potential Upset"
                    reasons.append(
                        f"⚠️ UPSET ALERT: Home favorite ({ctx.odds_data.get('ms_h', 'N/A')}) but 1/2 reversal risk ({round(prob_12*100, 1)}% > {round(dynamic_threshold*100, 1)}% threshold)"
                    )
                elif favorite_side == "A" and prob_21 > dynamic_threshold:
                    # Away favorite, but 2/1 (away leads HT, home wins FT) has potential
                    is_surprise = True
                    score += 2.0
                    surprise_type = "2/1 Potential Upset"
                    reasons.append(
                        f"⚠️ UPSET ALERT: Away favorite ({ctx.odds_data.get('ms_a', 'N/A')}) but 2/1 reversal risk ({round(prob_21*100, 1)}% > {round(dynamic_threshold*100, 1)}% threshold)"
                    )
                elif gap > 0.5 and (prob_12 > dynamic_threshold or prob_21 > dynamic_threshold):
                    # Strong favorite (big odds gap) with any reversal potential
                    reversal_type = "1/2" if prob_12 > prob_21 else "2/1"
                    reversal_prob = max(prob_12, prob_21)
                    is_surprise = True
                    score += 1.5
                    surprise_type = f"{reversal_type} Potential Upset"
                    reasons.append(
                        f"⚠️ UPSET ALERT: Strong favorite (gap {round(gap, 2)}) with {reversal_type} risk ({round(reversal_prob*100, 1)}%)"
                    )
            
        # Determine level
        if score < 4.0:
            level = "LOW"
        elif score < 7.0:
            level = "MEDIUM"
        elif score < 9.0:
            level = "HIGH"
        else:
            level = "EXTREME"
            
        return RiskAnalysis(
            risk_score=score,
            risk_level=level,
            is_surprise_risk=is_surprise,
            surprise_type=surprise_type,
            reasons=reasons
        )
