#!/usr/bin/env python3
"""
亚盘预测 - 使用独立的亚盘模型（0325版本）
使用独立的亚盘模型预测，而非大小球模型推导
"""
from __future__ import annotations
import os, sys, json, re, warnings
from collections import defaultdict
from pathlib import Path
from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional
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, ARCHIVE_DIR, parse_asian_handicap, fetch_json, recommend_level, confidence_band, LEAGUE_MAP, LEAGUE_ZH, TEAM_ZH

AH_MODEL_DIR = REPO_ROOT / 'models/ah_models'
AH_CONFIG_DIR = AH_MODEL_DIR  # 同目录

# 支持亚盘模型的联赛（已训练）
AH_MODEL_LEAGUES = {
    'e2': '英冠', 'e3': '英乙', 'e1': '英甲', 'd1': '德甲',
    'f1': '法甲', 'i1': '意甲', 'sp1': '西甲', 'sc1': '苏超',
    'tr1': '土超', 'p1': '葡超', 'e2n': '荷乙', 'cz1': '捷克甲',
}

FEATURE_COLUMNS = [
    'bm_ah_direction', 'bm_ah_conf', 'bm_ah_balance',
    'bm_ah_home_prob_net', 'bm_ah_away_prob_net',
    'ahh', 'ahh_abs',
    'ah_strong_home', 'ah_medium_home', 'ah_neutral', 'ah_medium_away', 'ah_strong_away',
    'bm_3way_direction', 'bm_3way_home_prob',
    'home_gf_avg', 'home_ga_avg', 'home_tot_avg', 'home_over_rate',
    'home_gf_last3', 'home_ga_last3', 'home_over_last5',
    'home_home_gf', 'home_home_ga', 'home_home_over',
    'home_ah_win_rate', 'home_ah_streak',
    'away_gf_avg', 'away_ga_avg', 'away_tot_avg', 'away_over_rate',
    'away_gf_last3', 'away_ga_last3', 'away_over_last5',
    'away_away_gf', 'away_away_ga', 'away_away_over',
    'away_ah_win_rate',
    'league_ah_home_win_rate', 'league_tot_avg', 'league_over_rate',
    'form_diff', 'eg_diff', 'home_away_gf_diff', 'home_away_def_diff',
    'bm_dir_vs_home_form', 'bm_dir_vs_away_form',
    'day_of_week', 'month',
]


def load_ah_model(code: str):
    """加载单个联赛的亚盘模型"""
    model_path = AH_MODEL_DIR / f'{code}_ah_model.pkl'
    scaler_path = AH_MODEL_DIR / f'{code}_ah_scaler.pkl'
    config_path = AH_MODEL_DIR / f'{code}_ah_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):
    if odds and float(odds) > 1:
        return 1.0 / float(odds)
    return np.nan


def mean_or_default(v, d):
    return float(np.mean(v)) if v else float(d)


def build_team_histories_from_api(fixtures: List[dict], league_code: str) -> dict:
    """从API获取的历史数据建立球队历史"""
    # 使用历史数据文件
    hp = REPO_ROOT / 'historical_data' / f'{league_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').reset_index(drop=True)

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

    team_stats = {}
    league_ah = []
    league_tot = []
    league_over = []

    for _, row in df.tail(500).iterrows():
        home = str(row['HomeTeam'])
        away = str(row['AwayTeam'])
        hg = float(row['FTHG'])
        ag = float(row['FTAG'])
        tot = hg + ag
        is_over = int(tot > 2.5)
        ahh = float(row['AHh'])
        gd = hg - ag + ahh
        ah_hw = int(gd > 0)

        if home not in team_stats:
            team_stats[home] = {'gf': [], 'ga': [], 'tot': [], 'over': [],
                                 'home_gf': [], 'home_ga': [], 'home_tot': [], 'home_over': [],
                                 'ah_home_wins': [], 'ah_away_wins': []}
        if away not in team_stats:
            team_stats[away] = {'gf': [], 'ga': [], 'tot': [], 'over': [],
                                 'home_gf': [], 'home_ga': [], 'home_tot': [], 'home_over': [],
                                 'ah_home_wins': [], 'ah_away_wins': []}

        h = team_stats[home]
        a = team_stats[away]
        h['gf'].append(hg); h['ga'].append(ag); h['tot'].append(tot); h['over'].append(is_over)
        h['home_gf'].append(hg); h['home_ga'].append(ag); h['home_tot'].append(tot); h['home_over'].append(is_over)
        h['ah_home_wins'].append(ah_hw)
        a['gf'].append(ag); a['ga'].append(hg); a['tot'].append(tot); a['over'].append(is_over)
        a['away_gf'].append(ag); a['away_ga'].append(hg); a['away_tot'].append(tot); a['away_over'].append(is_over)
        a['ah_away_wins'].append(1 - ah_hw)
        league_ah.append(ah_hw)
        league_tot.append(tot)
        league_over.append(is_over)

    league_hist = {'ah_hw': league_ah, 'tot': league_tot, 'over': league_over}
    return team_stats, league_hist


def extract_features(home_stats: dict, away_stats: dict, league_hist: dict,
                     ahh: float, bm_home_prob: float, bm_away_prob: float,
                     bm_home_3w: float, date) -> dict:
    """提取亚盘特征"""
    def mean5(v):
        if not v: return 2.5
        return float(np.mean(v[-5:] if len(v) >= 5 else v))
    def mean3(v):
        if not v: return 2.5
        return float(np.mean(v[-3:] if len(v) >= 3 else v))
    def wr(v):
        if not v: return 0.5
        return float(np.mean(v))
    def strk(v, t):
        s = 0
        for x in reversed(v):
            if x == t: s += 1
            else: break
        return s

    h = home_stats
    a = away_stats
    overround = bm_home_prob + bm_away_prob
    bm_home_net = bm_home_prob / overround if overround > 0 else 0.5

    return {
        'bm_ah_direction': 1 if bm_home_net > 0.5 else 0,
        'bm_ah_conf': abs(bm_home_net - 0.5) * 2,
        'bm_ah_balance': abs(1.0/bm_home_prob - 1.0/bm_away_prob) if bm_home_prob > 1 and bm_away_prob > 1 else 0,
        'bm_ah_home_prob_net': bm_home_net,
        'bm_ah_away_prob_net': 1 - bm_home_net,
        'ahh': ahh,
        'ahh_abs': abs(ahh),
        'ah_strong_home': 1 if ahh <= -1.0 else 0,
        'ah_medium_home': 1 if -1.0 < ahh < 0 else 0,
        'ah_neutral': 1 if abs(ahh) <= 0.01 else 0,
        'ah_medium_away': 1 if 0 < ahh <= 0.75 else 0,
        'ah_strong_away': 1 if ahh > 0.75 else 0,
        'bm_3way_direction': 1 if bm_home_3w > 0.5 else 0,
        'bm_3way_home_prob': bm_home_3w,
        'home_gf_avg': mean5(h.get('gf', [])),
        'home_ga_avg': mean5(h.get('ga', [])),
        'home_tot_avg': mean5(h.get('tot', [])),
        'home_over_rate': wr(h.get('over', [])[-10:] if h.get('over') else []),
        'home_gf_last3': mean3(h.get('gf', [])),
        'home_ga_last3': mean3(h.get('ga', [])),
        'home_over_last5': wr(h.get('over', [])[-5:] if h.get('over') else []),
        'home_home_gf': mean5(h.get('home_gf', [])),
        'home_home_ga': mean5(h.get('home_ga', [])),
        'home_home_over': wr(h.get('home_over', [])[-5:] if h.get('home_over') else []),
        'home_ah_win_rate': wr(h.get('ah_home_wins', [])[-10:] if h.get('ah_home_wins') else []),
        'home_ah_streak': strk(h.get('ah_home_wins', []) if h.get('ah_home_wins') else [], 1),
        'away_gf_avg': mean5(a.get('gf', [])),
        'away_ga_avg': mean5(a.get('ga', [])),
        'away_tot_avg': mean5(a.get('tot', [])),
        'away_over_rate': wr(a.get('over', [])[-10:] if a.get('over') else []),
        'away_gf_last3': mean3(a.get('gf', [])),
        'away_ga_last3': mean3(a.get('ga', [])),
        'away_over_last5': wr(a.get('over', [])[-5:] if a.get('over') else []),
        'away_away_gf': mean5(a.get('away_gf', [])),
        'away_away_ga': mean5(a.get('away_ga', [])),
        'away_away_over': wr(a.get('away_over', [])[-5:] if a.get('away_over') else []),
        'away_ah_win_rate': wr(a.get('ah_away_wins', [])[-10:] if a.get('ah_away_wins') else []),
        'league_ah_home_win_rate': float(np.mean(league_hist.get('ah_hw', [0.5]))) if league_hist.get('ah_hw') else 0.5,
        'league_tot_avg': float(np.mean(league_hist.get('tot', [2.5]))) if league_hist.get('tot') else 2.5,
        'league_over_rate': float(np.mean(league_hist.get('over', [0.5]))) if league_hist.get('over') else 0.5,
        'form_diff': wr(h.get('over', [])[-5:] if h.get('over') else []) - wr(a.get('over', [])[-5:] if a.get('over') else []),
        'eg_diff': mean5(h.get('gf', [])) - mean5(a.get('ga', [])),
        'home_away_gf_diff': mean5(h.get('home_gf', [])) - mean5(a.get('away_gf', [])),
        'home_away_def_diff': mean5(h.get('home_ga', [])) - mean5(a.get('away_ga', [])),
        'bm_dir_vs_home_form': (1 if bm_home_net > 0.5 else 0) - wr(h.get('ah_home_wins', [])[-5:] if h.get('ah_home_wins') else []),
        'bm_dir_vs_away_form': (0 if bm_home_net > 0.5 else 1) - wr(a.get('ah_away_wins', [])[-5:] if a.get('ah_away_wins') else []),
        'day_of_week': date.weekday() if hasattr(date, 'weekday') else 3,
        'month': date.month if hasattr(date, 'month') else 6,
    }


def generate_ah_predictions(hours: int = DEFAULT_HOURS) -> pd.DataFrame:
    """生成亚盘预测（使用独立AH模型）"""
    base_url = os.environ['API_FOOTBALL_BASE_URL'].rstrip('/')
    api_key = os.environ['API_FOOTBALL_KEY']
    session = requests.Session()
    session.headers.update({'x-apisports-key': api_key})

    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():
        payload = fetch_json(session, base_url, '/fixtures', {'date': current_date.isoformat()})
        for item in payload.get('response', []):
            league_key = (item['league']['country'], item['league']['name'])
            if league_key not in LEAGUE_MAP:
                continue
            code = LEAGUE_MAP[league_key]
            kickoff = datetime.fromtimestamp(item['fixture']['timestamp'], tz=timezone.utc)
            if now <= kickoff <= end:
                item['code'] = code
                fixtures_by_league[code].append(item)
        current_date += timedelta(days=1)

    # 对每个联赛生成预测
    rows = []
    for code in sorted(fixtures_by_league.keys()):
        subset = fixtures_by_league[code]
        model, scaler, config = load_ah_model(code)

        # 建立球队历史
        team_stats, league_hist = build_team_histories_from_api(subset, code)

        for item in subset:
            odds_payload = fetch_json(session, base_url, '/odds', {'fixture': item['fixture']['id']})
            response = odds_payload.get('response', [])
            if not response:
                continue
            bookmakers = response[0].get('bookmakers', [])
            handicap, home_handicap_odd, away_handicap_odd = parse_asian_handicap(bookmakers)
            home_odds, draw_odds, away_odds = None, None, None
            for bm in bookmakers:
                for bet in bm.get('bets', []):
                    if bet.get('name') == 'Match Winner':
                        vals = {v.get('value'): v.get('odd') for v in bet.get('values', [])}
                        try:
                            home_odds = float(vals.get('Home'))
                            draw_odds = float(vals.get('Draw'))
                            away_odds = float(vals.get('Away'))
                        except:
                            pass
                if home_odds:
                    break

            if handicap is None or home_handicap_odd is None or away_handicap_odd is None:
                continue

            home = item['teams']['home']['name']
            away = item['teams']['away']['name']
            kickoff_bj = datetime.fromtimestamp(item['fixture']['timestamp'], tz=timezone.utc).astimezone(BEIJING_TZ)

            bm_home_prob = prob(home_handicap_odd)
            bm_away_prob = prob(away_handicap_odd)
            bm_home_3w = prob(home_odds) if home_odds else 0.5

            if model is not None and scaler is not None:
                # 使用独立AH模型
                h = team_stats.get(home, {'gf': [], 'ga': [], 'tot': [], 'over': [],
                                         'home_gf': [], 'home_ga': [], 'home_tot': [], 'home_over': [],
                                         'ah_home_wins': [], 'ah_away_wins': []})
                a = team_stats.get(away, {'gf': [], 'ga': [], 'tot': [], 'over': [],
                                         'home_gf': [], 'home_ga': [], 'home_tot': [], 'home_over': [],
                                         'ah_home_wins': [], 'ah_away_wins': []})
                features = extract_features(h, a, league_hist, handicap, bm_home_prob, bm_away_prob, bm_home_3w, kickoff_bj)
                try:
                    X = pd.DataFrame([features])[FEATURE_COLUMNS].fillna(0)
                    X_s = scaler.transform(X)
                    probs = model.predict_proba(X_s)[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模型({config.get('model_name', '?')})"
                    model_acc = config.get('accuracy', 0)
                except Exception as e:
                    # 模型预测失败，fallback到庄家方向
                    prob_up = bm_home_prob / (bm_home_prob + bm_away_prob) * 100 if bm_home_prob + bm_away_prob > 0 else 50
                    prob_down = 100 - prob_up
                    conf = abs(prob_up - 50)
                    pred = '上盘' if home_handicap_odd < away_handicap_odd else '下盘'
                    model_type = '庄家AH'
                    model_acc = np.nan
            else:
                # 无模型，直接用庄家方向
                prob_up = bm_home_prob / (bm_home_prob + bm_away_prob) * 100 if bm_home_prob + bm_away_prob > 0 else 50
                prob_down = 100 - prob_up
                conf = abs(prob_up - 50)
                pred = '上盘' if home_handicap_odd < away_handicap_odd else '下盘'
                model_type = '庄家AH(fallback)'
                model_acc = np.nan

            rec_label, rec_level = recommend_level(conf)
            rows.append({
                'fixture_id': int(item['fixture']['id']),
                '联赛代码': 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'),
                '主队': home,
                '客队': away,
                '让球数': handicap,
                '上盘赔率': round(float(home_handicap_odd), 2),
                '下盘赔率': round(float(away_handicap_odd), 2),
                '主胜欧赔': round(float(home_odds), 2) if home_odds else np.nan,
                '平局欧赔': round(float(draw_odds), 2) if draw_odds else np.nan,
                '客胜欧赔': round(float(away_odds), 2) if away_odds else np.nan,
                '上盘概率': round(prob_up, 1),
                '下盘概率': round(prob_down, 1),
                '预测方向': pred,
                '置信区间': confidence_band(conf),
                '置信度': 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()
    print(f"亚盘预测: {len(df)} 场")
    if not df.empty:
        print(df[['联赛', '主队', '客队', '让球数', '预测方向', '置信度', '推荐等级', '模型类型']].to_string())
