#!/usr/bin/env python3
"""Generate Goal Number predictions using the cross-league model."""

from __future__ import annotations

import json
import os
import re
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path

import joblib
import numpy as np
import pandas as pd
import requests

REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT / 'scripts') not in sys.path:
    sys.path.insert(0, str(REPO_ROOT / 'scripts'))

from team_name_zh import ensure_team_name_map

BEIJING_TZ = timezone(timedelta(hours=8))
MODEL_PATH = REPO_ROOT / 'experiments' / 'cross_league_goal_number' / 'cross_league_goal_number_pipeline.pkl'
CONFIG_PATH = REPO_ROOT / 'experiments' / 'cross_league_goal_number' / 'cross_league_goal_number_config.json'
OUTPUT_DIR = REPO_ROOT / 'predictions'
CACHE_DIR = REPO_ROOT / 'artifacts' / 'goal_number_api_dynamic_cache'

LEAGUE_CODE_MAP: Dict[Tuple[str, str], str] = {
    ('Australia', 'A-League'): 'a1',
    ('Brazil', 'Serie A'): 'br1',
    ('Czech-Republic', 'Czech Liga'): 'cz1',
    ('Germany', 'Bundesliga'): 'd1',
    ('Germany', '2. Bundesliga'): 'd2',
    ('England', 'Premier League'): 'e0',
    ('England', 'Championship'): 'e2',
    ('England', 'League One'): 'e1',
    ('England', 'League Two'): 'e3',
    ('France', 'Ligue 1'): 'f1',
    ('France', 'Ligue 2'): 'f2',
    ('Greece', 'Super League 1'): 'g1',
    ('Italy', 'Serie A'): 'i1',
    ('Japan', 'J1 League'): 'j1',
    ('Netherlands', 'Eerste Divisie'): 'e2n',
    ('Portugal', 'Primeira Liga'): 'p1',
    ('Russia', 'Premier League'): 'r1',
    ('Scotland', 'Premiership'): 'sc1',
    ('Spain', 'La Liga'): 'sp1',
    ('Turkey', 'Süper Lig'): 'tr1',
    ('USA', 'Major League Soccer'): 'm1',
    ('Uzbekistan', 'Super League'): 'uz1',
}

LEAGUE_ZH_MAP: Dict[str, str] = {
    'a1': '澳超',
    'br1': '巴甲',
    'cz1': '捷克甲',
    'd1': '德甲',
    'd2': '德乙',
    'e0': '英超',
    'e1': '英甲',
    'e2': '英冠',
    'e3': '英乙',
    'e2n': '荷乙',
    'f1': '法甲',
    'f2': '法乙',
    'g1': '希腊超',
    'i1': '意甲',
    'j1': '日职联',
    'p1': '葡超',
    'r1': '俄超',
    'sc1': '苏超',
    'sp1': '西甲',
    'tr1': '土超',
    'm1': '美职联',
    'uz1': '乌兹别克超',
}


def parse_total_value(raw: str | None) -> tuple:
    """Parse 'Over 2.5' or 'Under 2.5' into (side, line)."""
    m = re.match(r'^(Over|Under)\s+([0-9]+(?:\.[0-9]+)?)$', str(raw or '').strip(), re.I)
    if not m:
        return None, None
    return m.group(1).lower(), float(m.group(2))


def choose_main_total(bookmakers: list) -> dict | None:
    """Choose main O/U line from multiple bookmakers."""
    per_line = {}
    for bookmaker in bookmakers:
        for bet in bookmaker.get('bets', []):
            if bet.get('name') != 'Goals Over/Under':
                continue
            local = {}
            for item in bet.get('values', []):
                parsed = parse_total_value(item.get('value'))
                if not parsed:
                    continue
                side, line = parsed
                try:
                    odd = float(item.get('odd'))
                except Exception:
                    continue
                if odd <= 1:
                    continue
                local.setdefault(line, {})[side] = odd
            for line, sides in local.items():
                if 'over' not in sides or 'under' not in sides:
                    continue
                rec = per_line.setdefault(line, {'quotes': 0, 'over': [], 'under': []})
                rec['quotes'] += 1
                rec['over'].append(float(sides['over']))
                rec['under'].append(float(sides['under']))
    
    candidates = []
    for line, rec in per_line.items():
        over_odds = float(np.mean(rec['over']))
        under_odds = float(np.mean(rec['under']))
        over_prob = 1.0 / over_odds
        under_prob = 1.0 / under_odds
        fair_over = over_prob / (over_prob + under_prob) if (over_prob + under_prob) > 0 else np.nan
        candidates.append({
            'ou_line': float(line),
            'over_odds': over_odds,
            'under_odds': under_odds,
            'ou_quote_count': int(rec['quotes']),
            'fair_over': fair_over,
            'even_gap': abs((fair_over or 0.5) - 0.5),
        })
    if not candidates:
        return None
    candidates.sort(key=lambda x: (-x['ou_quote_count'], x['even_gap'], abs(x['ou_line'] - 2.5)))
    chosen = candidates[0].copy()
    chosen.pop('even_gap', None)
    return chosen


def choose_main_1x2(bookmakers: list) -> dict:
    """Choose main 1X2 odds from multiple bookmakers."""
    rows = []
    for bookmaker in bookmakers:
        for bet in bookmaker.get('bets', []):
            if bet.get('name') != 'Match Winner':
                continue
            values = {v.get('value'): v.get('odd') for v in bet.get('values', [])}
            try:
                home = float(values['Home'])
                draw = float(values['Draw'])
                away = float(values['Away'])
            except Exception:
                continue
            if min(home, draw, away) <= 1:
                continue
            rows.append((home, draw, away))
    if not rows:
        return {'home_odds': np.nan, 'draw_odds': np.nan, 'away_odds': np.nan}
    arr = np.array(rows, dtype=float)
    return {
        'home_odds': float(arr[:, 0].mean()),
        'draw_odds': float(arr[:, 1].mean()),
        'away_odds': float(arr[:, 2].mean()),
    }


def main() -> None:
    """Generate predictions using cross-league model."""
    print("Loading cross-league model...")
    pipeline = joblib.load(MODEL_PATH)
    
    with open(CONFIG_PATH) as f:
        config = json.load(f)
    
    threshold = config.get('threshold', 0.79)
    print(f"Model threshold: {threshold:.2f}")
    print(f"Model accuracy: {config.get('bet_acc', 0):.2%}")
    
    print("\nFetching fixtures and odds...")
    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)
    end = now + timedelta(hours=24)
    
    fixtures = []
    current_date = now.date()
    while current_date <= end.date():
        payload = session.get(base_url + '/fixtures', params={'date': current_date.isoformat()}, timeout=60).json()
        for item in payload.get('response', []):
            league_key = (item['league']['country'], item['league']['name'])
            if league_key not in LEAGUE_CODE_MAP:
                continue
            kickoff = datetime.fromtimestamp(item['fixture']['timestamp'], tz=timezone.utc)
            if now <= kickoff <= end:
                item['code'] = LEAGUE_CODE_MAP[league_key]
                fixtures.append(item)
        current_date += timedelta(days=1)
    
    print(f"Found {len(fixtures)} fixtures")
    
    if not fixtures:
        print("No fixtures found in the next 24 hours.")
        return
    
    rows = []
    for item in fixtures:
        try:
            odds_payload = session.get(base_url + '/odds', params={'fixture': item['fixture']['id']}, timeout=60).json()
            response = odds_payload.get('response', [])
            if not response:
                continue
            
            bookmakers = response[0].get('bookmakers', [])
            chosen_total = choose_main_total(bookmakers)
            chosen_1x2 = choose_main_1x2(bookmakers)
            
            if not chosen_total:
                continue
            
            home_team = item['teams']['home']['name']
            away_team = item['teams']['away']['name']
            kickoff_bj = datetime.fromtimestamp(item['fixture']['timestamp'], tz=timezone.utc).astimezone(BEIJING_TZ)
            
            ou_line = chosen_total['ou_line']
            over_odds = chosen_total['over_odds']
            under_odds = chosen_total['under_odds']
            
            over_prob = 1.0 / over_odds if pd.notna(over_odds) else np.nan
            under_prob = 1.0 / under_odds if pd.notna(under_odds) else np.nan
            fair_over = over_prob / (over_prob + under_prob) if (over_prob + under_prob) > 0 else np.nan
            
            home_odds = chosen_1x2.get('home_odds', np.nan)
            draw_odds = chosen_1x2.get('draw_odds', np.nan)
            away_odds = chosen_1x2.get('away_odds', np.nan)
            
            home_prob = 1.0 / home_odds if pd.notna(home_odds) and home_odds > 1 else np.nan
            draw_prob = 1.0 / draw_odds if pd.notna(draw_odds) and draw_odds > 1 else np.nan
            away_prob = 1.0 / away_odds if pd.notna(away_odds) and away_odds > 1 else np.nan
            
            # Build features (simplified)
            feature_row = {
                'ou_line': ou_line,
                'over_odds_open': over_odds,
                'under_odds_open': under_odds,
                'fair_over_open': fair_over,
                'ou_margin_open': (over_prob + under_prob - 1.0) if not pd.isna(over_prob) and not pd.isna(under_prob) else np.nan,
                'over_odds_close': np.nan,
                'under_odds_close': np.nan,
                'fair_over_close': np.nan,
                'ou_move_to_close': np.nan,
                'home_odds': home_odds,
                'draw_odds': draw_odds,
                'away_odds': away_odds,
                'home_prob': home_prob,
                'draw_prob': draw_prob,
                'away_prob': away_prob,
                'home_away_prob_gap': (home_prob - away_prob) if not pd.isna(home_prob) and not pd.isna(away_prob) else np.nan,
                'home_gf_avg': 1.4,
                'home_ga_avg': 1.4,
                'away_gf_avg': 1.2,
                'away_ga_avg': 1.2,
                'home_tot_avg': 2.6,
                'away_tot_avg': 2.6,
                'home_over_rate': 0.5,
                'away_over_rate': 0.5,
                'home_home_gf_avg': 1.4,
                'home_home_ga_avg': 1.4,
                'away_away_gf_avg': 1.2,
                'away_away_ga_avg': 1.2,
                'home_home_over_rate': 0.5,
                'away_away_over_rate': 0.5,
                'home_gf_last5': 1.4,
                'home_ga_last5': 1.4,
                'away_gf_last5': 1.2,
                'away_ga_last5': 1.2,
                'home_over_last5': 0.5,
                'away_over_last5': 0.5,
                'expected_goals_proxy': 2.6,
                'combined_over_rate': 0.5,
            }
            
            # Predict
            X = pd.DataFrame([feature_row]).fillna(0)
            prob_over = float(pipeline.predict_proba(X)[0, 1])
            prob_under = 1.0 - prob_over
            conf = max(prob_over, prob_under)
            pred = '大球' if prob_over >= prob_under else '小球'
            
            rows.append({
                '开赛时间': kickoff_bj.strftime('%m-%d %H:%M'),
                '联赛': LEAGUE_ZH_MAP.get(item['code'], item['code']),
                '主队': home_team,
                '客队': away_team,
                '大小球盘口': ou_line,
                '大球赔率': round(over_odds, 2) if pd.notna(over_odds) else np.nan,
                '小球赔率': round(under_odds, 2) if pd.notna(under_odds) else np.nan,
                '大球概率': round(prob_over * 100, 1),
                '小球概率': round(prob_under * 100, 1),
                '预测方向': pred,
                '置信度': round(conf * 100, 1),
                '是否达阈值': '是' if conf >= threshold else '否',
                '推荐等级': '推荐' if conf >= threshold else '观望',
                '模型类型': '跨联赛RF',
                '模型回测命中率': round(config.get('bet_acc', 0.85) * 100, 1),
            })
        except Exception as e:
            print(f"Error processing fixture {item['fixture']['id']}: {e}")
            continue
    
    if not rows:
        print("No predictions generated!")
        return
    
    df = pd.DataFrame(rows)
    df = df.sort_values(['开赛时间', '置信度'], ascending=[True, False]).reset_index(drop=True)
    
    print(f"Generated {len(df)} predictions")
    
    # Save
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    csv_path = OUTPUT_DIR / 'goal_number_api_dynamic_24h.csv'
    xlsx_path = OUTPUT_DIR / 'goal_number_api_dynamic_24h.xlsx'
    
    df.to_csv(csv_path, index=False, encoding='utf-8-sig')
    with pd.ExcelWriter(xlsx_path, engine='openpyxl') as writer:
        df.to_excel(writer, index=False, sheet_name='24h_predictions')
    
    print(f"Saved to: {csv_path}")
    print(f"Saved to: {xlsx_path}")
    
    # Summary
    达阈值 = len(df[df['是否达阈值'] == '是'])
    print(f"\nSummary:")
    print(f"Total predictions: {len(df)}")
    print(f"达阈值: {达阈值} ({达阈值/len(df)*100:.1f}%)")
    print(f"Model accuracy: {config.get('bet_acc', 0):.2%}")


if __name__ == '__main__':
    main()
