"""Daily picks: compute model predictions for upcoming fixtures across leagues.

- Bundesliga: fixtures from OpenLigaDB (richer), EV merged from the-odds-api
- Other top-5 leagues: fixtures+odds from the-odds-api (needs ODDS_API_KEY),
  standings from Wikipedia
- Output: markdown report to ./picks_today.md, printed to stdout
- Telegram: if TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID are set, sends the report

Usage: python3 picks.py
"""
import os
import sys
import json
import datetime

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from sources import (LEAGUES, openligadb_matches, openligadb_current_season,
                     standings_with_avg, oddsapi_fixtures, oddsapi_key,
                     match_team)
from model import predict, ev_1x2

UTC = datetime.timezone.utc
FIXTURE_WINDOW_DAYS = 3
KO_SLACK_H = 6  # kickoff matching tolerance for merging odds onto fixtures


def fmt_odds_line(home, away, st, avg, n_map, odds=None):
    p = predict(st[home], st[away], avg, n_map.get(home), n_map.get(away))
    line = (f"{home} vs {away} | 1 {p['ph']:.0%} X {p['pd']:.0%} "
            f"2 {p['pa']:.0%} | pick {p['pick']} ({p['pick_prob']:.0%})")
    if odds:
        o = {"1": odds.get(home), "X": odds.get("Draw"), "2": odds.get(away)}
        ev = ev_1x2(o, {"1": p["ph"], "X": p["pd"], "2": p["pa"]})
        best_key = max(ev, key=lambda k: ev[k]["ev"]) if ev else None
        best = ev[best_key] if best_key else None
        if best and best["ev"] > 0.50:
            flag = f"  ⚠HUGE({best_key})"  # model-market clash = likely model error
        elif best and best["ev"] > 0.04:
            flag = f"  ⚡BET({best_key})"
        else:
            flag = ""
        k = best["kelly"] * 100 if best else 0
        line += (f" | odds {o['1'] or '-'}/{o['X'] or '-'}/{o['2'] or '-'}"
                 f" | EV({best_key}) {best['ev']:+.0%} K {k:.1f}%{flag}")
    return line


def remap_odds(odds, st):
    """Map the-odds-api outcome names (English short) onto standings keys."""
    if not odds:
        return odds
    out = {}
    for k, v in odds.items():
        if k == "Draw":
            out["Draw"] = v
        else:
            out[match_team(k, st) or k] = v
    return out


def bundesliga_block():
    season = openligadb_current_season()
    matches = openligadb_matches("bl1", season)
    now = datetime.datetime.now(UTC).isoformat()
    horizon = (datetime.datetime.now(UTC)
               + datetime.timedelta(days=FIXTURE_WINDOW_DAYS)).isoformat()
    upcoming = [m for m in matches
                if not m["finished"] and m["kickoff_utc"]
                and now <= m["kickoff_utc"] <= horizon]
    st = standings_with_avg(LEAGUES["Bundesliga"])
    avg = st.pop("_avg")
    n_map = st.pop("_n")

    odds_by_ko = {}  # (norm_home, norm_away) -> odds dict
    if oddsapi_key():
        for f in oddsapi_fixtures(LEAGUES["Bundesliga"]["odds_key"]):
            h = match_team(f["home"], st)
            a = match_team(f["away"], st)
            if h and a:
                odds_by_ko[(h, a)] = remap_odds(f["odds"], st)

    lines = ["## Bundesliga", "```"]
    n_ev = 0
    for m in sorted(upcoming, key=lambda x: x["kickoff_utc"]):
        h, a = m["home"], m["away"]
        if h not in st or a not in st:
            continue
        odds = odds_by_ko.get((h, a))
        if odds:
            n_ev += 1
        p = predict(st[h], st[a], avg, n_map.get(h), n_map.get(a))
        lines.append(fmt_odds_line(h, a, st, avg, n_map, odds)
                     + f" | xG {p['lh']+p['la']:.1f}"
                     f" | {m['kickoff_utc'][5:16]} UTC")
    lines.append("```")
    if not oddsapi_key():
        lines.append("_Only Bundesliga: set ODDS_API_KEY to enable the other "
                     "leagues + EV layer_")
    elif n_ev == 0:
        lines.append("_No odds matched for today's fixtures_")
    return "\n".join(lines)


def ev_block_for(league_name, cfg):
    if not oddsapi_key():
        return ""
    st = standings_with_avg(cfg)
    avg = st.pop("_avg")
    n_map = st.pop("_n")
    fixs = oddsapi_fixtures(cfg["odds_key"])
    now = datetime.datetime.now(UTC).isoformat()
    horizon = (datetime.datetime.now(UTC)
               + datetime.timedelta(days=FIXTURE_WINDOW_DAYS)).isoformat()
    fixs = [f for f in fixs if f["kickoff_utc"]
            and now <= f["kickoff_utc"] <= horizon]
    lines = [f"## {league_name}", "```"]
    n = 0
    for f in fixs:
        h = match_team(f["home"], st)
        a = match_team(f["away"], st)
        if not h or not a or not f.get("odds"):
            continue
        n += 1
        ko = (f["kickoff_utc"] or "")[5:16]
        lines.append(fmt_odds_line(h, a, st, avg, n_map, remap_odds(f["odds"], st))
                     + f" | {ko} UTC")
    lines.append("```")
    if n == 0:
        return f"## {league_name}\n_No fixtures with odds right now_"
    return "\n".join(lines)


def send_telegram(text, chat=None, parse=True):
    token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
    chat = chat or os.environ.get("TELEGRAM_CHAT_ID", "").strip()
    if not token or not chat:
        return False
    import urllib.request
    import urllib.parse
    import json
    for i in range(0, len(text), 3800):
        params = {"chat_id": chat, "text": text[i:i+3800]}
        if parse:
            params["parse_mode"] = "Markdown"
        payload = urllib.parse.urlencode(params).encode()
        req = urllib.request.Request(
            f"https://api.telegram.org/bot{token}/sendMessage", data=payload,
            headers={"Content-Type": "application/x-www-form-urlencoded"})
        try:
            with urllib.request.urlopen(req, timeout=25) as r:
                resp = json.loads(r.read())
        except urllib.error.HTTPError as e:
            if e.code == 400 and parse:
                return send_telegram(text, chat, parse=False)
            print("telegram HTTP", e.code, e.read().decode("utf-8", "replace")[:200])
            return False
        except Exception as e:
            print("telegram failed:", e)
            return False
        if not resp.get("ok"):
            print("telegram error:", resp)
            return False
    return True


def world_block():
    """World.xyz section: fair binary prices for upcoming matches.

    Prices are fetched by no feed (catalog API is Cloudflare-gated to the
    world.xyz frontend), so you compare these fair prices against the YES
    price shown in Phantom. BUY YES <= fair-0.07, BUY NO >= fair+0.07
    (0.05 edge + 0.02 dealer spread). world_check.py does the math per market.
    """
    from world import save_prices
    from model import binary_markets
    now = datetime.datetime.now(UTC).isoformat()
    horizon = (datetime.datetime.now(UTC)
               + datetime.timedelta(days=FIXTURE_WINDOW_DAYS)).isoformat()

    rows, lines = [], ["## World.xyz — fair binary prices (compare in Phantom)", "```"]

    # Bundesliga (OpenLigaDB fixtures)
    matches = openligadb_matches("bl1", openligadb_current_season())
    upcoming = [m for m in matches if not m["finished"] and m["kickoff_utc"]
                and now <= m["kickoff_utc"] <= horizon]
    st = standings_with_avg(LEAGUES["Bundesliga"])
    avg, n_map = st.pop("_avg"), st.pop("_n")
    for m in sorted(upcoming, key=lambda x: x["kickoff_utc"]):
        h, a = m["home"], m["away"]
        if h not in st or a not in st:
            continue
        p = predict(st[h], st[a], avg, n_map.get(h), n_map.get(a))
        b = binary_markets(p["lh"], p["la"])
        rows.append({"league": "Bundesliga", "home": h, "away": a,
                     "kickoff_utc": m["kickoff_utc"], "markets": b})

    # other leagues (the-odds-api fixtures, cached)
    for name, cfg in LEAGUES.items():
        if cfg.get("openligadb"):
            continue
        st = standings_with_avg(cfg)
        avg, n_map = st.pop("_avg"), st.pop("_n")
        for f in oddsapi_fixtures(cfg["odds_key"]):
            if not f["kickoff_utc"] or not (now <= f["kickoff_utc"] <= horizon):
                continue
            h = match_team(f["home"], st)
            a = match_team(f["away"], st)
            if not h or not a:
                continue
            p = predict(st[h], st[a], avg, n_map.get(h), n_map.get(a))
            b = binary_markets(p["lh"], p["la"])
            rows.append({"league": name, "home": h, "away": a,
                         "kickoff_utc": f["kickoff_utc"], "markets": b})

    for r in sorted(rows, key=lambda x: x["kickoff_utc"]):
        b = r["markets"]
        def thr(key):
            v = b[key]
            return f"{key}: {v:.0%} (YES≤{v-0.07:.0%} / NO≥{v+0.07:.0%})"
        line = f"{r['home']} vs {r['away']} | {thr('home_win')} | {thr('over_2_5')}"
        if abs(b["btts"] - 0.5) > 0.25:
            line += f" | {thr('btts')}"
        lines.append(line)
    lines.append("```")
    lines.append("_Check a price: python3 world_check.py \"<team> vs <team>\" <price> "
                 "[home_win|away_win|over_2_5|under_2_5|btts]_")
    save_prices(rows)
    global LAST_FIXTURES
    LAST_FIXTURES = [{"league": r["league"], "home": r["home"], "away": r["away"],
                      "kickoff_utc": r["kickoff_utc"]} for r in rows]
    return "\n".join(lines)


def main():
    blocks = [bundesliga_block()]
    for name, cfg in LEAGUES.items():
        if cfg.get("openligadb"):
            continue
        blk = ev_block_for(name, cfg)
        if blk:
            blocks.append(blk)
    blocks.append(world_block())
    # Match context (Digitain form/H2H) — bonus data, must never break the run
    try:
        import statsfeed
        fixtures = globals().get("LAST_FIXTURES") or []
        if fixtures:
            ctxs = statsfeed.build_context(fixtures)
            cm = statsfeed.context_markdown(ctxs)
            if cm:
                blocks.append(cm)
            statsfeed.write_app_context(ctxs)
            print("[context]", len(ctxs), "fixtures contextualized")
    except Exception as e:
        print("[context] skipped:", e)
    report = ("# Sports picks — {}\n\n".format(
        datetime.datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC"))
        + "\n\n".join(blocks))
    out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "picks_today.md")
    with open(out, "w") as f:
        f.write(report)
    # Append to permanent history so audit.py can grade rounds after the
    # daily file has been overwritten by the next run.
    hist = os.path.join(os.path.dirname(os.path.abspath(__file__)), "picks_history.md")
    try:
        prev_lines = open(hist, encoding="utf-8").read().splitlines()
    except OSError:
        prev_lines = []
    prev_dates = [l for l in prev_lines if l.startswith("# Sports picks —")]
    today_hdr = "# Sports picks — " + datetime.datetime.now(UTC).strftime("%Y-%m-%d")
    if not prev_dates or not prev_dates[-1].startswith(today_hdr):
        with open(hist, "a", encoding="utf-8") as f:
            f.write(report + "\n\n---\n\n")
    print(report)
    print("\n[saved to]", out)
    # Export JSON for the Telegram mini-app (picks UI)
    try:
        import world as world_mod
        web_data = []
        for r in world_mod.load_prices():
            web_data.append({
                "league": r.get("league"),
                "home": r.get("home"),
                "away": r.get("away"),
                "kickoff_utc": r.get("kickoff_utc", ""),
                "markets": r.get("markets"),
            })
        app_data = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                "app_data.json")
        with open(app_data, "w") as f:
            json.dump({"generated_utc": datetime.datetime.now(UTC).isoformat(),
                       "matches": web_data}, f, ensure_ascii=False)
        print("[app_data]", app_data, len(web_data), "matches")
    except Exception as e:
        print("[app_data] failed:", e)
    if send_telegram(report):
        print("[telegram] sent")


if __name__ == "__main__":
    main()