"""Utility Mixin — generic helpers (safe_float, label normalisation, JSON parsing).

Auto-extracted mixin module — split from services/single_match_orchestrator.py.
All methods here are composed into SingleMatchOrchestrator via inheritance.
`self` attributes (self.dsn, self.enrichment, self.v25_predictor, etc.) are
initialised in the main __init__.
"""

from __future__ import annotations

import json
import re
import time
import math
import os
import pickle
from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple, overload

import pandas as pd
import numpy as np

import psycopg2
from psycopg2.extras import RealDictCursor

from data.db import get_clean_dsn
from schemas.prediction import FullMatchPrediction
from schemas.match_data import MatchData
from models.v25_ensemble import V25Predictor, get_v25_predictor
try:
    from models.v27_predictor import V27Predictor, compute_divergence, compute_value_edge
except ImportError:
    class V27Predictor:  # type: ignore[no-redef]
        def __init__(self): self.models = {}
        def load_models(self): return False
        def predict_all(self, features): return {}
    def compute_divergence(*args, **kwargs):
        return {}
    def compute_value_edge(*args, **kwargs):
        return {}
from features.odds_band_analyzer import OddsBandAnalyzer
try:
    from models.basketball_v25 import (
        BasketballMatchPrediction,
        get_basketball_v25_predictor,
    )
except ImportError:
    BasketballMatchPrediction = Any  # type: ignore[misc]
    def get_basketball_v25_predictor() -> Any:
        raise ImportError("Basketball predictor is not available")
from core.engines.player_predictor import PlayerPrediction, get_player_predictor
from services.feature_enrichment import FeatureEnrichmentService
from services.betting_brain import BettingBrain
from services.v26_shadow_engine import V26ShadowEngine, get_v26_shadow_engine
from services.match_commentary import generate_match_commentary
from utils.top_leagues import load_top_league_ids
from utils.league_reliability import load_league_reliability
from config.config_loader import build_threshold_dict, get_threshold_default
from models.calibration import get_calibrator


class UtilsMixin:
    @staticmethod
    @overload
    def _safe_float(value: Any, default: float) -> float: ...

    @staticmethod
    @overload
    def _safe_float(value: Any, default: None = ...) -> Optional[float]: ...

    @staticmethod
    def _safe_float(value: Any, default: Optional[float] = None) -> Optional[float]:
        try:
            return float(value)
        except (TypeError, ValueError):
            return default

    @staticmethod
    def _safe_float(value: Any, default: float = 0.0) -> float:
        try:
            return float(value)
        except (TypeError, ValueError):
            return default

    @staticmethod
    def _calibrator_key(market: str, pick: str) -> Optional[str]:
        """Map (market, pick) → trained-calibrator key in models/calibration."""
        m = (market or "").upper()
        p = (pick or "").strip().casefold()
        if m == "MS":
            if p == "1":
                return "ms_home"
            if p == "x" or p == "0":
                return "ms_draw"
            if p == "2":
                return "ms_away"
            return None
        if m == "DC":
            return "dc"
        if m == "OU15" and ("over" in p or "üst" in p or "ust" in p):
            return "ou15"
        if m == "OU25" and ("over" in p or "üst" in p or "ust" in p):
            return "ou25"
        if m == "OU35" and ("over" in p or "üst" in p or "ust" in p):
            return "ou35"
        if m == "BTTS" and ("yes" in p or "var" in p):
            return "btts"
        if m == "HT":
            if p == "1":
                return "ht_home"
            if p == "x" or p == "0":
                return "ht_draw"
            if p == "2":
                return "ht_away"
            return None
        if m == "HTFT":
            return "ht_ft"
        return None

    @staticmethod
    def _confidence_label(score: float) -> Tuple[str, str]:
        """Turkish UX label + interpretation for a 0-100 confidence score."""
        if score >= 75:
            return "YUKSEK", "Bu sinyal güçlü ve güvenilir"
        if score >= 60:
            return "ORTA", "Sinyal makul, çelişen veri yok"
        if score >= 45:
            return "DUSUK", "Sinyal zayıf, dikkatli yorumla"
        return "COK_DUSUK", "Veri yetersiz veya çelişkili — bu motoru bu maç için ihmal et"

    @staticmethod
    def _to_float(value: Any, default: float) -> float:
        try:
            if value is None:
                return default
            return float(value)
        except Exception:
            return default

    @staticmethod
    def _normalize_text(value: Any) -> str:
        text = str(value or "").casefold().replace("i̇", "i")
        return " ".join(text.split())

    def _selection_value(
        self,
        selections: Dict[str, Any],
        aliases: Tuple[str, ...],
        default: float,
    ) -> float:
        if not isinstance(selections, dict):
            return default

        normalized_aliases = {self._normalize_text(alias) for alias in aliases}
        for key, value in selections.items():
            key_norm = self._normalize_text(key)
            if key_norm in normalized_aliases:
                return self._to_float(value, default)

        # Secondary match for entries like "2,5 Üst" or "Toplam Alt"
        for key, value in selections.items():
            key_norm = self._normalize_text(key)
            if any(alias in key_norm for alias in normalized_aliases):
                return self._to_float(value, default)

        return default

    def _parse_json_dict(self, payload: Any) -> Optional[Dict[str, Any]]:
        if isinstance(payload, str):
            try:
                payload = json.loads(payload)
            except Exception:
                return None
        return payload if isinstance(payload, dict) else None
