
import os
import sys
import torch
import torch.nn.functional as F
import pandas as pd
import numpy as np
import psycopg2
from psycopg2.extras import RealDictCursor
from datetime import datetime

# Add path
sys.path.append(os.getcwd())
sys.path.append(os.path.join(os.getcwd(), 'ai-engine'))

from models.hybrid_v11 import HybridDeepModel
from features.odds_history import OddsHistoryEngine

DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
MODEL_PATH = 'ai-engine/models/v11_hybrid_model.pth'
MATCH_ID = '8yl78ecnv1fqynawwtf5159uc' # User Request Re-test

def get_db_conn():
    db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
    return psycopg2.connect(db_url, cursor_factory=RealDictCursor)

def get_team_history(conn, team_id, match_date, seq_len=10):
    query = """
        SELECT 
            m.home_team_id, m.away_team_id,
            m.score_home, m.score_away,
            m.ht_score_home, m.ht_score_away
        FROM matches m
        WHERE (m.home_team_id = %s OR m.away_team_id = %s)
          AND m.mst_utc < %s
          AND m.status = 'FT'
          AND m.score_home IS NOT NULL
        ORDER BY m.mst_utc DESC
        LIMIT %s
    """
    with conn.cursor() as cur:
        cur.execute(query, (team_id, team_id, match_date, seq_len))
        rows = cur.fetchall()
        
    if len(rows) < seq_len:
        print(f"⚠️ Warning: Not enough history for team {team_id} (Found {len(rows)})")
        # Pad with zeros or return None?
        # Let's pad with simple placeholders if needed, but better to just use what we have or duplicate
        pass 
        
    # Process rows (Reverse order to be chronological)
    history = []
    for r in reversed(rows):
        # Normalize to Team Perspective
        is_home = (r['home_team_id'] == team_id)
        if is_home:
            gf = r['score_home']
            ga = r['score_away']
            res = 1.0 if gf > ga else (0.5 if gf == ga else 0.0)
            loc = 1.0
        else:
            gf = r['score_away']
            ga = r['score_home']
            res = 1.0 if gf > ga else (0.5 if gf == ga else 0.0)
            loc = 0.0
            
        history.append([gf, ga, res, loc])
        
    # Pad if short
    while len(history) < seq_len:
        history.insert(0, [0, 0, 0.5, 0.5]) # Neutral padding
        
    return np.array(history, dtype=np.float32)

def predict():
    print(f"🔮 Predicting Match: {MATCH_ID}")
    
    conn = get_db_conn()
    
    # 1. Get Match Info
    with conn.cursor() as cur:
        # Try 'matches' table first
        cur.execute("""
            SELECT m.home_team_id, m.away_team_id, m.mst_utc, 
                   ht.name as home_name_db, at.name as away_name_db
            FROM matches m
            LEFT JOIN teams ht ON m.home_team_id = ht.id
            LEFT JOIN teams at ON m.away_team_id = at.id
            WHERE m.id = %s
        """, (MATCH_ID,))
        match = cur.fetchone()
        
        if not match:
            print("⚠️ Match not found in 'matches'. Checking 'live_matches'...")
            cur.execute("""
                SELECT m.home_team_id, m.away_team_id, m.mst_utc, m.match_name,
                       ht.name as home_name_db, at.name as away_name_db
                FROM live_matches m
                LEFT JOIN teams ht ON m.home_team_id = ht.id
                LEFT JOIN teams at ON m.away_team_id = at.id
                WHERE m.id = %s
            """, (MATCH_ID,))
            match = cur.fetchone()
        
    if not match:
        print("❌ Match not found in either table!")
        return

    # Fallback for names
    home_name = match.get('home_name_db') or (match.get('match_name') or 'Unknown Home').split(' vs ')[0]
    away_name = match.get('away_name_db') or (match.get('match_name') or 'Unknown Away').split(' vs ')[-1]
    
    # CRITICAL FIX for match 8yl... where IDs are null
    if MATCH_ID == '8yl78ecnv1fqynawwtf5159uc':
        if not match['home_team_id']: 
            match['home_team_id'] = 'bmgtxgipsznlb1j20zwjti3xh' # Eyüpspor
            print(f"   🛠️  Injected Home ID: {match['home_team_id']} (Eyüpspor)")
        if not match['away_team_id']: 
            match['away_team_id'] = '2ez9cvam9lp9jyhng3eh3znb4' # Beşiktaş
            print(f"   🛠️  Injected Away ID: {match['away_team_id']} (Beşiktaş)")

    print(f"⚔️  {home_name} vs {away_name}")
    print(f"📅 Date: {datetime.fromtimestamp(match['mst_utc']/1000)}")
    
    # 2. Get Odds (Context) -> Odds might be in 'odds' column JSON in live_matches?
    # Or odd_categories table might link to live_matches? Usually they link via match_id regardless of table.
    # We will assume odd_categories works or default.
    with conn.cursor() as cur:
        try:
            cur.execute("""
                SELECT oc.name, os.name as selection, os.odd_value 
                FROM odd_categories oc
                JOIN odd_selections os ON oc.db_id = os.odd_category_db_id
                WHERE oc.match_id = %s AND oc.name IN ('Maç Sonucu', '2,5 Alt/Üst')
            """, (MATCH_ID,))
            odds_rows = cur.fetchall()
        except Exception as e:
            print(f"⚠️ Odds fetch failed: {e}")
            conn.rollback()
            odds_rows = []
        
    odds = {'1': 2.5, 'X': 3.2, '2': 2.5, 'Over': 1.80, 'Under': 1.80} # Defaults
    for r in odds_rows:
        sel = r['selection']
        val = float(r['odd_value'])
        if r['name'] == 'Maç Sonucu':
            if sel in ['1', 'X', '2']: odds[sel] = val
        elif r['name'] == '2,5 Alt/Üst':
            if 'Üst' in sel or 'Over' in sel: odds['Over'] = val
            if 'Alt' in sel or 'Under' in sel: odds['Under'] = val
            
    print(f"📊 Market Odds: 1:{odds['1']} X:{odds['X']} 2:{odds['2']} | O:{odds['Over']} U:{odds['Under']}")

    # 3. Build Sequences
    seq_home = get_team_history(conn, match['home_team_id'], match['mst_utc'])
    
    # 4. Reconstruct Team Map (MUST match training logic)
    # This ensures Team IDs map to the correct Embedding Indices.
    from pipeline.sequence_builder import SequenceBuilder
    print("   🗺️  Reconstructing Team Map for Identity alignment...")
    builder = SequenceBuilder()
    _, _, meta_all = builder.build_sequences()
    unique_teams = meta_all['team_id'].unique()
    team_map = {tid: i for i, tid in enumerate(unique_teams)}
    
    # Get Indices (Fallback to 0 if not found)
    home_idx = team_map.get(match['home_team_id'], 0)
    away_idx = team_map.get(match['away_team_id'], 0)
    print(f"   🆔 Identity: {home_name} (Idx:{home_idx}) vs {away_name} (Idx:{away_idx})")
    
    # 5. Load Model
    # ... (Model loading logic follows)
    
    try:
        state = torch.load(MODEL_PATH, map_location=DEVICE)
        # Handle shape mismatch if num_teams changed? 
        # State dict has specific size for 'entity_emb.weight'.
        emb_key = 'entity_emb.weight' if 'entity_emb.weight' in state else 'team_embedding.weight'
        saved_vocab_size = state[emb_key].shape[0]
        
        # Initialize & Load
        model = HybridDeepModel(num_teams=saved_vocab_size)
        new_state = {k.replace('team_embedding', 'entity_emb'): v for k, v in state.items()}
        model.load_state_dict(new_state, strict=False)
        print("✅ Model loaded successfully.")
        
    except Exception as e:
        print(f"❌ Model load failed: {e}")
        return

    model.eval()
    
    # 5. Prepare Input Tensors
    entities = torch.LongTensor([home_idx, away_idx]).unsqueeze(0).to(DEVICE)
    
    seq = torch.FloatTensor(seq_home).unsqueeze(0).to(DEVICE)
    
    eng = OddsHistoryEngine()
    hist_win_rate = eng.get_feature(match['home_team_id'], odds['1'])
    syn_xg = 1.35 # Avg
    
    ctx = torch.FloatTensor([
        odds['1'], odds['X'], odds['2'],
        odds['Over'], odds['Under'],
        syn_xg, syn_xg,
        hist_win_rate
    ]).unsqueeze(0).to(DEVICE)
    
    # 6. Predict
    with torch.no_grad():
        logits_res, pred_goals, logits_btts, logits_ht_ft = model(entities, seq, ctx)
        
        # 1X2
        probs_1x2 = F.softmax(logits_res, dim=1).cpu().numpy()[0]
        
        # Goals
        exp_goals = pred_goals.item()
        
        # BTTS
        prob_btts = torch.sigmoid(logits_btts).item()
        
        # HT/FT
        probs_ht = F.softmax(logits_ht_ft, dim=1).cpu().numpy()[0]
        
    # 7. Report with Value Analysis
    print("\n🧠 AI PREDICTION REPORT (V11 REFINED)")
    print("-" * 40)
    
    print(f"\n🏆 MATCH RESULT (1X2) - VALUE ANALYSIS")
    headers = ["Selection", "AI Prob", "Market Odd", "Exp. Value (EV)"]
    print(f"{headers[0]:<10} | {headers[1]:<8} | {headers[2]:<10} | {headers[3]:<12}")
    print("-" * 40)
    
    outcomes = [('1', probs_1x2[0]), ('X', probs_1x2[1]), ('2', probs_1x2[2])]
    best_ev = -99
    value_bet = None
    
    for label, prob in outcomes:
        odd = float(odds.get(label, 1.0))
        ev = (prob * odd) - 1.0 # Expected profit per 1 unit stake
        color = "✅" if ev > 0.1 else ("⚠️" if ev > -0.1 else "❌")
        print(f"{label:<10} | {prob*100:>7.1f}% | {odd:>10.2f} | {ev:>12.2f} {color}")
        
        if ev > best_ev:
            best_ev = ev
            value_bet = label

    print(f"\n👉 AI RECOMMENDATION: {value_bet} (EV: {best_ev:.2f})")
    if best_ev > 1.0:
        print("🔥 WARNING: HIGH VALUE ALERT! Odds significantly underpriced by market.")
    
    print(f"\n⚽ GOALS ANALYSIS")
    print(f"   Expected Goals: {exp_goals:.2f}")
    # Banko check
    if exp_goals < 4.5: print("   🛡️  BANKO: 4.5 Alt/Under (High Conf)")
    if exp_goals > 1.5: print("   🛡️  BANKO: 1.5 Üst/Over (High Conf)")
    
    print(f"\n⌛ HT/FT (Half Time / Full Time) - ALL CLASSES")
    ht_map = ["1/1", "1/X", "1/2", "X/1", "X/X", "X/2", "2/1", "2/X", "2/2"]
    all_idx = np.argsort(probs_ht)[::-1]
    for idx in all_idx:
        print(f"   {ht_map[idx]:<4}: {probs_ht[idx]*100:>5.1f}%")

if __name__ == "__main__":
    predict()
