from dataclasses import dataclass, field
from typing import List, Optional, Any
from .base_calculator import BaseCalculator, CalculationContext
from .match_result_calculator import MatchResultPrediction
from .over_under_calculator import OverUnderPrediction
from .risk_assessor import RiskAnalysis


@dataclass
class MarketPredictionDTO:
    market_type: str
    pick: str
    probability: float
    confidence: float
    odds: float = 0.0
    is_recommended: bool = False
    is_value_bet: bool = False
    edge: float = 0.0
    is_skip: bool = False  # NEW: If model is unsure, mark as skip

@dataclass
class RecommendationResult:
    best_bet: Optional[MarketPredictionDTO]
    recommended_bets: List[MarketPredictionDTO]
    alternative_bet: Optional[MarketPredictionDTO]
    value_bets: List[MarketPredictionDTO]
    skipped_bets: List[MarketPredictionDTO]  # NEW: Track what we decided NOT to predict


class BetRecommender(BaseCalculator):
    def calculate(self,  # type: ignore[override]
                  ctx: CalculationContext,
                  ms_res: MatchResultPrediction,
                  ou_res: OverUnderPrediction,
                  risk: RiskAnalysis) -> RecommendationResult:

        odds_data = ctx.odds_data

        # Market-Specific Minimum Confidence Thresholds (Hard Gates)
        # Below these, we say "I don't know" (SKIP)
        min_conf_thresholds = {
            "MS": 45.0,      # 3-way is hard, need at least 45%
            "ÇŞ": 40.0,      # Double chance is safer, but still need 40%
            "1.5 Üst/Alt": 50.0,
            "2.5 Üst/Alt": 45.0,
            "3.5 Üst/Alt": 45.0,
            "BTTS": 45.0,
            "HT": 40.0,
        }

        # Prepare candidates
        markets = [
            MarketPredictionDTO("MS", ms_res.ms_pick,
                                ms_res.ms_home_prob if ms_res.ms_pick == "1" else (ms_res.ms_away_prob if ms_res.ms_pick == "2" else ms_res.ms_draw_prob),
                                ms_res.ms_confidence,
                                odds_data.get(f"ms_{ms_res.ms_pick.lower()}", 0)),

            MarketPredictionDTO("ÇŞ", ms_res.dc_pick,
                                ms_res.dc_1x_prob if ms_res.dc_pick == "1X" else (ms_res.dc_x2_prob if ms_res.dc_pick == "X2" else ms_res.dc_12_prob),
                                ms_res.dc_confidence,
                                odds_data.get(f"dc_{ms_res.dc_pick.lower()}", 0)),

            MarketPredictionDTO("1.5 Üst/Alt", ou_res.ou15_pick,
                                ou_res.over_15_prob if "Üst" in ou_res.ou15_pick else ou_res.under_15_prob,
                                ou_res.ou15_confidence, 0),

            MarketPredictionDTO("2.5 Üst/Alt", ou_res.ou25_pick,
                                ou_res.over_25_prob if "Üst" in ou_res.ou25_pick else ou_res.under_25_prob,
                                ou_res.ou25_confidence,
                                odds_data.get("ou25_o" if "Üst" in ou_res.ou25_pick else "ou25_u", 0)),

            MarketPredictionDTO("3.5 Üst/Alt", ou_res.ou35_pick,
                                ou_res.over_35_prob if "Üst" in ou_res.ou35_pick else ou_res.under_35_prob,
                                ou_res.ou35_confidence, 0),

            MarketPredictionDTO("BTTS", ou_res.btts_pick,
                                ou_res.btts_yes_prob if "Var" in ou_res.btts_pick else ou_res.btts_no_prob,
                                ou_res.btts_confidence,
                                odds_data.get("btts_y" if "Var" in ou_res.btts_pick else "btts_n", 0)),
        ]

        # Market weights from config (historical accuracy weighting)
        market_weights = self.config.get("recommendations.market_weights", {})
        default_weight = 1.0

        safe_markets = set(self.config.get("recommendations.safe_markets", ["ÇŞ", "1.5 Üst/Alt"]))
        risk_level = risk.risk_level

        # Confidence calibration (backtest-derived accuracy scaling)
        market_accuracy = self.config.get("recommendations.market_accuracy", {})
        baseline_accuracy = self.config.get("recommendations.baseline_accuracy", 65.0)

        def _calibrated_confidence(m):
            """Scale raw confidence by market's historical accuracy ratio."""
            accuracy = market_accuracy.get(m.market_type, baseline_accuracy) if isinstance(market_accuracy, dict) else baseline_accuracy
            ratio = accuracy / baseline_accuracy
            return m.confidence * ratio

        def _score(m):
            mw = market_weights.get(m.market_type, default_weight) if isinstance(market_weights, dict) else default_weight

            # 1. Base Score: calibrated confidence * market weight
            cal_conf = _calibrated_confidence(m)
            score = cal_conf * mw

            # 2. Value/Edge Bonus
            odds_val = m.odds if m.odds is not None else 0.0
            if odds_val > 0:
                implied = 1.0 / odds_val
                edge = (m.probability - implied) * 100
                if edge > 0:
                    score += edge * 4.0

            # 3. Risk adjustment
            if risk_level in ("HIGH", "EXTREME"):
                if m.market_type in safe_markets:
                    score *= self.config.get("recommendations.risk_safe_boost", 1.2)
                elif m.market_type == "MS":
                    score *= self.config.get("recommendations.risk_ms_penalty_high", 0.5)
                else:
                    score *= self.config.get("recommendations.risk_other_penalty", 0.7)
            elif risk_level == "MEDIUM":
                if m.market_type == "MS":
                    score *= self.config.get("recommendations.risk_ms_penalty_medium", 0.8)

            # 4. Extreme Confidence Bonus
            if cal_conf > 80:
                score *= 1.15

            return score

        recommended = []
        value_bets = []
        skipped_bets = []

        conf_thr = self.config.get("recommendations.confidence_threshold", 60)

        val_min = self.config.get("recommendations.value_confidence_min", 45) # Increased from 30
        val_max = self.config.get("recommendations.value_confidence_max", 60)
        val_margin = self.config.get("recommendations.value_edge_margin", 0.03) # Increased from 0.02
        val_upgrade = self.config.get("recommendations.value_upgrade_edge", 5.0)

        for m in markets:
            # --- SKIP LOGIC (Hard Gate) ---
            # 1. Confidence is below market threshold
            min_conf = min_conf_thresholds.get(m.market_type, 45.0)
            if m.confidence < min_conf:
                m.is_skip = True
                skipped_bets.append(m)
                continue

            # 2. Negative Value Edge (Odds are too low for our probability)
            if m.odds > 0:
                implied = 1.0 / m.odds
                edge = m.probability - implied
                # If our prob is significantly lower than implied (negative edge > 3%), SKIP
                if edge < -0.03:
                    m.is_skip = True
                    skipped_bets.append(m)
                    continue

            # --- PROCESS BET ---
            # 1. Regular recommended
            if m.confidence >= conf_thr:
                m.is_recommended = True
                recommended.append(m)

            # 2. Value bet logic
            if m.confidence is not None and val_min <= m.confidence <= val_max and m.odds > 0:
                implied = 1.0 / m.odds
                if m.probability > (implied + val_margin):
                    m.is_value_bet = True
                    m.edge = (m.probability - implied) * 100

                    if m.edge > val_upgrade:
                        m.is_recommended = True
                        recommended.append(m)
                    else:
                        value_bets.append(m)

        # Best bet (from recommended only)
        best_bet = None
        if recommended:
            # Re-sort only recommended markets to find the best one
            valid_markets = [m for m in markets if not m.is_skip and m.is_recommended]
            if valid_markets:
                valid_markets.sort(key=_score, reverse=True)
                best_bet = valid_markets[0]
                best_bet.is_recommended = True

        # Alternative bet
        alternative = None
        if risk.is_surprise_risk and ms_res.ms_pick in ["1", "2"]:
            # Check if alternative is not skipped
            alt_candidate = MarketPredictionDTO(
                "2.5 Üst/Alt", ou_res.ou25_pick,
                ou_res.over_25_prob if "Üst" in ou_res.ou25_pick else ou_res.under_25_prob,
                ou_res.ou25_confidence,
                odds_data.get("ou25_o" if "Üst" in ou_res.ou25_pick else "ou25_u", 0)
            )
            if alt_candidate.confidence >= min_conf_thresholds.get("2.5 Üst/Alt", 45.0):
                alternative = alt_candidate

        return RecommendationResult(
            best_bet=best_bet,
            recommended_bets=recommended,
            alternative_bet=alternative,
            value_bets=value_bets,
            skipped_bets=skipped_bets
        )
