"""
Live Betting Signal Scanner
Downloads today's fixtures from football-data.co.uk and flags
value bets using the CLV strategy (B365 vs sharp reference odds).

Sharp reference priority: BFE (Betfair Exchange) > Avg market odds
"""
import pandas as pd
import urllib.request
from datetime import datetime

# ---------------------------------------------------------------
# CONFIG
# ---------------------------------------------------------------
BANKROLL       = 1000.0   # <-- set your current bankroll here
KELLY_MULT     = 0.25     # quarter Kelly
EDGE_THRESHOLD = 0.05     # 5% minimum edge
MAX_BET_PCT    = 0.10     # cap any single bet at 10% of bankroll
FIXTURES_URL   = 'https://www.football-data.co.uk/fixtures.csv'

# Consistently profitable league+outcome combinations (backtested 2012-2026)
LEAGUES = [
    'E0','E1','E2','E3','EC','SP1','SP2','I1','I2','D1','D2',
    'F1','F2','N1','B1','P1','T1','G1','SC0','SC1','SC2','SC3',
]
OUTCOMES = [('Home','B365H'),('Draw','B365D'),('Away','B365A')]
LEAGUE_NAMES = {
    'E0':'EPL','E1':'Championship','E2':'League One','E3':'League Two','EC':'Conference',
    'SP1':'La Liga','SP2':'Segunda','I1':'Serie A','I2':'Serie B',
    'D1':'Bundesliga','D2':'2.Bundesliga','F1':'Ligue 1','F2':'Ligue 2',
    'N1':'Eredivisie','B1':'Belgian Pro League','P1':'Primeira Liga',
    'T1':'Super Lig','G1':'Greek SL',
    'SC0':'Scottish Prem','SC1':'Scottish Champ','SC2':'Scottish L1','SC3':'Scottish L2',
}

# ---------------------------------------------------------------
def remove_margin(h, d, a):
    over = 1/h + 1/d + 1/a
    return 1/h/over, 1/d/over, 1/a/over

def kelly_stake(edge, net_odds, bankroll):
    f = edge / net_odds
    f = f * KELLY_MULT
    f = min(f, MAX_BET_PCT)
    return round(bankroll * f, 2)

# ---------------------------------------------------------------
# Download fixtures
# ---------------------------------------------------------------
print(f"Downloading fixtures... ({datetime.now().strftime('%Y-%m-%d %H:%M')})")
urllib.request.urlretrieve(FIXTURES_URL, 'E:/soccer/fixtures_live.csv')
df = pd.read_csv('E:/soccer/fixtures_live.csv', encoding='utf-8-sig')
df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
df = df.dropna(subset=['Date'])
print(f"Loaded {len(df)} fixtures across {df['Div'].nunique()} leagues")
print(f"Date range: {df['Date'].min().date()} to {df['Date'].max().date()}\n")

# ---------------------------------------------------------------
# Scan for signals
# ---------------------------------------------------------------
signals = []

for _, row in df.iterrows():
    div = row.get('Div')
    if div not in LEAGUES:
        continue

    # Sharp reference: prefer BFE (Betfair Exchange), fall back to Avg
    ref_h = ref_d = ref_a = ref_source = None
    for h_col, d_col, a_col in [('BFEH','BFED','BFEA'), ('AvgH','AvgD','AvgA')]:
        try:
            rh = float(row[h_col]); rd = float(row[d_col]); ra = float(row[a_col])
            if all(x > 1.0 for x in [rh, rd, ra]):
                ref_h, ref_d, ref_a = rh, rd, ra
                ref_source = h_col.replace('H','')
                break
        except (ValueError, KeyError, TypeError):
            continue

    if ref_h is None:
        continue

    tp_h, tp_d, tp_a = remove_margin(ref_h, ref_d, ref_a)
    fair_map = {'B365H': 1/tp_h, 'B365D': 1/tp_d, 'B365A': 1/tp_a}
    prob_map = {'B365H': tp_h,   'B365D': tp_d,   'B365A': tp_a  }

    for outcome, b365_col in OUTCOMES:
        try:
            b365_odds = float(row[b365_col])
        except (ValueError, KeyError, TypeError):
            continue
        if b365_odds <= 1.0:
            continue

        ps_fair = fair_map[b365_col]
        ps_prob = prob_map[b365_col]
        edge = (b365_odds - ps_fair) / ps_fair
        if edge < EDGE_THRESHOLD:
            continue

        ev = b365_odds * ps_prob - 1
        net_odds = b365_odds - 1
        if ev <= 0:
            continue
        kelly_f_raw = ev / net_odds
        stake = kelly_stake(ev, net_odds, BANKROLL)

        signals.append({
            'Date':       row['Date'].strftime('%a %d %b'),
            'Time':       str(row.get('Time', '')).strip(),
            'League':     LEAGUE_NAMES.get(div, div),
            'Home':       row['HomeTeam'],
            'Away':       row['AwayTeam'],
            'Bet':        outcome,
        'B365':       round(b365_odds, 2),
        'PS_fair':    round(ps_fair, 2),
        'Edge%':      round(edge*100, 1),
        'Kelly_raw%': round(kelly_f_raw*100, 1),
        'Stake £':    stake,
        'Ref':        ref_source,
    })

signals_df = pd.DataFrame(signals).sort_values(['Date','Time']).reset_index(drop=True)

# ---------------------------------------------------------------
# Print betting slip
# ---------------------------------------------------------------
print("=" * 95)
print(f"BETTING SIGNALS — {datetime.now().strftime('%Y-%m-%d')}  |  Bankroll: £{BANKROLL:,.2f}  |  Strategy: {KELLY_MULT}x Kelly, >{EDGE_THRESHOLD*100:.0f}% edge")
print("=" * 95)

if signals_df.empty:
    print("  No qualifying bets found.")
else:
    total_stake = signals_df['Stake £'].sum()
    print(f"\n  {len(signals_df)} qualifying bets  |  Total stake: £{total_stake:,.2f}  ({total_stake/BANKROLL*100:.1f}% of bankroll)\n")
    print(f"  {'Date':<12} {'Time':<7} {'League':<20} {'Match':<35} {'Bet':<6} {'B365':>6} {'Ref Fair':>9} {'Edge':>6} {'Kelly':>6} {'Stake £':>8} {'Ref':<5}")
    print("  " + "-"*100)

    for _, s in signals_df.iterrows():
        match = f"{s['Home']} vs {s['Away']}"
        if len(match) > 34: match = match[:31] + "..."
        print(f"  {s['Date']:<12} {s['Time']:<7} {s['League']:<20} {match:<35} {s['Bet']:<6} "
              f"{s['B365']:>6.2f} {s['PS_fair']:>9.2f} {s['Edge%']:>5.1f}% {s['Kelly_raw%']:>5.1f}% "
              f"£{s['Stake £']:>7.2f} {s['Ref']:<5}")

    print()
    print("=" * 95)
    print(f"  Total outlay: £{total_stake:,.2f}")
    print(f"  If all bets win (unlikely): £{sum((s['B365']-1)*s['Stake £'] for _,s in signals_df.iterrows()):+,.2f}")
    print(f"  If all bets lose (worst case): -£{total_stake:,.2f}")
    print("=" * 95)

# ---------------------------------------------------------------
# Also show upcoming fixtures with no signal (close to threshold)
# ---------------------------------------------------------------
near_misses = []
for _, row in df.iterrows():
    div = row.get('Div')
    if div not in LEAGUE_CONFIG: continue
    outcome, b365_col, ps_col = LEAGUE_CONFIG[div]
    try:
        b365_odds = float(row[b365_col])
    except: continue
    ref_h = ref_d = ref_a = None
    for h_col,d_col,a_col in [('BFEH','BFED','BFEA'),('AvgH','AvgD','AvgA')]:
        try:
            rh=float(row[h_col]); rd=float(row[d_col]); ra=float(row[a_col])
            if all(x>1.0 for x in [rh,rd,ra]):
                ref_h,ref_d,ref_a = rh,rd,ra; break
        except: continue
    if ref_h is None or b365_odds<=1.0: continue
    tp_h,tp_d,tp_a = remove_margin(ref_h,ref_d,ref_a)
    ps_fair = {'B365H':1/tp_h,'B365D':1/tp_d,'B365A':1/tp_a}[b365_col]
    edge = (b365_odds - ps_fair) / ps_fair
    if 0.02 <= edge < EDGE_THRESHOLD:
        near_misses.append({
            'Date': row['Date'].strftime('%a %d %b'),
            'Time': str(row.get('Time','')).strip(),
            'League': LEAGUE_NAMES.get(div,div),
            'Match': f"{row['HomeTeam']} vs {row['AwayTeam']}",
            'Bet': outcome,
            'B365': round(b365_odds,2),
            'PS_fair': round(ps_fair,2),
            'Edge%': round(edge*100,1),
        })

if near_misses:
    nm_df = pd.DataFrame(near_misses).sort_values('Edge%', ascending=False)
    print(f"\n  NEAR MISSES (2-5% edge, watching brief):")
    print(f"  {'Date':<12} {'Time':<7} {'League':<20} {'Match':<35} {'Bet':<6} {'B365':>6} {'PS Fair':>8} {'Edge':>6}")
    print("  " + "-"*93)
    for _, s in nm_df.iterrows():
        match = s['Match'] if len(s['Match'])<=34 else s['Match'][:31]+"..."
        print(f"  {s['Date']:<12} {s['Time']:<7} {s['League']:<20} {match:<35} {s['Bet']:<6} "
              f"{s['B365']:>6.2f} {s['PS_fair']:>8.2f} {s['Edge%']:>5.1f}%")
