#!/usr/bin/env python3
"""
亚盘预测 - 使用独立的v2亚盘模型
覆盖所有支持的联赛，使用高置信度策略
"""
from __future__ import annotations
import os, sys, json, re, warnings
from pathlib import Path
from datetime import datetime, timedelta, timezone
from collections import defaultdict
import numpy as np
import pandas as pd
import requests
import joblib

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
from generate_predictions_48h import (
    REPO_ROOT, BEIJING_TZ, DEFAULT_HOURS,
    LEAGUE_MAP, LEAGUE_ZH, TEAM_ZH,
)
from team_name_zh import ensure_team_name_map

AH_MODEL_DIR = REPO_ROOT / 'models/ah_models_v2'
FEATURES = [
    'bm_home_prob','bm_away_prob','bm_edge','bm_conf','bm_balance','bm_dir',
    'ahh','ahh_abs','ah_strong','ah_away',
    'h_gf_avg','h_ga_avg','h_tot_avg','h_ov_rate',
    'a_gf_avg','a_ga_avg','a_tot_avg','a_ov_rate',
    'h_ah_rate','a_ah_rate',
    'h_gf_l3','a_gf_l3','h_ov5','a_ov5',
    'h_strk','a_strk',
    'form_diff','eg_diff','att_diff','def_diff','home_adv',
    'bm_vs_h','bm_vs_a','bm_vs_lg',
    'lg_ah_rate','lg_tot','lg_ov',
    'mkt_align','dow','month',
]


def load_model(code):
    model_path = AH_MODEL_DIR / f'{code}_model.pkl'
    scaler_path = AH_MODEL_DIR / f'{code}_scaler.pkl'
    config_path = AH_MODEL_DIR / f'{code}_config.json'
    if not model_path.exists():
        return None, None, None
    model = joblib.load(model_path)
    scaler = joblib.load(scaler_path)
    with open(config_path) as f:
        config = json.load(f)
    return model, scaler, config


def prob(odds):
    return 1.0 / float(odds) if odds and float(odds) > 1 else np.nan


def mean5(v):
    if not v: return 2.5
    return float(np.mean(v[-5:] if len(v) >= 5 else v))

def wr(v):
    return float(np.mean(v)) if v else 0.5

def strk(v, t):
    s = 0
    for x in reversed(v):
        if x == t: s += 1
        else: break
    return s


def build_histories_from_file(code):
    """从历史文件建立球队近期统计（用于赛前预测）"""
    hp = REPO_ROOT / 'historical_data' / f'{code}_history.csv'
    if not hp.exists():
        return {}, {}

    df = pd.read_csv(hp, low_memory=False)
    df['Date'] = pd.to_datetime(df['Date'], errors='coerce', dayfirst=True)
    df = df.sort_values('Date').tail(500).reset_index(drop=True)

    # 只用有AH数据的行
    df = df[df['AHh'].notna() & (df['B365AHH'] > 1)]

    team_stats = defaultdict(lambda: {
        'gf': [], 'ga': [], 'tot': [], 'ov': [],
        'hm_gf': [], 'hm_ga': [], 'hm_tot': [], 'hm_ov': [],
        'ah': [], 'awa': [],
    })
    lg = {'tot': [], 'ov': [], 'ah': []}

    for _, row in df.iterrows():
        hm = str(row['HomeTeam'])
        aw = str(row['AwayTeam'])
        try:
            hg = float(row['FTHG']); ag = float(row['FTAG'])
            tot = hg + ag; is_ov = int(tot > 2.5)
            ahh = float(row['AHh'])
            gd = hg - ag + ahh
            ahw = int(gd > 0)

            h = team_stats[hm]; a = team_stats[aw]
            h['gf'].append(hg); h['ga'].append(ag); h['tot'].append(tot); h['ov'].append(is_ov)
            h['hm_gf'].append(hg); h['hm_ga'].append(ag); h['hm_tot'].append(tot); h['hm_ov'].append(is_ov)
            h['ah'].append(ahw)
            a['gf'].append(ag); a['ga'].append(hg); a['tot'].append(tot); a['ov'].append(is_ov)
            a['awa'].append(1 - ahw)
            lg['tot'].append(tot); lg['ov'].append(is_ov); lg['ah'].append(ahw)
        except:
            pass

    return team_stats, lg


def extract_features(home_stats, away_stats, lg_hist, ahh, bm_ph, bm_pa, bm_3w_prob, dow, month):
    h = home_stats; a = away_stats
    bm_dir = 1 if bm_ph > 0.5 else 0
    h_ah = wr(h.get('ah', [])[-10:])
    a_ah = wr(a.get('awa', [])[-10:])
    l_ah = wr(lg_hist.get('ah', [])[-50:]) if lg_hist.get('ah') else 0.5
    h_gf = mean5(h.get('gf', []))
    a_gf = mean5(a.get('gf', []))
    h_ga = mean5(h.get('ga', []))
    a_ga = mean5(a.get('ga', []))

    return {
        'bm_home_prob': bm_ph,
        'bm_away_prob': bm_pa,
        'bm_edge': bm_ph - 0.5,
        'bm_conf': abs(bm_ph - 0.5) * 2,
        'bm_balance': abs(1.0/bm_ph - 1.0/bm_pa) if bm_ph > 1 and bm_pa > 1 else 0.0,
        'bm_dir': bm_dir,
        'ahh': ahh,
        'ahh_abs': abs(ahh),
        'ah_strong': 1.0 if abs(ahh) >= 1.0 else 0.0,
        'ah_away': 1.0 if ahh > 0 else 0.0,
        'h_gf_avg': h_gf,
        'h_ga_avg': h_ga,
        'h_tot_avg': mean5(h.get('tot', [])),
        'h_ov_rate': wr(h.get('ov', [])[-10:]),
        'a_gf_avg': a_gf,
        'a_ga_avg': a_ga,
        'a_tot_avg': mean5(a.get('tot', [])),
        'a_ov_rate': wr(a.get('ov', [])[-10:]),
        'h_ah_rate': h_ah,
        'a_ah_rate': a_ah,
        'h_gf_l3': float(np.mean(h.get('gf', [])[-3:])) if h.get('gf') else 2.5,
        'a_gf_l3': float(np.mean(a.get('gf', [])[-3:])) if a.get('gf') else 2.5,
        'h_ov5': wr(h.get('ov', [])[-5:]),
        'a_ov5': wr(a.get('ov', [])[-5:]),
        'h_strk': strk(h.get('ah', []) if h.get('ah') else [], 1),
        'a_strk': strk(a.get('awa', []) if a.get('awa') else [], 1),
        'form_diff': wr(h.get('ov', [])[-5:]) - wr(a.get('ov', [])[-5:]),
        'eg_diff': h_gf - a_ga,
        'att_diff': h_gf - a_gf,
        'def_diff': a_ga - h_ga,
        'home_adv': h_gf - a_ga + 0.3,
        'bm_vs_h': float(bm_dir) - h_ah,
        'bm_vs_a': float(1 - bm_dir) - a_ah,
        'bm_vs_lg': bm_ph - l_ah if l_ah else 0.0,
        'lg_ah_rate': l_ah,
        'lg_tot': float(np.mean(lg_hist.get('tot', [2.5]))) if lg_hist.get('tot') else 2.5,
        'lg_ov': wr(lg_hist.get('ov', [])[-50:]) if lg_hist.get('ov') else 0.5,
        'mkt_align': 1.0 if ((bm_ph > 0.5) == (h_ah > 0.5)) else 0.0,
        'dow': dow,
        'month': month,
    }


def recommend_level(conf):
    """根据置信度给出推荐等级"""
    if conf >= 0.72: return '★★★ 强烈推荐', 3
    if conf >= 0.62: return '★★ 推荐', 2
    if conf >= 0.52: return '★ 可关注', 1
    return '未达标准', 0


def generate_ah_predictions_v2(hours=DEFAULT_HOURS):
    """生成亚盘预测（使用v2模型）"""
    team_name_map = ensure_team_name_map([])  # 空列表，返回已缓存的映射

    base_url = os.environ.get('API_FOOTBALL_BASE_URL', '').rstrip('/')
    api_key = os.environ.get('API_FOOTBALL_KEY', '')
    if not api_key:
        return pd.DataFrame()

    now = datetime.now(timezone.utc)
    generated_at_bj = now.astimezone(BEIJING_TZ)
    end = now + timedelta(hours=hours)

    # 获取近期fixtures
    fixtures_by_league = defaultdict(list)
    current_date = now.date()
    while current_date <= end.date():
        try:
            r = requests.get(base_url + '/fixtures',
                          params={'date': current_date.isoformat()},
                          headers={'x-apisports-key': api_key}, timeout=30)
            data = r.json()
            for item in data.get('response', []):
                lk = (item['league']['country'], item['league']['name'])
                if lk not in LEAGUE_MAP:
                    continue
                code = LEAGUE_MAP[lk]
                kickoff_utc = datetime.fromtimestamp(item['fixture']['timestamp'], tz=timezone.utc)
                if now <= kickoff_utc <= end:
                    fixtures_by_league[code].append(item)
        except:
            pass
        current_date += timedelta(days=1)

    rows = []
    for code, fixtures in fixtures_by_league.items():
        model, scaler, config = load_model(code)
        team_stats, lg_hist = build_histories_from_file(code)

        for item in fixtures:
            fid = int(item['fixture']['id'])
            home_api = item['teams']['home']['name']
            away_api = item['teams']['away']['name']
            kickoff_utc = datetime.fromtimestamp(item['fixture']['timestamp'], tz=timezone.utc)
            kickoff_bj = kickoff_utc.astimezone(BEIJING_TZ)

            # 获取赔率
            try:
                r2 = requests.get(base_url + '/odds',
                                 params={'fixture': fid},
                                 headers={'x-apisports-key': api_key}, timeout=30)
                odds_data = r2.json()
                resp = odds_data.get('response', [])
                bookmakers = resp[0].get('bookmakers', []) if resp else []
            except:
                bookmakers = []

            # 解析亚盘
            ahh_val = None; ah_home_odd = None; ah_away_odd = None
            home_3w = None
            for bm in bookmakers:
                for bet in bm.get('bets', []):
                    if bet.get('name') == 'Asian Handicap':
                        for v in bet.get('values', []):
                            raw = v.get('value', '')
                            odd = v.get('odd', 0)
                            try:
                                odd_f = float(odd)
                            except:
                                continue
                            if odd_f <= 1:
                                continue
                            parts = raw.split()
                            if len(parts) >= 2:
                                side = parts[0]
                                try:
                                    hcap = float(parts[1])
                                except:
                                    continue
                                if side == 'Home':
                                    ahh_val = hcap
                                    ah_home_odd = odd_f
                                elif side == 'Away':
                                    ah_away_odd = odd_f
                    elif bet.get('name') == 'Match Winner':
                        vals = {v.get('value'): v.get('odd') for v in bet.get('values', [])}
                        try:
                            home_3w = float(vals.get('Home', 0))
                        except:
                            pass

            if ahh_val is None or ah_home_odd is None or ah_away_odd is None:
                continue

            bm_ph = prob(ah_home_odd)
            bm_pa = prob(ah_away_odd)
            if bm_ph is np.nan or bm_pa is np.nan:
                continue
            overround = bm_ph + bm_pa
            if overround > 0:
                bm_ph_n = bm_ph / overround
                bm_pa_n = bm_pa / overround
            else:
                bm_ph_n = 0.5; bm_pa_n = 0.5

            bm_3w_prob = prob(home_3w) if home_3w else 0.5
            dow = kickoff_bj.weekday()
            month = kickoff_bj.month

            # 获取球队历史
            hk = team_name_map.get(home_api, home_api)
            ak = team_name_map.get(away_api, away_api)
            home_st = team_stats.get(hk, team_stats.get(home_api, {}))
            away_st = team_stats.get(ak, team_stats.get(away_api, {}))

            features = extract_features(home_st, away_st, lg_hist,
                                        ahh_val, bm_ph_n, bm_pa_n, bm_3w_prob, dow, month)

            # 模型预测
            if model is not None and scaler is not None:
                try:
                    X = pd.DataFrame([features])[FEATURES].fillna(0)
                    Xs = scaler.transform(X)
                    probs = model.predict_proba(Xs)[0]
                    prob_up = float(probs[1])
                    prob_down = float(probs[0])
                    conf = max(prob_up, prob_down)
                    pred = '上盘' if prob_up >= prob_down else '下盘'
                    model_type = f"AH模型v2({config.get('model', '?')})"
                    model_acc = config.get('accuracy', 0)
                except Exception as e:
                    prob_up = bm_ph_n * 100
                    prob_down = bm_pa_n * 100
                    conf = abs(bm_ph_n - 0.5) * 2
                    pred = '上盘' if ah_home_odd < ah_away_odd else '下盘'
                    model_type = '庄家AH(fallback)'
                    model_acc = np.nan
            else:
                prob_up = bm_ph_n * 100
                prob_down = bm_pa_n * 100
                conf = abs(bm_ph_n - 0.5) * 2
                pred = '上盘' if ah_home_odd < ah_away_odd else '下盘'
                model_type = '庄家AH'
                model_acc = np.nan

            rec_label, rec_level = recommend_level(conf)
            rows.append({
                'fixture_id': fid,
                '联赛代码': code,
                '预测生成时间': generated_at_bj.strftime('%Y-%m-%d %H:%M:%S'),
                '预测日期': generated_at_bj.strftime('%Y-%m-%d'),
                '预测窗口小时': int(hours),
                '联赛': LEAGUE_ZH.get(code, code),
                '开赛时间': kickoff_bj.strftime('%m-%d %H:%M'),
                '主队': team_name_map.get(home_api, TEAM_ZH.get(home_api, home_api)),
                '客队': team_name_map.get(away_api, TEAM_ZH.get(away_api, away_api)),
                '主队原名': home_api,
                '客队原名': away_api,
                '让球数': ahh_val,
                '上盘赔率': round(float(ah_home_odd), 2),
                '下盘赔率': round(float(ah_away_odd), 2),
                '主胜欧赔': home_3w,
                '上盘概率': round(prob_up, 1),
                '下盘概率': round(prob_down, 1),
                '预测方向': pred,
                '置信度': round(conf, 1),
                '推荐等级': rec_label,
                '推荐级别': rec_level,
                '模型类型': model_type,
                '模型准确率': round(float(model_acc) * 100, 1) if model_acc and not np.isnan(model_acc) else np.nan,
            })

    if not rows:
        return pd.DataFrame(columns=[
            'fixture_id', '联赛代码', '预测生成时间', '预测日期', '预测窗口小时',
            '联赛', '开赛时间', '主队', '客队', '主队原名', '客队原名',
            '让球数', '上盘赔率', '下盘赔率', '主胜欧赔',
            '上盘概率', '下盘概率', '预测方向', '置信度', '推荐等级', '推荐级别',
            '模型类型', '模型准确率'
        ])

    return pd.DataFrame(rows).sort_values(['开赛时间', '置信度'], ascending=[True, False]).reset_index(drop=True)


if __name__ == '__main__':
    df = generate_ah_predictions_v2()
    print(f"亚盘v2预测: {len(df)} 场")
    if not df.empty:
        print(df[['联赛', '主队', '客队', '让球数', '预测方向', '置信度', '推荐等级', '模型类型']].to_string())
