#!/usr/bin/env python3
"""
Standalone Betting Refresh — Predictable Tempo Strategy
========================================================
Fetches live odds from The Odds API, applies Bill's full strategy
(tempo classification + odds-based intelligence + validation),
and writes picks directly to dashboard-data.json.

Independent of Luke. Can be run manually or on a schedule.

Usage:
  python3 betting_refresh.py              # uses .env for API key
  ODDS_API_KEY=xxx python3 betting_refresh.py  # explicit key
"""

import os
import sys
import json
import logging
from pathlib import Path
from datetime import datetime, timedelta, date

try:
    import requests
except ImportError:
    print("pip install requests")
    sys.exit(1)

# Load .env if present
try:
    from dotenv import load_dotenv
    load_dotenv(Path(__file__).resolve().parent / ".env")
except ImportError:
    pass

logging.basicConfig(level=logging.INFO, format="%(asctime)s [BETTING] %(message)s", datefmt="%H:%M:%S")
log = logging.getLogger("betting-refresh")

# ── Config ────────────────────────────────────────────────────
ODDS_API_KEY = os.getenv("ODDS_API_KEY", "")
ODDS_BASE = "https://api.the-odds-api.com/v4"
DASHBOARD_JSON = Path(__file__).resolve().parent / "dashboard-data.json"
LOGS_DIR = Path(__file__).resolve().parent / "logs"
LOGS_DIR.mkdir(exist_ok=True)

# ── Sport selection ───────────────────────────────────────────
# Free tier = 500 requests/month.  Budget: 5 sports × 2 runs/day × 30 days = 300.
# Only query the leagues that best fit Predictable Tempo strategy.
# Tier 1 (controlled/structured): NHL, EPL, Serie A, La Liga
# Tier 2 (UCL when in-season — knockout stages are low-scoring)
SPORT_KEYS = {
    "nhl":    "icehockey_nhl",       # Best tempo sport — controlled matchups
    "epl":    "soccer_epl",          # Tier 1 league — prime under territory
    "seriea": "soccer_italy_serie_a",# Tier 1 — defensive culture
    "laliga": "soccer_spain_la_liga",# Tier 1 — tactical, low-event
    "ucl":    "soccer_uefa_champs_league",  # Knockout stages = cagey
}

# ── NHL Team Tempo Profiles ──────────────────────────────────
NHL_TEAMS = {
    # LOW-EVENT — best for unders
    "Carolina Hurricanes": "low", "Minnesota Wild": "low", "Nashville Predators": "low",
    "Dallas Stars": "low", "Winnipeg Jets": "low", "New York Islanders": "low",
    "Columbus Blue Jackets": "low", "Los Angeles Kings": "low", "Boston Bruins": "low",
    "New Jersey Devils": "low", "Vegas Golden Knights": "low", "St Louis Blues": "low",
    "Seattle Kraken": "low", "Vancouver Canucks": "low", "Anaheim Ducks": "low",
    # HIGH-OFFENSE — avoid for unders
    "Toronto Maple Leafs": "high", "Edmonton Oilers": "high", "Colorado Avalanche": "high",
    "Florida Panthers": "high", "Tampa Bay Lightning": "high", "Ottawa Senators": "high",
    "Buffalo Sabres": "high", "Detroit Red Wings": "high", "Pittsburgh Penguins": "high",
    "New York Rangers": "high", "Washington Capitals": "high", "Calgary Flames": "high",
    "Philadelphia Flyers": "high", "Chicago Blackhawks": "high", "Montreal Canadiens": "high",
    "San Jose Sharks": "high", "Utah Hockey Club": "high",
}

# ── Soccer League Tiers ──────────────────────────────────────
SOCCER_TIERS = {
    "soccer_italy_serie_a":                  {"tier": 1, "label": "Serie A"},
    "soccer_france_ligue_one":               {"tier": 1, "label": "Ligue 1"},
    "soccer_portugal_primeira_liga":         {"tier": 1, "label": "Liga Portugal"},
    "soccer_spain_la_liga":                  {"tier": 2, "label": "La Liga"},
    "soccer_epl":                            {"tier": 2, "label": "EPL"},
    "soccer_usa_mls":                        {"tier": 2, "label": "MLS"},
    "soccer_germany_bundesliga":             {"tier": 3, "label": "Bundesliga"},
    "soccer_netherlands_eredivisie":         {"tier": 3, "label": "Eredivisie"},
    "soccer_uefa_champs_league":             {"tier": 1, "label": "Champions League", "is_cup": True},
    "soccer_uefa_europa_league":             {"tier": 1, "label": "Europa League", "is_cup": True},
    "soccer_uefa_europa_conference_league":  {"tier": 1, "label": "Conference League", "is_cup": True},
}


# ── Strategy Engine ──────────────────────────────────────────

def classify_nhl(home, away):
    h = NHL_TEAMS.get(home, "medium")
    a = NHL_TEAMS.get(away, "medium")
    if h == "low" and a == "low":
        return "controlled"
    if h == "high" and a == "high":
        return "chaotic"
    return "mixed"


def get_soccer_tier(sport_key):
    return SOCCER_TIERS.get(sport_key, {"tier": 2, "label": sport_key})


def extract_odds_intel(game):
    """Extract favorite, total line, spread from bookmaker data."""
    bms = game.get("bookmakers", [])
    preferred = ["bet365", "draftkings", "fanduel", "bovada", "betmgm"]
    book = None
    for bname in preferred:
        book = next((b for b in bms if b["key"] == bname), None)
        if book:
            break
    if not book and bms:
        book = bms[0]
    if not book:
        return {}

    intel = {"book": book.get("title", book.get("key", ""))}

    # H2H — favorite
    h2h = next((m for m in book.get("markets", []) if m["key"] == "h2h"), None)
    if h2h and h2h.get("outcomes"):
        fav = min(h2h["outcomes"], key=lambda o: o["price"])
        dog = max(h2h["outcomes"], key=lambda o: o["price"])
        intel["favorite"] = fav["name"]
        intel["fav_price"] = fav["price"]
        intel["dog_price"] = dog["price"]

    # Totals
    totals = next((m for m in book.get("markets", []) if m["key"] == "totals"), None)
    if totals and totals.get("outcomes"):
        over = next((o for o in totals["outcomes"] if o["name"] == "Over"), None)
        under = next((o for o in totals["outcomes"] if o["name"] == "Under"), None)
        if over:
            intel["total_line"] = over.get("point", 0)
            intel["over_price"] = over.get("price", 0)
        if under:
            intel["under_price"] = under.get("price", 0)

    # Spreads
    spreads = next((m for m in book.get("markets", []) if m["key"] == "spreads"), None)
    if spreads and spreads.get("outcomes"):
        fav_spread = next((o for o in spreads["outcomes"] if o.get("point", 0) < 0), None)
        if fav_spread:
            intel["spread"] = abs(fav_spread["point"])

    return intel


def build_pick(game, sport_label, sport_key):
    """Build a pick with legs, validation, and odds intelligence."""
    home = game["home_team"]
    away = game["away_team"]

    # Parse time to ET
    time_str = ""
    try:
        ct = datetime.fromisoformat(game["commence_time"].replace("Z", "+00:00"))
        eastern = ct - timedelta(hours=4)  # EDT
        time_str = eastern.strftime("%-I:%M %p ET")
    except (ValueError, KeyError):
        pass

    odds = extract_odds_intel(game)
    favorite = odds.get("favorite")
    total_line = odds.get("total_line", 0)
    spread = odds.get("spread", 0)
    over_price = odds.get("over_price", 0)
    under_price = odds.get("under_price", 0)
    under_favored = under_price < over_price if (over_price and under_price) else None
    book_name = odds.get("book", "market")

    # ── NHL ──
    if "icehockey" in sport_key:
        tempo = classify_nhl(home, away)

        if tempo == "controlled":
            under_target = f"Under {total_line} total goals" if total_line else "Under 6.5 total goals"
            legs = [under_target]
            if favorite:
                legs.append(f"{favorite} ML (moneyline)")
                if spread and spread <= 1.5:
                    legs.append(f"{favorite} +1.5 PL (puckline)")
            legs.append("1st period Under 1.5")

            conf = 7
            reason = "Both teams are low-event — controlled tempo"
            if total_line and total_line <= 5.5:
                conf = 9; reason += ", total line confirms (≤5.5)"
            elif total_line and total_line <= 6.0:
                conf = 8; reason += f", total line supports ({total_line})"
            elif total_line and total_line >= 7.0:
                conf = 6; reason = f"Low-event teams but total line high ({total_line}) — market disagrees"
            if under_favored:
                conf = min(10, conf + 1)

            return {
                "home": home, "away": away, "time": time_str, "sport": "NHL",
                "tag": "★ CONTROLLED", "legs": legs, "rating": "prime",
                "validation": {
                    "verdict": "VALIDATED" if conf >= 7 else "CAUTION",
                    "confidence": conf,
                    "reason": reason[:80],
                    "sources": ["Tempo Profile", f"Live Odds ({book_name})"],
                },
            }

        elif tempo == "chaotic":
            return {
                "home": home, "away": away, "time": time_str, "sport": "NHL",
                "tag": "⚡ CHAOTIC", "legs": ["Skip unders — high-event matchup"], "rating": "avoid",
                "validation": {
                    "verdict": "DROPPED", "confidence": 2,
                    "reason": "Both teams are high-event — chaotic matchup",
                    "sources": ["Tempo Profile"],
                },
            }

        else:  # mixed
            anchor = home if NHL_TEAMS.get(home) == "low" else away
            non_anchor = away if anchor == home else home
            under_target = f"Under {total_line} total goals" if total_line else "Under 6.5 total goals"
            legs = [under_target, f"{non_anchor} Under 3.5 team total", "1st period Under 1.5"]

            conf = 5
            reason = f"{anchor} anchors the pace, risk from {non_anchor}"
            if total_line and total_line <= 5.5:
                conf = 7; reason += " — but total line is tight"
            elif total_line and total_line <= 6.0:
                conf = 6; reason += " — total line moderate"
            if under_favored:
                conf = min(10, conf + 1)
            if favorite == anchor:
                conf = min(10, conf + 1)
                reason = f"{anchor} favored + controls tempo"

            tag = f"★ VALIDATED — {anchor} anchors" if conf >= 7 else f"MIXED — {anchor} anchors"
            rating = "prime" if conf >= 7 else "playable"

            return {
                "home": home, "away": away, "time": time_str, "sport": "NHL",
                "tag": tag, "legs": legs, "rating": rating,
                "validation": {
                    "verdict": "VALIDATED" if conf >= 7 else "CAUTION",
                    "confidence": conf,
                    "reason": reason[:80],
                    "sources": ["Tempo Profile", f"Live Odds ({book_name})"],
                },
            }

    # ── Soccer ──
    league = get_soccer_tier(sport_key)

    if league["tier"] == 1:
        under_target = f"Under {total_line} goals" if total_line and total_line <= 2.5 else "Under 2.5 goals"
        legs = [under_target]
        if favorite:
            legs.append(f"{favorite} DC (draw no bet)")
            legs.append(f"{favorite} Over 3 corners")
        else:
            legs.append("BTTS No (both teams to score)")
            legs.append("Under 8.5 corners")

        conf = 7
        reason = f"{league['label']} — prime under territory"
        if league.get("is_cup"):
            reason = f"{league['label']} knockout — teams protect leads"; conf = 8
        if total_line and total_line <= 2.25:
            conf = min(10, conf + 1); reason += ", line confirms"
        if under_favored:
            conf = min(10, conf + 1)

        return {
            "home": home, "away": away, "time": time_str, "sport": league["label"],
            "tag": "★ TIER 1", "legs": legs, "rating": "prime",
            "validation": {
                "verdict": "VALIDATED" if conf >= 7 else "CAUTION",
                "confidence": conf,
                "reason": reason[:80],
                "sources": ["League Tier", f"Live Odds ({book_name})"],
            },
        }

    elif league["tier"] == 2:
        legs = []
        if sport_key == "soccer_usa_mls":
            legs.append(f"Under {total_line} goals" if total_line else "Under 3.0 goals")
        else:
            legs.append("Under 2.5 goals")
        if favorite:
            legs.append(f"{favorite} DC (draw no bet)")
            legs.append(f"{favorite} Over 3 corners")
        else:
            legs.append("BTTS No")
            legs.append("Under 8.5 corners")

        conf = 5
        reason = f"{league['label']} — usable with filtering"
        if total_line and total_line <= 2.25:
            conf = 7; reason += ", line confirms under"
        elif total_line and total_line <= 2.5:
            conf = 6; reason += ", line moderate"
        if under_favored:
            conf = min(10, conf + 1)
        if spread and spread >= 1.5:
            conf = min(10, conf + 1)
            reason = f"Heavy favorite → controlled tempo in {league['label']}"

        tag = f"★ VALIDATED — {league['label']}" if conf >= 7 else "TIER 2 — filter"
        rating = "prime" if conf >= 7 else "playable"

        return {
            "home": home, "away": away, "time": time_str, "sport": league["label"],
            "tag": tag, "legs": legs, "rating": rating,
            "validation": {
                "verdict": "VALIDATED" if conf >= 7 else "CAUTION",
                "confidence": conf,
                "reason": reason[:80],
                "sources": ["League Tier", f"Live Odds ({book_name})"],
            },
        }

    else:  # Tier 3
        return {
            "home": home, "away": away, "time": time_str, "sport": league["label"],
            "tag": "TIER 3 — AVOID", "legs": ["Skip unders — high-variance league"], "rating": "avoid",
            "validation": {
                "verdict": "DROPPED", "confidence": 2,
                "reason": f"{league['label']} — high-scoring league, avoid unders",
                "sources": ["League Tier"],
            },
        }


# ── Main Fetch & Write ───────────────────────────────────────

def discover_available_sports():
    """Call /v4/sports to find which sports the API key can access."""
    try:
        resp = requests.get(
            f"{ODDS_BASE}/sports/",
            params={"apiKey": ODDS_API_KEY},
            timeout=15,
        )
        if resp.status_code != 200:
            log.warning(f"Sports discovery failed: {resp.status_code} — {resp.text[:200]}")
            return None
        sports = resp.json()
        active = {s["key"] for s in sports if s.get("active")}
        log.info(f"API has {len(active)} active sports available")
        return active
    except Exception as e:
        log.warning(f"Sports discovery error: {e}")
        return None


def fetch_all_picks():
    """Fetch odds for all sports, build picks, return sorted list."""
    if not ODDS_API_KEY:
        log.error("No ODDS_API_KEY found. Set it in .env or environment.")
        return []

    # Step 1: discover which sports the API key can access
    available = discover_available_sports()
    if available is not None:
        for label, sport_key in list(SPORT_KEYS.items()):
            if sport_key not in available:
                log.info(f"{label} ({sport_key}): not available on this plan, skipping")
        sports_to_query = {
            label: sk for label, sk in SPORT_KEYS.items() if sk in available
        }
        if not sports_to_query:
            log.error("No matching sports found! Available keys: " +
                      ", ".join(sorted(available)[:20]))
            return []
        log.info(f"Querying {len(sports_to_query)} sports: {', '.join(sports_to_query.keys())}")
    else:
        # Discovery failed — try all anyway
        sports_to_query = SPORT_KEYS

    all_picks = []
    now = datetime.now(tz=None)  # local time
    from datetime import timezone
    utc_now = datetime.now(timezone.utc)

    # Window: from now until end of today + 6h buffer for late west-coast games
    end_of_day = now.replace(hour=23, minute=59, second=59)
    cutoff = end_of_day + timedelta(hours=6)
    cutoff_utc = utc_now + (cutoff - now)

    remaining = "?"

    for label, sport_key in sports_to_query.items():
        try:
            url = f"{ODDS_BASE}/sports/{sport_key}/odds/"
            params = {
                "apiKey": ODDS_API_KEY,
                "regions": "us,us2,uk,eu",
                "markets": "h2h,totals,spreads",
                "oddsFormat": "american",
                "dateFormat": "iso",
            }
            resp = requests.get(url, params=params, timeout=15)

            if resp.status_code == 401:
                body = resp.text[:150]
                if "quota" in body.lower():
                    log.error(f"{label}: MONTHLY QUOTA EXHAUSTED — no more requests until reset")
                    log.error("Free tier = 500 requests/month. Upgrade at https://the-odds-api.com")
                    return all_picks  # stop immediately, don't waste calls
                else:
                    log.info(f"{label}: auth failed (401) — {body}")
                continue
            if resp.status_code == 429:
                log.warning(f"{label}: rate limited (429) — waiting won't help, skipping")
                continue
            if resp.status_code == 422:
                log.info(f"{label}: not on plan (422), skipping")
                continue
            if resp.status_code != 200:
                log.warning(f"{label}: API error {resp.status_code} — {resp.text[:100]}")
                continue

            rem = resp.headers.get("x-requests-remaining", remaining)
            remaining = rem
            games = resp.json()
            in_window = 0

            for game in games:
                try:
                    game_time = datetime.fromisoformat(
                        game["commence_time"].replace("Z", "+00:00")
                    )
                    # Make naive for comparison
                    game_utc = game_time.replace(tzinfo=None)
                    utc_now_naive = utc_now.replace(tzinfo=None)
                    cutoff_naive = cutoff_utc.replace(tzinfo=None)
                    if game_utc < utc_now_naive:
                        continue
                    if game_utc > cutoff_naive:
                        continue
                    in_window += 1
                    pick = build_pick(game, label, sport_key)
                    if pick:
                        all_picks.append(pick)
                except (KeyError, ValueError) as e:
                    continue

            log.info(f"{label}: {len(games)} games, {in_window} in today's window → {sum(1 for p in all_picks if p.get('sport','').lower().startswith(label[:3]))} picks")

        except Exception as e:
            log.warning(f"{label}: fetch failed — {e}")

    log.info(f"Total: {len(all_picks)} picks, {remaining} API requests remaining")

    # Sort: prime first, then by confidence descending
    rating_order = {"prime": 0, "playable": 1, "neutral": 2, "avoid": 3}
    all_picks.sort(key=lambda p: (
        rating_order.get(p.get("rating", "neutral"), 2),
        -(p.get("validation", {}).get("confidence", 0)),
    ))

    return all_picks


def write_to_dashboard(picks):
    """Write picks to dashboard-data.json under the 'betting' key."""
    if not DASHBOARD_JSON.exists():
        log.error(f"Dashboard JSON not found: {DASHBOARD_JSON}")
        return False

    try:
        data = json.loads(DASHBOARD_JSON.read_text())
    except (json.JSONDecodeError, OSError) as e:
        log.error(f"Failed to read dashboard JSON: {e}")
        return False

    data["betting"] = {
        "games": picks,
        "updated": datetime.now().isoformat(),
        "source": "betting_refresh.py",
    }

    try:
        DASHBOARD_JSON.write_text(json.dumps(data, indent=2))
        log.info(f"Wrote {len(picks)} picks to {DASHBOARD_JSON}")
        return True
    except OSError as e:
        log.error(f"Failed to write dashboard JSON: {e}")
        return False


def main():
    log.info("=" * 50)
    log.info("Betting Refresh — Predictable Tempo Strategy")
    log.info(f"Date: {date.today().isoformat()}")
    log.info("=" * 50)

    picks = fetch_all_picks()

    if picks:
        prime = sum(1 for p in picks if p["rating"] == "prime")
        playable = sum(1 for p in picks if p["rating"] == "playable")
        avoid = sum(1 for p in picks if p["rating"] == "avoid")
        validated = sum(1 for p in picks if p.get("validation", {}).get("verdict") == "VALIDATED")
        log.info(f"Results: {prime} prime, {playable} playable, {avoid} avoid | {validated} validated")

        write_to_dashboard(picks)

        # Print summary
        print(f"\n{'=' * 50}")
        print(f"TODAY'S PICKS — {date.today().strftime('%A, %B %-d')}")
        print(f"{'=' * 50}\n")
        for p in picks:
            if p["rating"] == "avoid":
                continue
            v = p.get("validation", {})
            verdict = v.get("verdict", "")
            conf = v.get("confidence", 0)
            icon = "★" if verdict == "VALIDATED" else "⚠" if verdict == "CAUTION" else "✕"
            print(f"{icon} {p['away']} @ {p['home']}")
            print(f"  {p['time']} · {p['sport']} · {p['tag']}")
            for leg in p.get("legs", []):
                print(f"    → {leg}")
            if verdict:
                print(f"  {icon} {verdict} ({conf}/10) — {v.get('reason', '')}")
            print()
    else:
        log.info("No picks generated — either no games or API issue")
        # Still write empty to dashboard so it shows "no games" vs stale data
        write_to_dashboard([])


if __name__ == "__main__":
    main()
