#!/usr/bin/env python3
"""
回测：使用r1模型对上周（3月17-23日）的比赛进行预测，与实际结果对比
"""

import json
import math
import sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from pathlib import Path

import joblib
import numpy as np
import pandas as pd

sys.path.insert(0, str(Path(__file__).parent))
from goal_number_api_dynamic import (
    BEIJING_TZ, LEAGUE_CODE_MAP, LEAGUE_ZH_MAP,
    APIFootballClient, choose_main_total, settle_binary_full_only, load_serving_codes
)

REPO_ROOT = Path(__file__).resolve().parents[1]
ARTIFACT_DIR = REPO_ROOT / 'experiments' / 'goal_number_r1_only'
CACHE_DIR = REPO_ROOT / 'artifacts' / 'goal_number_api_dynamic_cache'

BEIJING_TZ = timezone(timedelta(hours=8))
SUPPORTED_FINAL_STATUSES = {'FT', 'AET', 'PEN'}


def mean_or_default(values, default):
    return float(np.mean(values)) if values else float(default)


def build_features(home_hist, away_hist, league_hist, ou_line):
    """基于历史数据构建特征（用于r1模型）"""
    league_tot_avg = mean_or_default(league_hist['tot'], 2.6)
    
    home_gf_avg = mean_or_default(home_hist['gf'], 1.4)
    home_ga_avg = mean_or_default(home_hist['ga'], 1.4)
    away_gf_avg = mean_or_default(away_hist['gf'], 1.2)
    away_ga_avg = mean_or_default(away_hist['ga'], 1.2)
    
    home_tot_avg = mean_or_default(home_hist['tot'], league_tot_avg)
    away_tot_avg = mean_or_default(away_hist['tot'], league_tot_avg)
    
    home_over_rate = mean_or_default(home_hist['over'], 0.5)
    away_over_rate = mean_or_default(away_hist['over'], 0.5)
    
    home_home_gf_avg = mean_or_default(home_hist['home_gf'], home_gf_avg)
    home_home_ga_avg = mean_or_default(home_hist['home_ga'], home_ga_avg)
    away_away_gf_avg = mean_or_default(away_hist['away_gf'], away_gf_avg)
    away_away_ga_avg = mean_or_default(away_hist['away_ga'], away_ga_avg)
    
    home_home_over_rate = mean_or_default(home_hist['home_over'], home_over_rate)
    away_away_over_rate = mean_or_default(away_hist['away_over'], away_over_rate)
    
    home_gf_last5 = mean_or_default(home_hist['gf'][-5:], home_gf_avg)
    home_ga_last5 = mean_or_default(home_hist['ga'][-5:], home_ga_avg)
    away_gf_last5 = mean_or_default(away_hist['gf'][-5:], away_gf_avg)
    away_ga_last5 = mean_or_default(away_hist['ga'][-5:], away_ga_avg)
    
    home_over_last5 = mean_or_default(home_hist['over'][-5:], home_over_rate)
    away_over_last5 = mean_or_default(away_hist['over'][-5:], away_over_rate)
    
    expected_goals_proxy = np.nanmean([
        home_gf_avg, away_ga_avg,
        away_gf_avg, home_ga_avg,
        home_home_gf_avg, away_away_ga_avg,
        away_away_gf_avg, home_home_ga_avg,
    ])
    
    combined_over_rate = np.nanmean([
        home_over_rate, away_over_rate,
        home_over_last5, away_over_last5,
    ])
    
    return {
        'home_gf_avg': home_gf_avg,
        'home_ga_avg': home_ga_avg,
        'away_gf_avg': away_gf_avg,
        'away_ga_avg': away_ga_avg,
        'home_tot_avg': home_tot_avg,
        'away_tot_avg': away_tot_avg,
        'home_over_rate': home_over_rate,
        'away_over_rate': away_over_rate,
        'home_home_gf_avg': home_home_gf_avg,
        'home_home_ga_avg': home_home_ga_avg,
        'away_away_gf_avg': away_away_gf_avg,
        'away_away_ga_avg': away_away_ga_avg,
        'home_home_over_rate': home_home_over_rate,
        'away_away_over_rate': away_away_over_rate,
        'home_gf_last5': home_gf_last5,
        'home_ga_last5': home_ga_last5,
        'away_gf_last5': away_gf_last5,
        'away_ga_last5': away_ga_last5,
        'home_over_last5': home_over_last5,
        'away_over_last5': away_over_last5,
        'expected_goals_proxy': expected_goals_proxy,
        'combined_over_rate': combined_over_rate,
    }


def main():
    print("=" * 70)
    print("大小球预测回测报告 - 上周（3月17-23日）")
    print("=" * 70)
    
    # 加载模型
    model_path = ARTIFACT_DIR / 'r1_goal_number_pipeline.pkl'
    config_path = ARTIFACT_DIR / 'r1_goal_number_config.json'
    
    if not model_path.exists():
        print(f"模型不存在: {model_path}")
        return
    
    model = joblib.load(model_path)
    with open(config_path, 'r', encoding='utf-8') as f:
        config = json.load(f)
    
    feature_cols = config['features']
    threshold = config.get('threshold', 0.74)
    
    print(f"\n模型信息:")
    print(f"  联赛: {config.get('league_name', 'Unknown')} ({config.get('league', '')})")
    print(f"  类型: {config.get('model', 'Unknown')}")
    print(f"  阈值: {threshold:.2f}")
    print(f"  回测准确率: {config.get('bet_acc', 0):.2%}")
    print(f"  回测样本数: {config.get('bet_count', 0)}")
    print(f"  特征数: {len(feature_cols)}")
    
    # 初始化客户端
    base_url = os.environ.get('API_FOOTBALL_BASE_URL', '').rstrip('/')
    api_key = os.environ.get('API_FOOTBALL_KEY', '')
    
    if not base_url or not api_key:
        print("缺少 API 配置，退出")
        return
    
    client = APIFootballClient(base_url, api_key, cache_dir=CACHE_DIR)
    allowed_codes = load_serving_codes()
    
    # 处理日期范围：需要更早的数据来建立历史
    # 训练数据是到3月23日，所以回测从3月17日开始是合理的
    dates = ['2026-03-17', '2026-03-18', '2026-03-19', '2026-03-20', 
             '2026-03-21', '2026-03-22', '2026-03-23']
    
    print(f"\n处理日期: {', '.join(dates)}")
    print("-" * 70)
    
    # 收集所有比赛
    all_matches = []
    for date_str in dates:
        fixtures = client.fixtures_by_date(date_str)
        odds_rows = client.odds_by_date(date_str)
        
        fixture_map = {int(x['fixture']['id']): x for x in fixtures}
        odds_map = {int(item['fixture']['id']): item for item in odds_rows}
        
        for fixture_id, fx in fixture_map.items():
            status = fx['fixture']['status']['short']
            if status not in SUPPORTED_FINAL_STATUSES:
                continue
            
            odds_item = odds_map.get(fixture_id)
            if not odds_item:
                continue
            
            chosen_total = choose_main_total(odds_item.get('bookmakers', []))
            if not chosen_total:
                continue
            
            league = fx.get('league', {})
            league_key = (league.get('country'), league.get('name'))
            league_code = LEAGUE_CODE_MAP.get(league_key, f"api_{league.get('id')}")
            
            if league_code not in allowed_codes:
                continue
            
            home_goals = int(fx['goals']['home'])
            away_goals = int(fx['goals']['away'])
            total_goals = home_goals + away_goals
            ou_line = float(chosen_total['ou_line'])
            
            actual_over = 1 if total_goals > ou_line else 0
            
            kickoff_raw = fx['fixture']['date']
            kickoff_dt = pd.to_datetime(kickoff_raw, utc=True).astimezone(BEIJING_TZ)
            
            all_matches.append({
                'fixture_id': fixture_id,
                'date': date_str,
                'kickoff_dt': kickoff_dt,
                'league_code': league_code,
                'league': LEAGUE_ZH_MAP.get(league_code, f"{league.get('country')} {league.get('name')}"),
                'home_team': fx['teams']['home']['name'],
                'away_team': fx['teams']['away']['name'],
                'ou_line': ou_line,
                'home_goals': home_goals,
                'away_goals': away_goals,
                'total_goals': total_goals,
                'actual_over': actual_over,
            })
    
    if not all_matches:
        print("没有找到可分析的比赛数据")
        return
    
    df = pd.DataFrame(all_matches)
    df = df.sort_values('kickoff_dt').reset_index(drop=True)
    
    print(f"\n总计: {len(df)} 场有盘口且已完赛的比赛")
    
    # 初始化历史数据
    team_hist = defaultdict(lambda: {
        'gf': [], 'ga': [], 'tot': [], 'over': [],
        'home_gf': [], 'home_ga': [], 'home_over': [],
        'away_gf': [], 'away_ga': [], 'away_over': [],
        'last_dt': None,
    })
    league_hist = defaultdict(lambda: {'tot': [], 'over': []})
    
    predictions = []
    
    for idx, row in df.iterrows():
        home_key = (row['league_code'], str(row['home_team']))
        away_key = (row['league_code'], str(row['away_team']))
        league_key = row['league_code']
        
        hh = team_hist[home_key]
        aa = team_hist[away_key]
        ll = league_hist[league_key]
        
        # 构建特征
        features = build_features(hh, aa, ll, row['ou_line'])
        
        # 预测
        try:
            X = pd.DataFrame([features])[feature_cols]
            prob = model.predict_proba(X)[0][1]
            
            if prob >= threshold:
                pred = '大球'
                confidence = prob
            elif prob <= (1 - threshold):
                pred = '小球'
                confidence = 1 - prob
            else:
                pred = '不确定'
                confidence = max(prob, 1-prob)
            
            hit = (pred == '大球' and row['actual_over'] == 1) or (pred == '小球' and row['actual_over'] == 0)
            
            predictions.append({
                **row.to_dict(),
                'predicted': pred,
                'prob_over': prob,
                'confidence': confidence,
                'hit': hit,
            })
        except Exception as e:
            print(f"  预测出错 at {idx}: {e}")
        
        # 更新历史
        total_goals = row['total_goals']
        ou_line = row['ou_line']
        is_over_line = settle_binary_full_only(int(total_goals), float(ou_line))
        if is_over_line is None:
            is_over_line = 1 if total_goals > ou_line else 0
        
        hh['gf'].append(float(row['home_goals']))
        hh['ga'].append(float(row['away_goals']))
        hh['tot'].append(total_goals)
        hh['over'].append(float(is_over_line))
        hh['home_gf'].append(float(row['home_goals']))
        hh['home_ga'].append(float(row['away_goals']))
        hh['home_over'].append(float(is_over_line))
        hh['last_dt'] = row['kickoff_dt']
        
        aa['gf'].append(float(row['away_goals']))
        aa['ga'].append(float(row['home_goals']))
        aa['tot'].append(total_goals)
        aa['over'].append(float(is_over_line))
        aa['away_gf'].append(float(row['away_goals']))
        aa['away_ga'].append(float(row['home_goals']))
        aa['away_over'].append(float(is_over_line))
        aa['last_dt'] = row['kickoff_dt']
        
        ll['tot'].append(total_goals)
        ll['over'].append(float(is_over_line))
    
    pred_df = pd.DataFrame(predictions)
    
    # 统计结果
    print("\n" + "=" * 70)
    print("回测结果汇总")
    print("=" * 70)
    
    total = len(pred_df)
    certain = pred_df[pred_df['predicted'] != '不确定']
    certain_count = len(certain)
    uncertain_count = total - certain_count
    
    if certain_count > 0:
        certain_hits = certain['hit'].sum()
        certain_acc = certain_hits / certain_count
        
        print(f"\n总比赛场次: {total}")
        print(f"给出预测场次: {certain_count} ({certain_count/total:.1%})")
        print(f"不确定场次: {uncertain_count} ({uncertain_count/total:.1%})")
        print(f"\n命中: {certain_hits}")
        print(f"失误: {certain_count - certain_hits}")
        print(f"准确率: {certain_acc:.2%}")
        
        # 按联赛统计
        print("\n" + "-" * 70)
        print("按联赛统计:")
        print("-" * 70)
        
        league_stats = certain.groupby('league').agg({
            'hit': ['sum', 'count'],
            'confidence': 'mean'
        }).reset_index()
        league_stats.columns = ['联赛', '命中', '总数', '平均置信度']
        league_stats['准确率'] = league_stats['命中'] / league_stats['总数']
        league_stats = league_stats.sort_values('准确率', ascending=False)
        
        for _, row in league_stats.iterrows():
            print(f"  {row['联赛']}: {int(row['命中'])}/{int(row['总数'])} ({row['准确率']:.1%}) 置信度:{row['平均置信度']:.1%}")
        
        # 按日期统计
        print("\n" + "-" * 70)
        print("按日期统计:")
        print("-" * 70)
        
        date_stats = certain.groupby('date').agg({
            'hit': ['sum', 'count']
        }).reset_index()
        date_stats.columns = ['日期', '命中', '总数']
        date_stats['准确率'] = date_stats['命中'] / date_stats['总数']
        
        for _, row in date_stats.iterrows():
            print(f"  {row['日期']}: {int(row['命中'])}/{int(row['总数'])} ({row['准确率']:.1%})")
        
        # 大球 vs 小球预测分布
        print("\n" + "-" * 70)
        print("预测分布:")
        print("-" * 70)
        
        over_preds = certain[certain['predicted'] == '大球']
        under_preds = certain[certain['predicted'] == '小球']
        
        if len(over_preds) > 0:
            over_hits = over_preds['hit'].sum()
            print(f"  预测大球: {len(over_preds)} 场, 命中: {over_hits} ({over_hits/len(over_preds):.1%})")
        
        if len(under_preds) > 0:
            under_hits = under_preds['hit'].sum()
            print(f"  预测小球: {len(under_preds)} 场, 命中: {under_hits} ({under_hits/len(under_preds):.1%})")
        
        # 详细预测结果
        print("\n" + "-" * 70)
        print("详细预测结果 (前20场):")
        print("-" * 70)
        
        for _, row in certain.head(20).iterrows():
            status = "✓" if row['hit'] else "✗"
            print(f"  {status} {row['date']} {row['league']} {row['home_team']} vs {row['away_team']}")
            print(f"      盘口:{row['ou_line']} 预测:{row['predicted']}({row['confidence']:.1%}) 实际:{'大球' if row['actual_over']==1 else '小球'} 比分:{row['home_goals']}-{row['away_goals']}")
        
        # 保存详细结果
        output_path = REPO_ROOT / 'predictions' / f'goal_number_backtest_week.csv'
        pred_df.to_csv(output_path, index=False, encoding='utf-8-sig')
        print(f"\n详细结果已保存到: {output_path}")
        
    else:
        print("\n没有足够的预测结果（模型置信度未达阈值）")
    
    print("\n" + "=" * 70)


if __name__ == '__main__':
    import os
    main()
