import os
from flask import Flask, jsonify
from flask_cors import CORS
from dotenv import load_dotenv
import cfbd
from datetime import datetime
import json
import time
import pickle
import fcntl
import tempfile
import shutil

load_dotenv()

# Get the directory where this app.py file is located
NEEDLE_DIR = os.path.dirname(os.path.abspath(__file__))

app = Flask(__name__, static_folder=os.path.join(NEEDLE_DIR, 'static'), static_url_path='')
CORS(app)

# CFBD API setup
api_key = os.getenv('CFBD_API_KEY', '')
configuration = cfbd.Configuration(access_token=api_key)
api_client = cfbd.ApiClient(configuration)

# Constants
TOTAL_BOWL_SPOTS = 82
CURRENT_YEAR = datetime.now().year
CURRENT_WEEK = None  # Will be determined dynamically

# Special teams
TRANSITIONAL_TEAMS = ['Missouri State', 'Delaware']
ARMY_TEAM = 'Army'
# Teams that are permanently ineligible (e.g., postseason bans)
INELIGIBLE_TEAMS = ['Akron', 'Akron Zips']

# Team name normalization mapping (CFBD name -> Display name)
TEAM_NAME_MAPPING = {
    'App State': 'Appalachian State',
    'South Florida': 'USF',
    'Western Kentucky': 'WKU',
    'Florida International': 'FIU',
    'Florida Atlantic': 'FAU',
    'East Carolina': 'ECU',
    'Massachusetts': 'UMass',
    'UL Monroe': 'ULM',
    # Add more mappings as needed
}

# Cache for display name -> APR abbreviated name mapping
_display_to_apr_cache = None

def get_apr_key(display_name):
    """Convert display name to APR data key (abbreviated name)"""
    global _display_to_apr_cache
    
    # Build cache on first call
    if _display_to_apr_cache is None:
        _display_to_apr_cache = {}
        import csv
        try:
            csv_path = os.path.join(NEEDLE_DIR, 'cfb_mastersheet.csv')
            with open(csv_path, 'r', encoding='utf-8') as f:
                reader = csv.DictReader(f)
                for row in reader:
                    if row.get('family_name') == 'FBS':
                        flair_name = row.get('flair_name', '').strip()
                        abbrev = row.get('flair_abbreviated', '').strip()
                        if flair_name and abbrev:
                            # Map CFBD name to abbrev
                            _display_to_apr_cache[flair_name] = abbrev
                            # Map display name (after TEAM_NAME_MAPPING) to abbrev
                            display_name_mapped = TEAM_NAME_MAPPING.get(flair_name, flair_name)
                            _display_to_apr_cache[display_name_mapped] = abbrev
        except Exception as e:
            print(f"Error building APR cache: {e}")
    
    # Return cached value or fallback to display name
    return _display_to_apr_cache.get(display_name, display_name)

# Conference bowl guarantees (approximate - you may need to adjust)
CONFERENCE_GUARANTEES = {
    'SEC': 10,
    'Big Ten': 9,
    'Big 12': 8,
    'ACC': 8,
    'Pac-12': 6,
    'American': 6,
    'Mountain West': 5,
    'MAC': 3,
    'Sun Belt': 3,
    'C-USA': 2,
    'Independents': 1,
}

# APR rankings for 5-7 teams (you'll need to provide actual data)
try:
    from apr_data import APR_RANKINGS
except ImportError:
    APR_RANKINGS = {}  # Will be populated from your data


# Cache for current week (refresh every 5 minutes)
_current_week_cache = {'value': None, 'timestamp': 0}
_current_week_cache_ttl = 300  # 5 minutes

def get_current_week():
    """Determine the current week of the season (cached)"""
    global _current_week_cache
    
    # Check cache
    current_time = time.time()
    if _current_week_cache['value'] is not None and (current_time - _current_week_cache['timestamp']) < _current_week_cache_ttl:
        return _current_week_cache['value']
    
    # Fetch fresh data
    games_api = cfbd.GamesApi(api_client)
    try:
        # Check multiple weeks to find the latest week with games
        latest_week = 1
        for week in range(1, 16):
            try:
                games = games_api.get_games(year=CURRENT_YEAR, week=week, season_type='regular', classification='fbs')
                if games:
                    latest_week = week
            except:
                continue
        result = latest_week if latest_week > 1 else 13
        
        # Update cache
        _current_week_cache = {'value': result, 'timestamp': current_time}
        return result
    except Exception as e:
        print(f"Error determining week: {e}")
        # Return cached value if available, otherwise default
        if _current_week_cache['value'] is not None:
            return _current_week_cache['value']
    return 13  # Default to week 13

# Cache for FBS teams list (refresh every hour)
_fbs_teams_cache = {'value': None, 'timestamp': 0}
_fbs_teams_cache_ttl = 3600  # 1 hour

def get_fbs_teams_cached():
    """Get FBS teams list (cached)"""
    global _fbs_teams_cache
    
    current_time = time.time()
    if _fbs_teams_cache['value'] is not None and (current_time - _fbs_teams_cache['timestamp']) < _fbs_teams_cache_ttl:
        return _fbs_teams_cache['value']
    
    teams_api = cfbd.TeamsApi(api_client)
    fbs_teams = teams_api.get_fbs_teams()
    _fbs_teams_cache = {'value': fbs_teams, 'timestamp': current_time}
    return fbs_teams


# Cache for FBS teams list (refresh every hour)
_fbs_teams_cache = {'value': None, 'timestamp': 0}
_fbs_teams_cache_ttl = 3600  # 1 hour

def get_fbs_teams_cached():
    """Get FBS teams list (cached)"""
    global _fbs_teams_cache
    
    current_time = time.time()
    if _fbs_teams_cache['value'] is not None and (current_time - _fbs_teams_cache['timestamp']) < _fbs_teams_cache_ttl:
        return _fbs_teams_cache['value']
    
    teams_api = cfbd.TeamsApi(api_client)
    fbs_teams = teams_api.get_fbs_teams()
    _fbs_teams_cache = {'value': fbs_teams, 'timestamp': current_time}
    return fbs_teams

def get_team_records():
    """Get current team records from CFBD - FBS teams only"""
    try:
        # Get FBS teams first to establish the list of teams we care about (cached)
        fbs_teams = get_fbs_teams_cached()
        
        # Build mapping of team names to conferences (FBS only)
        fbs_team_set = set()
        team_conferences = {}
        cfbd_to_display = {}  # Map CFBD names to display names
        for team in fbs_teams:
            team_name = getattr(team, 'school', None) or getattr(team, 'team', None)
            conference = getattr(team, 'conference', None) or 'Independents'
            if team_name:
                fbs_team_set.add(team_name)
                team_conferences[team_name] = conference
                # Normalize team name for display
                display_name = TEAM_NAME_MAPPING.get(team_name, team_name)
                cfbd_to_display[team_name] = display_name
        
        # RecordsApi is not available in this version of cfbd
        # Skip directly to calculating records from games
        
        # Fallback: Calculate records from completed games
        # Load cached historical games and fetch only recent games
        games_api = cfbd.GamesApi(api_client)
        current_week = get_current_week()
        all_games = []
        
        # Load cached historical games (Aug 2025 - Nov 24, 2025)
        cache_file = os.path.join(NEEDLE_DIR, 'games_cache.pkl')
        cached_games = {}
        if os.path.exists(cache_file):
            try:
                with open(cache_file, 'rb') as f:
                    # Lock for reading to prevent reading while writing
                    fcntl.flock(f.fileno(), fcntl.LOCK_SH)  # Shared lock for reading
                    try:
                        cached_games = pickle.load(f)
                    finally:
                        fcntl.flock(f.fileno(), fcntl.LOCK_UN)
            except (pickle.UnpicklingError, EOFError, IOError) as e:
                print(f"Error loading cache (corrupted?): {e}")
                # Try to backup corrupted cache before proceeding
                backup_file = cache_file + '.corrupted'
                try:
                    shutil.copy2(cache_file, backup_file)
                    print(f"Backed up corrupted cache to {backup_file}")
                except:
                    pass
                cached_games = {}  # Start fresh if cache is corrupted
            except Exception as e:
                print(f"Error loading cache: {e}")
                cached_games = {}
        
        # Add cached games to all_games
        for week, games in cached_games.items():
            all_games.extend(games)
        
        # Fetch only recent weeks (last 3 weeks) for fresh data
        # Older weeks' games are already completed and cached
        start_week = max(1, current_week - 2)  # Last 3 weeks
        recent_games_by_week = {}
        # Track which weeks we're fetching fresh to avoid duplicates
        weeks_to_fetch = set(range(start_week, min(16, current_week + 2)))
        
        for week in weeks_to_fetch:
            try:
                # Fetch FBS games (includes FBS vs FCS)
                week_games = games_api.get_games(year=CURRENT_YEAR, week=week, season_type='regular', classification='fbs')
                if week_games:
                    # Only add games that aren't already in cache (avoid duplicates)
                    # Games are identified by their attributes, so we'll deduplicate later using processed_games
                    all_games.extend(week_games)
                    recent_games_by_week[week] = week_games
            except Exception as e:
                print(f"Error fetching week {week} games: {e}")
                continue
        
        # Cache is read-only during runtime to prevent corruption
        # Only update cache via populate_cache.py script
        # This ensures the cache file remains stable and doesn't get corrupted by concurrent writes
        
        games = all_games
        
        # Initialize all FBS teams with 0-0 records (using display names)
        team_stats = {}
        for team_name in fbs_team_set:
            display_name = cfbd_to_display.get(team_name, team_name)
            team_stats[display_name] = {
                'wins': 0, 
                'losses': 0, 
                'conference': team_conferences.get(team_name, 'Unknown')
            }
        
        # Calculate records from completed games
        # Include FBS vs FBS games and FBS vs FCS games (teams can count 1 FCS win)
        # Track which games we've processed to avoid double counting
        # Use a dict to store the best version of each game (prefer completed with points)
        game_dict = {}
        for game in games:
            home_team = getattr(game, 'home_team', None)
            away_team = getattr(game, 'away_team', None)
            game_key = f"{home_team}_{away_team}_{getattr(game, 'week', 0)}"
            
            # Prefer games with points (completed) over games without
            if game_key not in game_dict:
                game_dict[game_key] = game
            else:
                # If current game has points and stored one doesn't, replace it
                current_points = getattr(game, 'home_points', None) is not None and getattr(game, 'away_points', None) is not None
                stored_points = getattr(game_dict[game_key], 'home_points', None) is not None and getattr(game_dict[game_key], 'away_points', None) is not None
                if current_points and not stored_points:
                    game_dict[game_key] = game
        
        processed_games = set()
        for game in game_dict.values():
            # Check if game is completed
            completed = getattr(game, 'completed', False)
            home_points = getattr(game, 'home_points', None)
            away_points = getattr(game, 'away_points', None)
            
            # Game is completed if it has points or marked as completed
            if completed or (home_points is not None and away_points is not None):
                home_team = getattr(game, 'home_team', None)
                away_team = getattr(game, 'away_team', None)
                game_id = getattr(game, 'id', None)
                
                # Create unique game identifier
                game_key = f"{home_team}_{away_team}_{getattr(game, 'week', 0)}"
                
                # Process FBS vs FBS games
                if home_team and away_team and home_team in fbs_team_set and away_team in fbs_team_set and game_key not in processed_games:
                    processed_games.add(game_key)
                    home_display = cfbd_to_display.get(home_team, home_team)
                    away_display = cfbd_to_display.get(away_team, away_team)
                    
                    # Ensure teams exist in stats
                    if home_display not in team_stats:
                        team_stats[home_display] = {
                            'wins': 0, 
                            'losses': 0, 
                            'conference': team_conferences.get(home_team, 'Unknown')
                        }
                    if away_display not in team_stats:
                        team_stats[away_display] = {
                            'wins': 0, 
                            'losses': 0, 
                            'conference': team_conferences.get(away_team, 'Unknown')
                        }
                    
                    if home_points > away_points:
                        team_stats[home_display]['wins'] += 1
                        team_stats[away_display]['losses'] += 1
                    elif away_points > home_points:
                        team_stats[away_display]['wins'] += 1
                        team_stats[home_display]['losses'] += 1
                    # Tie games don't count as wins or losses for bowl eligibility
                
                # Process FBS vs FCS games (only count wins for FBS team)
                elif home_team and away_team and game_key not in processed_games:
                    home_is_fbs = home_team in fbs_team_set
                    away_is_fbs = away_team in fbs_team_set
                    
                    # Only process if exactly one team is FBS (FBS vs FCS)
                    if (home_is_fbs and not away_is_fbs) or (away_is_fbs and not home_is_fbs):
                        processed_games.add(game_key)
                        
                        # FBS home team vs FCS away team
                        if home_is_fbs and not away_is_fbs:
                            home_display = cfbd_to_display.get(home_team, home_team)
                            if home_display not in team_stats:
                                team_stats[home_display] = {
                                    'wins': 0, 
                                    'losses': 0, 
                                    'conference': team_conferences.get(home_team, 'Unknown')
                                }
                            
                            # Count wins and losses (FCS losses DO count for record purposes)
                            if home_points > away_points:
                                team_stats[home_display]['wins'] += 1
                            elif away_points > home_points:
                                team_stats[home_display]['losses'] += 1
                        
                        # FCS home team vs FBS away team
                        elif away_is_fbs and not home_is_fbs:
                            away_display = cfbd_to_display.get(away_team, away_team)
                            if away_display not in team_stats:
                                team_stats[away_display] = {
                                    'wins': 0, 
                                    'losses': 0, 
                                    'conference': team_conferences.get(away_team, 'Unknown')
                                }
                            
                            # Count wins and losses (FCS losses DO count for record purposes)
                            if away_points > home_points:
                                team_stats[away_display]['wins'] += 1
                            elif home_points > away_points:
                                team_stats[away_display]['losses'] += 1
        
        return team_stats
        
    except Exception as e:
        print(f"Error fetching team records: {e}")
        import traceback
        traceback.print_exc()
        return {}


# Cache for upcoming games (3 minute TTL)
_upcoming_games_cache = None
_upcoming_games_cache_time = 0
UPCOMING_GAMES_CACHE_TTL = 180  # 3 minutes

def get_upcoming_games():
    """Get upcoming games with win probabilities for week 14+ - FBS only, cached for 3 minutes
    Uses lightweight get_games for non-live games, and get_scoreboard for live games"""
    global _upcoming_games_cache, _upcoming_games_cache_time
    
    # Check cache
    current_time = time.time()
    if _upcoming_games_cache is not None and (current_time - _upcoming_games_cache_time) < UPCOMING_GAMES_CACHE_TTL:
        return _upcoming_games_cache
    
    games_api = cfbd.GamesApi(api_client)
    current_week = get_current_week()
    
    # Get FBS teams set for filtering and name mapping (cached)
    fbs_teams = get_fbs_teams_cached()
    fbs_team_set = set()
    cfbd_to_display = {}
    for team in fbs_teams:
        team_name = getattr(team, 'school', None) or getattr(team, 'team', None)
        if team_name:
            fbs_team_set.add(team_name)
            display_name = TEAM_NAME_MAPPING.get(team_name, team_name)
            cfbd_to_display[team_name] = display_name
    
    # Build mapping from CSV for scoreboard team name matching
    # CSV has flair_name (e.g., "Kansas Jayhawks") which matches scoreboard team names
    # CSV also has flair_abbreviated (e.g., "Kansas") which we can use to find the display name
    scoreboard_to_display = {}
    try:
        import csv
        csv_path = os.path.join(NEEDLE_DIR, 'cfb_mastersheet.csv')
        with open(csv_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            for row in reader:
                if row.get('family_name') == 'FBS':
                    flair_name = row.get('flair_name', '').strip()
                    abbrev = row.get('flair_abbreviated', '').strip()
                    
                    if flair_name:
                        display_name = None
                        
                        # First try direct match in cfbd_to_display
                        if flair_name in cfbd_to_display:
                            display_name = cfbd_to_display[flair_name]
                        # If we have an abbreviation, find the CFBD name that maps to it
                        # (e.g., "ECU" abbrev should map to display name "ECU" via "East Carolina" -> "ECU")
                        elif abbrev:
                            # Look for a CFBD name whose display name matches the abbrev
                            for cfbd_name, disp_name in cfbd_to_display.items():
                                if disp_name == abbrev:
                                    display_name = disp_name
                                    break
                            # If abbrev itself is a valid display name (already in values), use it
                            if not display_name and abbrev in cfbd_to_display.values():
                                display_name = abbrev
                        
                        # If still not found, try extracting school name (remove mascot)
                        # This handles cases like "East Carolina Pirates" -> "East Carolina" -> "ECU"
                        # or "ECU Pirates" -> "ECU" -> "ECU"
                        if not display_name:
                            parts = flair_name.split()
                            if len(parts) >= 2:
                                school_name = ' '.join(parts[:-1])  # Everything except last word (mascot)
                                # Direct match in cfbd_to_display (e.g., "East Carolina" -> "ECU")
                                if school_name in cfbd_to_display:
                                    display_name = cfbd_to_display[school_name]
                                # If school_name is already a display name (e.g., "ECU" from "ECU Pirates")
                                elif school_name in cfbd_to_display.values():
                                    display_name = school_name
                                # Try to find CFBD name that matches school_name exactly (no partial matching)
                                else:
                                    for cfbd_name, disp_name in cfbd_to_display.items():
                                        if cfbd_name == school_name:
                                            display_name = disp_name
                                            break
                        
                        if display_name:
                            scoreboard_to_display[flair_name] = display_name
    except Exception as e:
        print(f"Warning: Could not build scoreboard name mapping: {e}")
        import traceback
        traceback.print_exc()
    
    # Fetch scoreboard data for live games (only if there might be live games)
    # This is more expensive, so we only do it if needed
    scoreboard_data = {}
    scoreboard_by_teams = {}  # Also index by team names for fallback matching
    try:
        # Scoreboard endpoint returns all live games across all classifications
        scoreboard_games = games_api.get_scoreboard(classification='fbs')
        print(f"DEBUG: Fetched {len(scoreboard_games)} games from scoreboard")
        for live_game in scoreboard_games:
            game_id = getattr(live_game, 'id', None)
            if game_id:
                # Extract live data from scoreboard
                # CFBD Python library converts camelCase to snake_case
                home_team_obj = getattr(live_game, 'home_team', None)
                away_team_obj = getattr(live_game, 'away_team', None)
                if home_team_obj and away_team_obj:
                    home_name = getattr(home_team_obj, 'name', None)
                    away_name = getattr(away_team_obj, 'name', None)
                    home_points = getattr(home_team_obj, 'points', None)
                    away_points = getattr(away_team_obj, 'points', None)
                    clock = getattr(live_game, 'clock', None)
                    period = getattr(live_game, 'period', None)
                    
                    # Extract win probabilities from nested team objects
                    # winProbability is nested in homeTeam/awayTeam objects
                    # The CFBD Python library model might not expose win_probability as an attribute,
                    # so we need to access it via to_dict() or check the underlying data
                    home_win_prob = None
                    
                    # Try direct attribute access first
                    if hasattr(home_team_obj, 'win_probability'):
                        val = getattr(home_team_obj, 'win_probability', None)
                        if val is not None:
                            home_win_prob = val
                    if home_win_prob is None and hasattr(home_team_obj, 'winProbability'):
                        val = getattr(home_team_obj, 'winProbability', None)
                        if val is not None:
                            home_win_prob = val
                    
                    # If not found, try accessing via to_dict() method
                    if home_win_prob is None and hasattr(home_team_obj, 'to_dict'):
                        try:
                            team_dict = home_team_obj.to_dict()
                            home_win_prob = team_dict.get('win_probability') or team_dict.get('winProbability')
                        except:
                            pass
                    
                    # If still not found, try accessing via dict() or __dict__
                    if home_win_prob is None:
                        try:
                            if hasattr(home_team_obj, '__dict__'):
                                home_win_prob = home_team_obj.__dict__.get('win_probability') or home_team_obj.__dict__.get('winProbability')
                        except:
                            pass
                    
                    # Debug logging for Kansas/Utah game
                    if game_id == 401756964 or ('Kansas' in str(home_name) and 'Utah' in str(away_name)):
                        print(f"DEBUG: Extracting win_prob for game {game_id} ({away_name} @ {home_name}):")
                        if hasattr(home_team_obj, 'to_dict'):
                            try:
                                team_dict = home_team_obj.to_dict()
                                print(f"  home_team_obj.to_dict() keys: {list(team_dict.keys())}")
                                print(f"  win_probability in dict: {'win_probability' in team_dict}")
                                print(f"  winProbability in dict: {'winProbability' in team_dict}")
                                if 'win_probability' in team_dict:
                                    print(f"  win_probability value: {team_dict['win_probability']}")
                                if 'winProbability' in team_dict:
                                    print(f"  winProbability value: {team_dict['winProbability']}")
                            except Exception as e:
                                print(f"  Error calling to_dict(): {e}")
                        print(f"  Final home_win_prob: {home_win_prob}")
                    
                    # Convert scoreboard team names to display names using CSV mapping
                    # Scoreboard uses flair_name (e.g., "Kansas Jayhawks"), CSV maps this to display name
                    home_display = scoreboard_to_display.get(home_name, cfbd_to_display.get(home_name, home_name))
                    away_display = scoreboard_to_display.get(away_name, cfbd_to_display.get(away_name, away_name))
                    
                    scoreboard_data[game_id] = {
                        'home_points': home_points,
                        'away_points': away_points,
                        'clock': clock[1:] if clock and clock.startswith('0') else clock,  # Remove leading 0
                        'period': period,
                        'completed': getattr(live_game, 'completed', False),
                        'home_win_prob': home_win_prob,  # Live win probability if available (None for scheduled games)
                        'home_team': home_display,  # Store display names for matching
                        'away_team': away_display
                    }
                    
                    # Also index by team names for fallback matching (use both full and display names)
                    if home_name and away_name:
                        team_key_full = f"{home_name}|{away_name}"
                        team_key_display = f"{home_display}|{away_display}"
                        scoreboard_by_teams[team_key_full] = game_id
                        scoreboard_by_teams[team_key_display] = game_id
                    
                    # Debug logging for Kansas/Utah game
                    if game_id == 401756964 or ('Kansas' in str(home_name) and 'Utah' in str(away_name)):
                        print(f"DEBUG: Stored scoreboard data for game {game_id} ({away_display} @ {home_display}): home_win_prob={home_win_prob}, clock={clock}, period={period}, home_name={home_name}->{home_display}")
    except Exception as e:
        # If scoreboard fails, continue with regular games endpoint
        print(f"Warning: Could not fetch scoreboard data: {e}")
        import traceback
        traceback.print_exc()
    
    result = []
    
    # Only fetch games from week 14 onwards (Nov 26 or later)
    # This improves performance by avoiding early season games
    start_week = max(14, current_week - 1)  # Week 14+ or current week - 1, whichever is later
    for week in range(start_week, 16):  # Weeks 14-15
        try:
            # Use lightweight games endpoint
            games = games_api.get_games(year=CURRENT_YEAR, week=week, season_type='regular', classification='fbs')
            for game in games:
                game_id = getattr(game, 'id', None)
                home_team = getattr(game, 'home_team', None)
                away_team = getattr(game, 'away_team', None)
                # Include FBS vs FBS games and FBS vs FCS games
                home_is_fbs = home_team in fbs_team_set if home_team else False
                away_is_fbs = away_team in fbs_team_set if away_team else False
                
                # Check if we have live data from scoreboard for this game
                # Try by game_id first, then by team names as fallback
                live_data = scoreboard_data.get(game_id) if game_id else None
                
                # Debug logging for Kansas/Utah game
                if game_id == 401756964 or ('Kansas' in str(home_team) and 'Utah' in str(away_team)):
                    print(f"DEBUG: Looking for live data: game_id={game_id}, home={home_team}, away={away_team}")
                    print(f"DEBUG: Scoreboard has {len(scoreboard_data)} games")
                    if game_id and game_id in scoreboard_data:
                        print(f"DEBUG: Found by game_id: {scoreboard_data[game_id]}")
                    else:
                        print(f"DEBUG: Not found by game_id, searching by team names...")
                
                if not live_data and home_team and away_team:
                    # Try matching by team names (in case game_id differs or team names don't match exactly)
                    # Scoreboard data now uses display names, so should match directly
                    for sb_game_id, sb_data in scoreboard_data.items():
                        sb_home = sb_data.get('home_team', '')
                        sb_away = sb_data.get('away_team', '')
                        # Check if team names match exactly (both use display names now)
                        if home_team == sb_home and away_team == sb_away:
                            live_data = sb_data
                            if game_id == 401756964 or ('Kansas' in str(home_team) and 'Utah' in str(away_team)):
                                print(f"DEBUG: Matched game by team names: game_id={game_id}, scoreboard_id={sb_game_id}, home={home_team}=={sb_home}, away={away_team}=={sb_away}")
                            break
                        # Also try reversed (home/away swapped)
                        elif home_team == sb_away and away_team == sb_home:
                            live_data = sb_data
                            if game_id == 401756964 or ('Kansas' in str(home_team) and 'Utah' in str(away_team)):
                                print(f"DEBUG: Matched game by team names (reversed): game_id={game_id}, scoreboard_id={sb_game_id}")
                            break
                
                # Use scoreboard data if available (more accurate for live games), otherwise use games data
                if live_data:
                    home_points = live_data.get('home_points')
                    away_points = live_data.get('away_points')
                    clock = live_data.get('clock')
                    period = live_data.get('period')
                    api_completed = live_data.get('completed', False)
                else:
                    # Fall back to games endpoint data
                    home_points = getattr(game, 'home_points', None)
                    away_points = getattr(game, 'away_points', None)
                    clock = getattr(game, 'clock', None)
                    period = getattr(game, 'period', None)
                    api_completed = getattr(game, 'completed', False)
                
                # Determine if game is completed
                # Game is completed if:
                # 1. API explicitly marks it as completed, OR
                # 2. Game has final scores (both points) AND no live info (no clock/period)
                has_live_info = clock is not None or period is not None
                has_final_scores = home_points is not None and away_points is not None
                
                # Game is completed if API says so, OR if it has final scores but no live info
                completed = api_completed or (has_final_scores and not has_live_info)
                
                # FBS vs FBS games
                if home_team and away_team and home_is_fbs and away_is_fbs:
                    # For completed games, use actual result (1.0 for win, 0.0 for loss)
                    # For in-progress/scheduled games, use win probability
                    home_prob = None
                    
                    if completed and home_points is not None and away_points is not None:
                        # Game is completed - use actual result
                        if home_points > away_points:
                            home_prob = 1.0  # Home team won
                        elif away_points > home_points:
                            home_prob = 0.0  # Home team lost
                        else:
                            home_prob = 0.5  # Tie (shouldn't happen in CFB, but handle it)
                    else:
                        # Game is not completed - use win probability
                        # Priority 1: If scoreboard has win probability, use it (for live games)
                        if live_data is not None:
                            scoreboard_win_prob = live_data.get('home_win_prob')
                            if scoreboard_win_prob is not None:
                                # Use live win probability from scoreboard
                                home_prob = scoreboard_win_prob
                                # Debug logging for Kansas/Utah game
                                if game_id == 401756964 or ('Kansas' in str(home_team) and 'Utah' in str(away_team)):
                                    print(f"DEBUG: Game {game_id} ({away_team} @ {home_team}): Using scoreboard win_prob={scoreboard_win_prob}")
                        
                        # Priority 2: If no scoreboard win prob, use games endpoint
                        if home_prob is None:
                            if hasattr(game, 'home_win_probability') and game.home_win_probability is not None:
                                home_prob = game.home_win_probability
                                # Debug logging
                                if game_id == 401756964 or ('Kansas' in str(home_team) and 'Utah' in str(away_team)):
                                    print(f"DEBUG: Game {game_id} ({away_team} @ {home_team}): Using games endpoint win_prob={home_prob}, live_data={live_data is not None}, scoreboard_win_prob={live_data.get('home_win_prob') if live_data else None}")
                        
                        # Priority 3: Fall back to ELO-based probability if available
                        if home_prob is None:
                            if hasattr(game, 'home_pregame_elo') and hasattr(game, 'away_pregame_elo'):
                                home_elo = game.home_pregame_elo or 1500
                                away_elo = game.away_pregame_elo or 1500
                                elo_diff = home_elo - away_elo
                                home_prob = 1 / (1 + 10 ** (-elo_diff / 400))
                        
                        # Default to 0.5 if no probability available
                        if home_prob is None:
                            home_prob = 0.5
                    
                    # Use display names
                    home_display = cfbd_to_display.get(home_team, home_team)
                    away_display = cfbd_to_display.get(away_team, away_team)
                    
                    game_data = {
                        'home_team': home_display,
                        'away_team': away_display,
                        'home_win_prob': home_prob,
                        'away_win_prob': 1 - home_prob,
                        'completed': completed,
                        'week': week
                    }
                    
                    # Add score and status info if available
                    if home_points is not None:
                        game_data['home_points'] = home_points
                    if away_points is not None:
                        game_data['away_points'] = away_points
                    if clock is not None:
                        game_data['clock'] = clock
                    if period is not None:
                        game_data['period'] = period
                    
                    result.append(game_data)
                
                # FBS vs FCS games (only include if not completed, as completed ones are handled in get_team_records)
                elif not completed:
                    if home_is_fbs and not away_is_fbs:
                        # FBS home vs FCS away - FBS heavily favored
                        home_display = cfbd_to_display.get(home_team, home_team)
                        result.append({
                            'home_team': home_display,
                            'away_team': None,  # FCS team, not tracked
                            'home_win_prob': 0.95,  # FBS heavily favored vs FCS
                            'away_win_prob': 0.05,
                            'completed': False,
                            'week': week
                        })
                    elif away_is_fbs and not home_is_fbs:
                        # FCS home vs FBS away - FBS heavily favored
                        away_display = cfbd_to_display.get(away_team, away_team)
                        result.append({
                            'home_team': None,  # FCS team, not tracked
                            'away_team': away_display,
                            'home_win_prob': 0.05,  # FCS home, so FBS (away) win prob is low
                            'away_win_prob': 0.95,  # FBS away team heavily favored
                            'completed': False,
                            'week': week
                        })
        except Exception as e:
            # Continue if a week doesn't exist yet
            continue
    
    # Cache the result for 3 minutes
    _upcoming_games_cache = result
    _upcoming_games_cache_time = current_time
    
    return result


def calculate_expected_eligibility(team_records, upcoming_games, apr_rankings):
    """
    Calculate expected number of bowl-eligible teams based on win probabilities
    Returns: (expected_eligible, expected_wins, cutoff_info, std_dev, currently_eligible)
    """
    import math
    
    # Track expected wins for each team (start with current wins)
    expected_wins = {team: float(data['wins']) for team, data in team_records.items()}
    expected_losses = {team: float(data['losses']) for team, data in team_records.items()}
    
    # Track probability of each team being eligible (for variance calculation)
    team_eligibility_probs = {}
    
    # Count currently eligible teams (6+ wins, non-transitional)
    currently_eligible = sum(1 for team, data in team_records.items() 
                           if data['wins'] >= 6 and team not in TRANSITIONAL_TEAMS)
    
    # Process upcoming games and add expected wins/losses
    for game in upcoming_games:
        # Skip completed games (they're already in the records)
        if game.get('completed', False):
            continue
            
        home_team = game.get('home_team')
        away_team = game.get('away_team')
        home_prob = game.get('home_win_prob', 0.5)
        away_prob = game.get('away_win_prob', 1 - home_prob)
        
        # FBS vs FBS games
        if home_team and away_team:
            # Add expected wins/losses for upcoming games
            if home_team in expected_wins:
                expected_wins[home_team] += home_prob
                expected_losses[home_team] += away_prob
            if away_team in expected_wins:
                expected_wins[away_team] += away_prob
                expected_losses[away_team] += home_prob
        # FBS vs FCS games (only count wins for FBS team, losses don't count)
        elif home_team and not away_team:
            # FBS home vs FCS away
            if home_team in expected_wins:
                expected_wins[home_team] += home_prob  # Only count wins
        elif away_team and not home_team:
            # FCS home vs FBS away
            if away_team in expected_wins:
                expected_wins[away_team] += away_prob  # Only count wins
    
    # Detect guaranteed eligibility games (5-6 vs 5-6)
    # These games guarantee exactly 1 eligible team, so we need to handle them specially
    guaranteed_game_teams = set()  # Teams in guaranteed games (we'll count them as 0.5 each = 1.0 total)
    for game in upcoming_games:
        if game.get('completed', False):
            continue
        home_team = game.get('home_team')
        away_team = game.get('away_team')
        if home_team and away_team:
            home_record = team_records.get(home_team, {})
            away_record = team_records.get(away_team, {})
            home_wins = home_record.get('wins', 0)
            home_losses = home_record.get('losses', 0)
            away_wins = away_record.get('wins', 0)
            away_losses = away_record.get('losses', 0)
            # Check if this is a 5-6 vs 5-6 game (guaranteed to produce 1 eligible team)
            if (home_wins == 5 and home_losses == 6 and 
                away_wins == 5 and away_losses == 6 and
                home_team not in TRANSITIONAL_TEAMS and away_team not in TRANSITIONAL_TEAMS):
                # This game guarantees exactly 1 eligible team
                # We'll count each team as 0.5 probability (sums to 1.0)
                guaranteed_game_teams.add(home_team)
                guaranteed_game_teams.add(away_team)
    
    # Calculate probability each team will be eligible (simplified: use expected wins as proxy)
    # For variance: treat each team as Bernoulli with p = probability of being eligible
    variance_sum = 0.0
    for team, wins in expected_wins.items():
        losses = expected_losses.get(team, team_records[team]['losses'])
        est_wins = wins
        current_wins = team_records[team]['wins']
        current_losses = team_records[team]['losses']
        
        # Estimate probability of being eligible (6+ wins, non-transitional)
        # Transitional teams don't count in main eligible - they're handled separately
        if team in TRANSITIONAL_TEAMS:
            prob_eligible = 0.0  # Don't count transitional teams in main expected eligible
        elif current_wins >= 6:
            # Already eligible = certainty (1.0)
            prob_eligible = 1.0
        elif team in guaranteed_game_teams:
            # Teams in guaranteed eligibility games (5-6 vs 5-6) contribute 0.5 each
            # This ensures the pair contributes exactly 1.0 total
            prob_eligible = 0.5
        elif current_wins == 5:
            # 5-win teams: probability = probability of winning at least one remaining game
            # Find remaining games for this team
            remaining_games = [g for g in upcoming_games 
                             if not g.get('completed', False) and
                             (g.get('home_team') == team or g.get('away_team') == team)]
            
            if remaining_games:
                # Probability of winning at least 1 = 1 - probability of losing all
                prob_lose_all = 1.0
                for game in remaining_games:
                    if game.get('home_team') == team:
                        prob_lose_all *= (1 - game.get('home_win_prob', 0.5))
                    elif game.get('away_team') == team:
                        prob_lose_all *= (1 - game.get('away_win_prob', 0.5))
                prob_eligible = 1 - prob_lose_all
            else:
                # No remaining games, can't reach 6 wins
                prob_eligible = 0.0
        else:
            # Check if team can mathematically reach 6 wins
            remaining_games = [g for g in upcoming_games 
                             if not g.get('completed', False) and
                             (g.get('home_team') == team or g.get('away_team') == team)]
            max_possible_wins = current_wins + len(remaining_games)
            
            if max_possible_wins < 6:
                # Cannot reach 6 wins - probability is 0.0
                prob_eligible = 0.0
            elif est_wins >= 6.5:
                # Very likely eligible
                prob_eligible = 0.98
            elif est_wins <= 5.5:
                # Very unlikely, but can reach 6 wins
                prob_eligible = 0.02
            else:
                # Linear interpolation between 5.5 and 6.5
                prob_eligible = 0.02 + (est_wins - 5.5) * 0.96
        
        team_eligibility_probs[team] = prob_eligible
        # Variance of Bernoulli: p(1-p)
        # Only include variance for teams that can affect eligibility (non-transitional)
        # Teams with probability 1.0 have 0 variance (certainty)
        # Teams in guaranteed games have variance 0.5 * 0.5 = 0.25 each, but together = 0 (certainty)
        if team not in TRANSITIONAL_TEAMS and prob_eligible < 1.0:
            if team in guaranteed_game_teams:
                # For guaranteed games, variance is 0.25 per team, but we need to account for correlation
                # Since one team must win, the variance of the sum is 0
                # So we don't add variance for guaranteed game teams
                pass
            else:
                variance_sum += prob_eligible * (1 - prob_eligible)
    
    # Standard deviation is sqrt of variance
    std_dev = math.sqrt(variance_sum)
    
    # Calculate expected eligible as sum of probabilities (not count)
    expected_eligible_sum = sum(team_eligibility_probs.values())
    
    # Calculate minimum and maximum eligible teams
    # Minimum = currently eligible + guaranteed eligibility games (each 5-6 vs 5-6 pair adds 1)
    currently_eligible_count = sum(1 for team, record in team_records.items() 
                                  if record['wins'] >= 6 and team not in TRANSITIONAL_TEAMS)
    
    # Count guaranteed eligibility games (5-6 vs 5-6 pairs)
    guaranteed_game_pairs = len(guaranteed_game_teams) // 2  # Each pair has 2 teams
    min_eligible = currently_eligible_count + guaranteed_game_pairs
    
    # Maximum = currently eligible + guaranteed games + all other 5-win teams that can reach 6 wins
    # Count 5-win teams that can reach 6 wins (excluding those in guaranteed games)
    other_5win_teams = 0
    for team, record in team_records.items():
        if team in TRANSITIONAL_TEAMS:
            continue
        wins = record['wins']
        losses = record['losses']
        if wins == 5 and team not in guaranteed_game_teams:
            # Check if they have remaining games
            remaining_games = [g for g in upcoming_games 
                             if not g.get('completed', False) and
                             (g.get('home_team') == team or g.get('away_team') == team)]
            if remaining_games:
                # Can reach 6 wins
                other_5win_teams += 1
    
    max_eligible = currently_eligible_count + guaranteed_game_pairs + other_5win_teams
    
    # Count expected eligible teams (6+ wins) for cutoff determination
    six_win_teams = []
    transitional_eligible = []
    five_seven_candidates = []
    
    for team, wins in expected_wins.items():
        losses = expected_losses.get(team, team_records[team]['losses'])
        
        # Use expected values (don't round yet, use decimal values)
        est_wins = wins
        est_losses = losses
        
        if est_wins >= 6:
            six_win_teams.append(team)
        elif team in TRANSITIONAL_TEAMS and est_wins >= 5:
            transitional_eligible.append(team)
        elif est_wins >= 5 and est_losses <= 7:
            # Could be 5-7 APR candidate
            if round(est_wins) == 5 and round(est_losses) == 7:
                five_seven_candidates.append(team)
    
    # Build list in priority order
    eligible_teams = six_win_teams.copy()
    
    # Add transitional teams if needed (only if not enough 6-win teams)
    if len(eligible_teams) < TOTAL_BOWL_SPOTS:
        for team in transitional_eligible:
            eligible_teams.append(team)
            if len(eligible_teams) >= TOTAL_BOWL_SPOTS:
                break
    
    # Add 5-7 teams by APR if still needed
    if len(eligible_teams) < TOTAL_BOWL_SPOTS:
        sorted_apr = sorted(five_seven_candidates, 
                           key=lambda t: apr_rankings.get(get_apr_key(t), apr_rankings.get(t, 0)), 
                           reverse=True)
        for team in sorted_apr:
            eligible_teams.append(team)
            if len(eligible_teams) >= TOTAL_BOWL_SPOTS:
                break
    
    # Determine which team is at the cutoff
    cutoff_info = None
    if len(eligible_teams) < TOTAL_BOWL_SPOTS:
        # Below 82 - show which team fills the gap
        if transitional_eligible and len(six_win_teams) < TOTAL_BOWL_SPOTS:
            cutoff_info = {'team': transitional_eligible[0], 'type': 'transitional'}
        elif five_seven_candidates:
            sorted_apr = sorted(five_seven_candidates, 
                               key=lambda t: apr_rankings.get(get_apr_key(t), apr_rankings.get(t, 0)), 
                               reverse=True)
            if sorted_apr:
                cutoff_info = {'team': sorted_apr[0], 'type': 'apr'}
    elif len(eligible_teams) > TOTAL_BOWL_SPOTS:
        # Above 82 - some teams won't bowl
        cutoff_info = {'team': eligible_teams[TOTAL_BOWL_SPOTS], 'type': 'excluded'}
    
    return expected_eligible_sum, expected_wins, cutoff_info, std_dev, currently_eligible, min_eligible, max_eligible


def get_team_status(team, wins, losses, expected_wins, upcoming_games, apr_rankings):
    """Determine current status of a team
    Returns: 'eligible', 'undecided_6win', 'undecided_57', or 'ineligible'
    """
    # Force ineligible teams (e.g., postseason bans) to always be red
    if team in INELIGIBLE_TEAMS:
        return 'ineligible'
    
    remaining_games = sum(1 for g in upcoming_games 
                         if not g.get('completed', False) and 
                         (g['home_team'] == team or g['away_team'] == team))
    
    # Red: Cannot get to 5-7 (8+ losses) - definitely ineligible
    if losses >= 8:
        return 'ineligible'
    
    # Green: 6+ wins (except transitional teams - they're always pinch)
    if wins >= 6:
        if team not in TRANSITIONAL_TEAMS:
            return 'eligible'
        # Transitional teams (Missouri State, Delaware) are always pinch if 5+ wins
        return 'undecided_57'
    
    # Pinch: Teams that can still reach 5-7 (APR eligible) or transitional teams
    # If a team has 7 losses and can still get to 5 wins, they're APR eligible
    if losses == 7:
        if wins == 5:
            return 'undecided_57'  # Already 5-7, APR candidate
        elif wins < 5 and remaining_games > 0:
            # Can they still reach 5 wins?
            max_possible_wins = wins + remaining_games
            if max_possible_wins >= 5:
                return 'undecided_57'  # Can reach 5-7, APR eligible
            else:
                return 'ineligible'  # Cannot reach 5 wins
        else:
            return 'ineligible'  # 7 losses, no remaining games, less than 5 wins
    
    # Transitional teams are always pinch if they can still get to 5 wins
    if team in TRANSITIONAL_TEAMS:
        if wins == 5:
            return 'undecided_57'
        elif wins < 5 and losses < 8:
            return 'undecided_57'  # Can still get to 5 wins
        else:
            return 'ineligible'
    
    # Teams at 5 wins with remaining games - check if they can reach 6 or 5-7
    # Priority: if they can reach 6 wins, they're question mark (except Delaware/Missouri State)
    if wins == 5 and remaining_games > 0:
        if team not in TRANSITIONAL_TEAMS:
            # Can they reach 6 wins? (need to win all remaining games)
            if wins + remaining_games >= 6:
                return 'undecided_6win'  # Can reach 6 wins - question mark
            # Otherwise check if they can finish 5-7
            max_possible_losses = losses + remaining_games
            if max_possible_losses <= 7:
                return 'undecided_57'  # Can finish 5-7 - pinch
        else:
            # Transitional teams at 5 wins are always pinch
            max_possible_losses = losses + remaining_games
            if max_possible_losses <= 7:
                return 'undecided_57'  # Can finish 5-7 - pinch
    
    # Army special case: 5-5, needs to win this week
    if team == ARMY_TEAM or team == 'Army West Point':
        if wins == 5 and losses == 5 and remaining_games > 0:
            return 'undecided_6win'  # Can reach 6 wins
        elif losses >= 8:
            return 'ineligible'
        elif remaining_games > 0:
            return 'undecided_6win'
    
    # Teams that can still mathematically reach 6 wins or 5-7
    if wins < 6 and remaining_games > 0:
        max_possible_wins = wins + remaining_games
        max_possible_losses = losses + remaining_games
        
        # Can reach 6 wins
        if max_possible_wins >= 6:
            return 'undecided_6win'
        # Can reach 5-7
        elif max_possible_wins == 5 and max_possible_losses <= 7:
            return 'undecided_57'
        # Cannot reach eligibility
        else:
            return 'ineligible'
    
    # Teams with no remaining games and not eligible
    if remaining_games == 0:
        if wins >= 6:
            # Transitional teams (Missouri State, Delaware) are always pinch, never eligible
            if team in TRANSITIONAL_TEAMS:
                return 'undecided_57'
            return 'eligible'
        elif wins == 5 and losses == 7:
            return 'undecided_57'  # 5-7 APR candidate
        else:
            return 'ineligible'
    
    return 'undecided_6win'  # Default to 6-win possibility


def generate_summary(expected_eligible, delaware_wins=None, delaware_losses=None, delaware_exp_wins=None):
    """
    Generate summary text based on expected eligible count and Delaware's status
    Returns: (summary_text, color)
    Note: expected_eligible is rounded to nearest whole number for summary text
    """
    # Round to nearest whole number for summary text
    rounded_expected = round(expected_eligible)
    
    if rounded_expected == 82:
        return "All eligible teams bowling", "#4caf50"  # green
    
    elif rounded_expected > 82:
        n = rounded_expected - 82
        return f"{n} bowl eligible team{'s' if n > 1 else ''} not bowling", "#f44336"  # red
    
    elif rounded_expected == 81:
        return "All eligible teams bowling + {MISSOURI_STATE}", "#4caf50"  # green
    
    elif rounded_expected == 80:
        # Delaware is confirmed 6-6, so always include them
        return "All eligible teams bowling + {MISSOURI_STATE} + {DELAWARE}", "#4caf50"  # green
    
    elif rounded_expected == 79:
        # Delaware is confirmed 6-6
        return "All eligible teams bowling + {MISSOURI_STATE} + {DELAWARE} + 1 APR team", "#4caf50"  # green
    
    elif rounded_expected < 79:
        n = 80 - rounded_expected
        apr_count = n + 1  # Missouri State + Delaware + n APR teams
        # Round APR counts to whole numbers
        apr_count_rounded = round(apr_count)
        apr_count_minus_one = round(apr_count - 1)
        # Delaware is confirmed 6-6, so always include them
        return f"All eligible teams bowling + {{MISSOURI_STATE}} + {{DELAWARE}} + {apr_count_minus_one}-{apr_count_rounded} APR team{'s' if apr_count_rounded > 1 else ''}", "#4caf50"  # green
    
    else:
        # Round to nearest whole number for display (1 sig fig for values >= 10)
        rounded_display = round(expected_eligible)
        return f"Expected {rounded_display} eligible teams", "#666"  # gray


@app.route('/api/status', methods=['GET'])
def get_status():
    """Main API endpoint returning current bowl eligibility status"""
    try:
        team_records = get_team_records()
        upcoming_games = get_upcoming_games()
        expected_eligible, expected_wins, cutoff_info, std_dev, currently_eligible, min_eligible, max_eligible = calculate_expected_eligibility(
            team_records, upcoming_games, APR_RANKINGS
        )
        
        # Get Delaware's record for summary (check both display name and CFBD name)
        delaware_record = team_records.get('Delaware', team_records.get('Delaware Blue Hens', {}))
        delaware_wins = delaware_record.get('wins', 0)
        delaware_losses = delaware_record.get('losses', 0)
        delaware_exp_wins = expected_wins.get('Delaware', expected_wins.get('Delaware Blue Hens', delaware_wins))
        
        # Generate summary text
        summary_text, summary_color = generate_summary(expected_eligible, delaware_wins, delaware_losses, delaware_exp_wins)
        
        # Get team statuses and build conference data
        teams_by_conference = {}
        # Build a lookup of upcoming games by team for probability calculations
        # Include all games (completed and non-completed) from week 14+ for display
        # But only non-completed games for probability calculations
        current_week = get_current_week()
        team_upcoming_games = {}
        for game in upcoming_games:
            home_team = game.get('home_team')
            away_team = game.get('away_team')
            game_week = game.get('week', current_week)
            is_completed = game.get('completed', False)
            
            # For probability calculations, skip completed games
            # But for display, include all games from week 14 (or current week)
            should_include_for_display = (game_week == 14 or game_week == current_week)
            should_include_for_prob = not is_completed
            
            # Always include for display if it's week 14/current week
            # Include for prob if not completed
            if should_include_for_display or should_include_for_prob:
                if home_team:
                    if home_team not in team_upcoming_games:
                        team_upcoming_games[home_team] = []
                    team_upcoming_games[home_team].append(game)
                if away_team:
                    if away_team not in team_upcoming_games:
                        team_upcoming_games[away_team] = []
                    team_upcoming_games[away_team].append(game)
        
        for team, record in team_records.items():
            exp_wins = expected_wins.get(team, record['wins'])
            status = get_team_status(
                team, record['wins'], record['losses'], 
                exp_wins,
                upcoming_games, APR_RANKINGS
            )
            
            conference = record.get('conference', 'Unknown')
            
            if conference not in teams_by_conference:
                teams_by_conference[conference] = []
            
            teams_by_conference[conference].append({
                'team': team,
                'record': f"{record['wins']}-{record['losses']}",
                'status': status,
                'expected_wins': round(exp_wins, 2),
                'upcoming_games': team_upcoming_games.get(team, [])
            })
        
        # Sort teams within each conference by wins (descending), then by expected wins
        conference_totals = {}
        cumulative_predicted = 0
        
        for conference in teams_by_conference:
            # Sort by wins descending, then expected wins descending
            # Parse wins from record string (e.g., "6-5" -> 6)
            teams_by_conference[conference].sort(
                key=lambda x: (-int(x['record'].split('-')[0]), -x['expected_wins'])
            )
            
            # Count currently eligible teams in this conference (6+ wins, non-transitional)
            conf_eligible = sum(1 for t in teams_by_conference[conference] 
                              if t['status'] == 'eligible')
            
            # Count predicted eligible teams using probability-based calculation
            # For each team, calculate probability they'll be eligible (6+ wins)
            conf_predicted = 0.0
            for t in teams_by_conference[conference]:
                team_name = t['team']
                if team_name in TRANSITIONAL_TEAMS:
                    # Transitional teams handled separately - don't count them here
                    continue
                
                exp_wins = t['expected_wins']
                record_parts = t['record'].split('-')
                current_wins = int(record_parts[0])
                current_losses = int(record_parts[1])
                
                # Check if team can mathematically reach 6 wins
                remaining_games = [g for g in t.get('upcoming_games', []) 
                                 if not g.get('completed', False)]
                max_possible_wins = current_wins + len(remaining_games)
                
                # If team cannot reach 6 wins, probability is 0.0
                if max_possible_wins < 6:
                    conf_predicted += 0.0
                # If already at 6+ wins, probability is 1.0
                elif current_wins >= 6:
                    conf_predicted += 1.0
                # If at 5 wins, calculate probability of winning at least 1 remaining game
                elif current_wins == 5:
                    if remaining_games:
                        # Calculate probability of winning at least 1 more game
                        # Probability of winning at least 1 = 1 - probability of losing all
                        prob_lose_all = 1.0
                        for game in remaining_games:
                            if game.get('home_team') == team_name:
                                prob_lose_all *= (1 - game.get('home_win_prob', 0.5))
                            elif game.get('away_team') == team_name:
                                prob_lose_all *= (1 - game.get('away_win_prob', 0.5))
                        prob_win_at_least_one = 1 - prob_lose_all
                        conf_predicted += prob_win_at_least_one
                    else:
                        # No remaining games, can't reach 6 wins
                        conf_predicted += 0.0
                # If less than 5 wins, use expected_wins to estimate probability
                # But only if they can actually reach 6 wins
                elif exp_wins >= 6.0:
                    conf_predicted += 1.0
                elif exp_wins >= 5.0:
                    # Between 5.0 and 6.0, interpolate probability
                    # This handles teams that might reach 6 wins
                    prob = (exp_wins - 5.0) / 1.0  # Maps 5.0->0, 6.0->1
                    conf_predicted += prob
                else:
                    # Less than 5.0 expected wins, very low probability
                    conf_predicted += 0.0
            
            # Calculate cumulative predicted across all conferences processed so far
            cumulative_predicted += conf_predicted
            conference_totals[conference] = {
                'eligible_count': conf_eligible,
                'predicted_count': conf_predicted,  # Just this conference's predicted count
                'cumulative_predicted': cumulative_predicted  # Cumulative across all conferences
            }
        
        # Find teams that could still finish 5-7 (APR candidates)
        apr_candidates = []
        for team, record in team_records.items():
            wins = record['wins']
            losses = record['losses']
            exp_wins = expected_wins.get(team, wins)
            exp_losses = 12 - exp_wins  # Approximate expected losses
            
            # Skip teams that are already eligible (6+ wins, non-transitional)
            if wins >= 6 and team not in TRANSITIONAL_TEAMS:
                continue
            
            # Skip teams that can't reach 5-7 (8+ losses)
            if losses >= 8:
                continue
            
            # Include teams that:
            # 1. Are already 5-7
            # 2. Could finish 5-7 (currently 4-7, 5-6, or could reach 5-7)
            remaining_games = sum(1 for g in upcoming_games 
                                 if not g.get('completed', False) and 
                                 (g.get('home_team') == team or g.get('away_team') == team))
            
            could_be_5_7 = False
            if wins == 5 and losses == 7:
                could_be_5_7 = True  # Already 5-7
            elif wins == 5 and losses == 6 and remaining_games > 0:
                could_be_5_7 = True  # Could finish 5-7 (need to lose remaining game)
            elif wins == 4 and losses == 7 and remaining_games > 0:
                could_be_5_7 = True  # Could finish 5-7 (need to win remaining game)
            elif wins == 5 and losses == 5 and remaining_games >= 2:
                # Could finish 5-7 if they lose 2 more
                could_be_5_7 = True
            elif wins == 4 and losses == 6 and remaining_games >= 1:
                # Could finish 5-7 if they win 1 and lose 1
                could_be_5_7 = True
            elif exp_wins >= 4.5 and exp_wins < 6.0 and exp_losses <= 7.5:
                # Could mathematically reach 5-7 based on expected record (between 4.5-5.9 wins, <=7.5 losses)
                could_be_5_7 = True
            
            if could_be_5_7:
                apr_score = APR_RANKINGS.get(get_apr_key(team), APR_RANKINGS.get(team, 0))
                apr_candidates.append({
                    'team': team,
                    'record': f"{wins}-{losses}",
                    'apr': apr_score
                })
        
        # Sort by APR descending
        apr_candidates.sort(key=lambda x: x['apr'], reverse=True)
        
        # Keep teams until we have at least 5 teams with 5+ wins
        # Parse wins from record string (format: "wins-losses")
        teams_with_5_plus_wins = 0
        filtered_apr_candidates = []
        
        for candidate in apr_candidates:
            # Parse wins from record (e.g., "5-7" -> wins = 5)
            wins = int(candidate['record'].split('-')[0])
            filtered_apr_candidates.append(candidate)
            
            if wins >= 5:
                teams_with_5_plus_wins += 1
                # Once we have 5+ teams with 5+ wins, we can stop
                if teams_with_5_plus_wins >= 7:
                    break
        
        apr_candidates = filtered_apr_candidates
        
        # Calculate expected APR spots available
        # This is the number of spots that would be filled by 5-7 teams
        # Subtract 2 for Missouri State and Delaware (they're guaranteed spots)
        # Expected spots = max(0, 82 - expected_eligible - 2)
        expected_apr_spots = max(0, TOTAL_BOWL_SPOTS - expected_eligible - 2)
        
        # Calculate Delaware's remaining games for frontend
        delaware_remaining = 0
        if 'Delaware' in team_records or 'Delaware Blue Hens' in team_records:
            delaware_team = 'Delaware' if 'Delaware' in team_records else 'Delaware Blue Hens'
            delaware_remaining = sum(1 for g in upcoming_games 
                                   if not g.get('completed', False) and 
                                   (g.get('home_team') == delaware_team or g.get('away_team') == delaware_team))
        
        return jsonify({
            'expected_eligible': expected_eligible,
            'currently_eligible': currently_eligible,
            'min_eligible': min_eligible,
            'max_eligible': max_eligible,
            'std_dev': std_dev,
            'total_spots': TOTAL_BOWL_SPOTS,
            'teams_by_conference': teams_by_conference,
            'conference_totals': conference_totals,
            'current_week': get_current_week(),
            'cutoff_info': cutoff_info,
            'summary_text': summary_text,
            'summary_color': summary_color,
            'apr_candidates': apr_candidates,
            'expected_apr_spots': expected_apr_spots,
            'delaware_wins': delaware_wins,
            'delaware_losses': delaware_losses,
            'delaware_remaining_games': delaware_remaining
        })
    except Exception as e:
        import traceback
        error_trace = traceback.format_exc()
        print(f"Error in /api/status: {e}")
        print(error_trace)
        return jsonify({'error': str(e), 'traceback': error_trace}), 500


@app.route('/', methods=['GET'])
def index():
    """Serve the main HTML page"""
    return app.send_static_file('index.html')


if __name__ == '__main__':
    app.run(debug=True, port=5001)

