"""
V28 — CONDITIONAL FREQUENCY ENGINE
====================================
User's strategy automated at scale:

For every match (e.g. Beşiktaş vs Konya):
  1. Look at Beşiktaş's HOME history when their MS1 odds were in the same band (e.g. 1.30-1.40)
     → What % of those matches ended OU 1.5 over? OU 2.5 over? MS1?
  2. Look at Konya's AWAY history when their MS2 odds were in the same band (e.g. 2.00-2.20)
     → Same questions
  3. COMBINE both signals:
     → If BOTH teams historically produce >80% OU1.5 over at these odds → BET OU1.5 over
     → This is the user's exact Excel strategy, now running on 104K matches

CRITICAL: Only uses PAST matches for each prediction (no future leakage)
"""
import pandas as pd
import numpy as np
from collections import defaultdict
import warnings
warnings.filterwarnings('ignore')

# ─── Load Data ───
print("Loading data...")
df = pd.read_csv('data/training_data_v27.csv', low_memory=False)
KEEP_STR = ['match_id', 'league_name', 'home_team', 'away_team',
            'home_team_id', 'away_team_id', 'league_id', 'mst_utc']
for c in df.columns:
    if c not in KEEP_STR:
        df[c] = pd.to_numeric(df[c], errors='coerce')

# Ensure chronological order (by match_id or date)
if 'mst_utc' in df.columns:
    df['mst_utc'] = pd.to_datetime(df['mst_utc'], errors='coerce')
    df = df.sort_values('mst_utc').reset_index(drop=True)

# Filter: need valid odds + scores
df = df.dropna(subset=['odds_ms_h', 'odds_ms_a', 'score_home', 'score_away',
                        'home_team_id', 'away_team_id', 'label_ms'])

# Compute actual goal labels
df['total_goals'] = df['score_home'] + df['score_away']
df['ou15_actual'] = (df['total_goals'] > 1.5).astype(int)
df['ou25_actual'] = (df['total_goals'] > 2.5).astype(int)
df['ou35_actual'] = (df['total_goals'] > 3.5).astype(int)
df['btts_actual'] = ((df['score_home'] > 0) & (df['score_away'] > 0)).astype(int)
df['ms_result'] = df['label_ms'].astype(int)  # 0=H, 1=D, 2=A

N = len(df)
print(f"Total matches: {N}")
print(f"Unique home teams: {df.home_team_id.nunique()}")
print(f"Unique away teams: {df.away_team_id.nunique()}")

# ─── Odds Band Helper ───
def get_odds_band(odds, band_width=0.10):
    """Round odds to nearest band. E.g. 1.35 → (1.30, 1.40)"""
    lower = round(np.floor(odds / band_width) * band_width, 2)
    upper = round(lower + band_width, 2)
    return (lower, upper)

def get_odds_band_wide(odds):
    """Wider band for less common teams. E.g. 1.35 → (1.20, 1.50)"""
    if odds < 1.50:
        return (1.01, 1.50)
    elif odds < 2.00:
        return (1.50, 2.00)
    elif odds < 2.50:
        return (2.00, 2.50)
    elif odds < 3.00:
        return (2.50, 3.00)
    elif odds < 4.00:
        return (3.00, 4.00)
    elif odds < 6.00:
        return (4.00, 6.00)
    else:
        return (6.00, 20.00)

# ─── Build Conditional Frequency Lookup (Expanding Window) ───
print("\nBuilding conditional frequency features (expanding window)...")

# We'll compute features for each match using only past data
MIN_MATCHES = 5  # minimum historical matches to generate a signal

# Pre-allocate feature arrays
feat_names = [
    'home_ou15_rate_at_band', 'home_ou25_rate_at_band', 'home_ou35_rate_at_band',
    'home_btts_rate_at_band', 'home_win_rate_at_band', 'home_n_at_band',
    'away_ou15_rate_at_band', 'away_ou25_rate_at_band', 'away_ou35_rate_at_band',
    'away_btts_rate_at_band', 'away_win_rate_at_band', 'away_n_at_band',
    'combined_ou15', 'combined_ou25', 'combined_ou35', 'combined_btts',
    'home_goals_at_band', 'away_goals_at_band', 'combined_goals_at_band',
    'home_conceded_at_band', 'away_conceded_at_band',
]
features = np.full((N, len(feat_names)), np.nan)

# Historical ledger: team_id → list of (odds_band, ou15, ou25, ou35, btts, ms_result, goals_scored, goals_conceded)
home_history = defaultdict(list)  # team performances when playing HOME
away_history = defaultdict(list)  # team performances when playing AWAY

for i in range(N):
    row = df.iloc[i]
    ht_id = row.home_team_id
    at_id = row.away_team_id
    h_odds = row.odds_ms_h
    a_odds = row.odds_ms_a

    if pd.isna(h_odds) or pd.isna(a_odds):
        continue

    h_band = get_odds_band_wide(h_odds)
    a_band = get_odds_band_wide(a_odds)

    # ── Look up HOME team's historical performance at this odds band ──
    h_hist = [x for x in home_history[ht_id] if h_band[0] <= x[0] < h_band[1]]
    if len(h_hist) >= MIN_MATCHES:
        features[i, 0] = np.mean([x[1] for x in h_hist])  # ou15 rate
        features[i, 1] = np.mean([x[2] for x in h_hist])  # ou25 rate
        features[i, 2] = np.mean([x[3] for x in h_hist])  # ou35 rate
        features[i, 3] = np.mean([x[4] for x in h_hist])  # btts rate
        features[i, 4] = np.mean([x[5] for x in h_hist])  # win rate (home win = 1 if ms==0)
        features[i, 5] = len(h_hist)
        features[i, 16] = np.mean([x[6] for x in h_hist])  # avg goals scored
        features[i, 19] = np.mean([x[7] for x in h_hist])  # avg goals conceded

    # ── Look up AWAY team's historical performance at this odds band ──
    a_hist = [x for x in away_history[at_id] if a_band[0] <= x[0] < a_band[1]]
    if len(a_hist) >= MIN_MATCHES:
        features[i, 6] = np.mean([x[1] for x in a_hist])   # ou15 rate
        features[i, 7] = np.mean([x[2] for x in a_hist])   # ou25 rate
        features[i, 8] = np.mean([x[3] for x in a_hist])   # ou35 rate
        features[i, 9] = np.mean([x[4] for x in a_hist])   # btts rate
        features[i, 10] = np.mean([x[5] for x in a_hist])  # away win rate
        features[i, 11] = len(a_hist)
        features[i, 17] = np.mean([x[6] for x in a_hist])  # avg goals scored (away)
        features[i, 20] = np.mean([x[7] for x in a_hist])  # avg goals conceded (away)

    # ── Combined signals ──
    if not np.isnan(features[i, 0]) and not np.isnan(features[i, 6]):
        features[i, 12] = (features[i, 0] + features[i, 6]) / 2   # combined ou15
        features[i, 13] = (features[i, 1] + features[i, 7]) / 2   # combined ou25
        features[i, 14] = (features[i, 2] + features[i, 8]) / 2   # combined ou35
        features[i, 15] = (features[i, 3] + features[i, 9]) / 2   # combined btts
        features[i, 18] = features[i, 16] + features[i, 17]       # combined goals

    # ── Add THIS match to history (for future lookups) ──
    ou15 = int(row.total_goals > 1.5)
    ou25 = int(row.total_goals > 2.5)
    ou35 = int(row.total_goals > 3.5)
    btts = int(row.score_home > 0 and row.score_away > 0)
    h_won = int(row.label_ms == 0)
    a_won = int(row.label_ms == 2)

    home_history[ht_id].append((h_odds, ou15, ou25, ou35, btts, h_won,
                                 row.score_home, row.score_away))
    away_history[at_id].append((a_odds, ou15, ou25, ou35, btts, a_won,
                                 row.score_away, row.score_home))

    if (i+1) % 20000 == 0:
        valid = np.sum(~np.isnan(features[:i+1, 12]))
        print(f"  Processed {i+1}/{N} matches, {valid} with combined signals")

# Count valid features
valid_mask = ~np.isnan(features[:, 12])
print(f"\nMatches with combined conditional signals: {valid_mask.sum()} / {N}")

# ─── BACKTEST: Walk-Forward ───
print("\n" + "="*70)
print("  CONDITIONAL FREQUENCY BACKTEST")
print("="*70)

# Only test on last 20% of data (to avoid early sparse data)
test_start = int(N * 0.7)
test_idx = range(test_start, N)
test_valid = [i for i in test_idx if valid_mask[i]]
print(f"Test window: matches {test_start}-{N} ({len(test_valid)} with signals)")

# Strategy: bet on OU1.5 over when combined_ou15 > threshold
markets = [
    ('OU 1.5 Over', 'combined_ou15', 12, 'ou15_actual', 'odds_ou15_o'),
    ('OU 2.5 Over', 'combined_ou25', 13, 'ou25_actual', 'odds_ou25_o'),
    ('OU 3.5 Over', 'combined_ou35', 14, 'ou35_actual', 'odds_ou35_o'),
    ('BTTS Yes',    'combined_btts', 15, 'btts_actual', 'odds_btts_y'),
]

for market_name, feat_key, feat_idx, label_col, odds_col in markets:
    print(f"\n  ── {market_name} ──")

    if odds_col not in df.columns:
        print(f"    No odds column '{odds_col}', skipping")
        continue

    for threshold in [0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90]:
        bets = 0
        wins = 0
        pnl = 0.0

        for i in test_valid:
            signal = features[i, feat_idx]
            if np.isnan(signal) or signal < threshold:
                continue
            odds_val = df.iloc[i][odds_col]
            if pd.isna(odds_val) or odds_val < 1.05:
                continue
            actual = df.iloc[i][label_col]
            if pd.isna(actual):
                continue

            bets += 1
            if actual == 1:
                wins += 1
                pnl += odds_val - 1
            else:
                pnl -= 1

        if bets >= 20:
            roi = pnl / bets * 100
            hit = wins / bets * 100
            ev = (wins/bets) * (pnl/wins + 1) if wins > 0 else 0
            marker = " *** PROFITABLE ***" if roi > 0 else ""
            print(f"    Threshold>{threshold:.2f}: {bets:5d} bets, "
                  f"hit={hit:.1f}%, ROI={roi:+.1f}%{marker}")

# Also test MS (1X2) market
print(f"\n  ── Maç Sonucu (1X2) ──")
# Home win when home_win_rate_at_band > X AND away team loses often at that band
for threshold in [0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80]:
    bets = wins = 0
    pnl = 0.0
    for i in test_valid:
        h_wr = features[i, 4]   # home win rate at band
        a_lr = 1 - features[i, 10] if not np.isnan(features[i, 10]) else np.nan  # away loss rate
        if np.isnan(h_wr) or np.isnan(a_lr):
            continue
        combined = (h_wr + a_lr) / 2
        if combined < threshold:
            continue
        odds_val = df.iloc[i].odds_ms_h
        if pd.isna(odds_val) or odds_val < 1.10 or odds_val > 5.0:
            continue
        bets += 1
        if df.iloc[i].label_ms == 0:
            wins += 1
            pnl += odds_val - 1
        else:
            pnl -= 1
    if bets >= 20:
        roi = pnl / bets * 100
        hit = wins / bets * 100
        marker = " *** PROFITABLE ***" if roi > 0 else ""
        print(f"    Home win comb>{threshold:.2f}: {bets:5d} bets, "
              f"hit={hit:.1f}%, ROI={roi:+.1f}%{marker}")

# ─── DEEP DIVE: Best performing niches ───
print("\n" + "="*70)
print("  DEEP DIVE: Combined OU15 + Odds Value Filter")
print("="*70)

# The user's strategy: high confidence + the odds must pay enough
for threshold in [0.75, 0.80, 0.85, 0.90]:
    for min_odds in [1.10, 1.20, 1.30, 1.40]:
        bets = wins = 0
        pnl = 0.0
        for i in test_valid:
            signal = features[i, 12]  # combined ou15
            if np.isnan(signal) or signal < threshold:
                continue
            odds_val = df.iloc[i].get('odds_ou15_o', np.nan) if 'odds_ou15_o' in df.columns else np.nan
            if pd.isna(odds_val) or odds_val < min_odds:
                continue
            actual = df.iloc[i].ou15_actual

            bets += 1
            if actual == 1:
                wins += 1
                pnl += odds_val - 1
            else:
                pnl -= 1

        if bets >= 30:
            roi = pnl / bets * 100
            hit = wins / bets * 100
            if roi > -5:  # show near-profitable too
                marker = " *** PROFITABLE ***" if roi > 0 else ""
                print(f"  OU15 sig>{threshold:.2f} odds>{min_odds}: "
                      f"{bets:5d} bets, hit={hit:.1f}%, ROI={roi:+.1f}%{marker}")

# ─── Additional: Goal expectation accuracy ───
print("\n" + "="*70)
print("  GOAL PREDICTION ACCURACY")
print("="*70)
valid_goals = [i for i in test_valid if not np.isnan(features[i, 18])]
if valid_goals:
    pred_goals = [features[i, 18] for i in valid_goals]
    actual_goals = [df.iloc[i].total_goals for i in valid_goals]
    from sklearn.metrics import mean_absolute_error
    mae = mean_absolute_error(actual_goals, pred_goals)
    corr = np.corrcoef(pred_goals, actual_goals)[0, 1]
    print(f"  Combined goal prediction MAE: {mae:.3f}")
    print(f"  Correlation: {corr:.4f}")
    print(f"  Avg predicted: {np.mean(pred_goals):.2f}, Avg actual: {np.mean(actual_goals):.2f}")

    # Bucket analysis
    print("\n  Goal prediction buckets:")
    for low, high in [(0, 1.5), (1.5, 2.0), (2.0, 2.5), (2.5, 3.0), (3.0, 3.5), (3.5, 5.0)]:
        bucket = [i for i, pg in zip(valid_goals, pred_goals) if low <= pg < high]
        if len(bucket) >= 20:
            avg_actual = np.mean([df.iloc[i].total_goals for i in bucket])
            ou25_rate = np.mean([df.iloc[i].ou25_actual for i in bucket])
            print(f"    Predicted {low:.1f}-{high:.1f}: n={len(bucket)}, "
                  f"actual_avg={avg_actual:.2f}, OU25%={ou25_rate*100:.1f}%")

print("\nDone!")
