"""Data sources for the sports predictor.

- OpenLigaDB (keyless): Bundesliga fixtures + full history + live table
- Wikipedia REST (keyless): current-season standings for any top league
- the-odds-api (key via env ODDS_API_KEY): upcoming fixtures + bookmaker odds

All sources return normalized dicts:
  fixture: {league, home, away, kickoff_utc, group}
  result:  {league, home, away, kickoff_utc, hg, ag}
  standings: {team_name: ppg}   (league avg : '_avg')
"""
import json
import os
import re
import statistics
import time
import unicodedata
import difflib
import html
import urllib.request
from urllib.parse import quote

UA = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) sports-predictor/1.0"}
CACHE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".cache")
os.makedirs(CACHE_DIR, exist_ok=True)

LEAGUES = {
    "Bundesliga": {
        "openligadb": "bl1",
        "wiki_title": "2026–27 Bundesliga",
        "odds_key": "soccer_germany_bundesliga",
    },
    "Premier League": {
        "openligadb": None,
        "wiki_title": "2026–27 Premier League",
        "odds_key": "soccer_epl",
    },
    "La Liga": {
        "openligadb": None,
        "wiki_title": "2026–27 La Liga",
        "odds_key": "soccer_spain_la_liga",
    },
    "Serie A": {
        "openligadb": None,
        "wiki_title": "2026–27 Serie A",
        "odds_key": "soccer_italy_serie_a",
    },
    "Ligue 1": {
        "openligadb": None,
        "wiki_title": "2026–27 Ligue 1",
        "odds_key": "soccer_france_ligue_one",
    },
}


def _get(url, timeout=25):
    req = urllib.request.Request(url, headers=UA)
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return r.read()


def _get_json(url, timeout=25):
    return json.loads(_get(url, timeout).decode("utf-8", "replace"))


def _cache(key, ttl_s, fetch, *args):
    path = os.path.join(CACHE_DIR, key + ".json")
    if os.path.exists(path) and time.time() - os.path.getmtime(path) < ttl_s:
        try:
            with open(path) as f:
                return json.load(f)
        except (ValueError, OSError):
            pass  # corrupt/partial cache -> refetch
    data = fetch(*args)
    with open(path, "w") as f:
        json.dump(data, f)
    return data


# ---------------------------------------------------------------- OpenLigaDB
def openligadb_current_season(shortcut="bl1"):
    return 2026  # current season in this world; bl1 2026 = season in progress


def openligadb_matches(shortcut="bl1", season=2026):
    raw = _get_json(f"https://api.openligadb.de/getmatchdata/{shortcut}/{season}")
    out = []
    for g in raw:
        res = _final_score(g.get("matchResults", []))
        out.append({
            "league": shortcut,
            "home": g["team1"]["teamName"],
            "away": g["team2"]["teamName"],
            "kickoff_utc": g.get("matchDateTimeUTC"),
            "group": (g.get("group") or {}).get("groupOrderID"),
            "finished": bool(g.get("matchIsFinished")),
            "hg": res[0] if res else None,
            "ag": res[1] if res else None,
        })
    return out


# ---------------------------------------------------------------- name matching
# the-odds-api uses English short names ("Bayern Munich", "Inter"); Wikipedia /
# OpenLigaDB use local spellings ("FC Bayern München", "Inter Milan"). Match via
# accent-stripped equality, containment (≥6 chars), or fuzzy ratio.
import unicodedata
import difflib

_TEAM_ALIASES = {
    "internazionale": "inter",
    "inter milan": "inter",
    "ac milan": "milan",
    "paris sg": "paris saint-germain",
    "psg": "paris saint-germain",
    "m'gladbach": "borussia monchengladbach",
    "real betis balompie": "real betis",
    "rcd espanyol": "espanyol",
    "ca osasuna": "osasuna",
    "rcd mallorca": "mallorca",
    "deportivo alaves": "alaves",
    "cd leganes": "leganes",
    "afc bournemouth": "bournemouth",
    "brighton & hove albion": "brighton and hove albion",
    "west ham united": "west ham",
    "tottenham hotspur": "tottenham",
    "newcastle united": "newcastle",
    "manchester city": "manchester city",
    "manchester united": "manchester united",
}


def normalize_team(name):
    s = unicodedata.normalize("NFKD", name or "")
    s = s.encode("ascii", "ignore").decode()
    s = re.sub(r"[^a-z0-9]+", " ", s.lower()).strip()
    s = _TEAM_ALIASES.get(s, s)
    return s


def match_team(src_name, standings):
    """Return the standings key matching src_name, or None."""
    n = normalize_team(src_name)
    if n in standings:
        return n
    for key in standings:
        k = normalize_team(key)
        if k == n:
            return key
    for key in standings:  # containment, only for long names
        k = normalize_team(key)
        if len(n) >= 6 and len(k) >= 6 and (n in k or k in n):
            return key
    best = max(standings, key=lambda s: difflib.SequenceMatcher(
        None, n, normalize_team(s)).ratio())
    if best and difflib.SequenceMatcher(None, n, normalize_team(best)).ratio() > 0.85:
        return best
    return None


def _final_score(match_results):
    for mr in match_results:
        if mr.get("resultTypeID") == 2:  # final result
            return mr.get("pointsTeam1"), mr.get("pointsTeam2")
    for mr in match_results:  # fallback: German naming
        name = (mr.get("resultName") or "").lower()
        if "endergebnis" in name or "final" in name:
            return mr.get("pointsTeam1"), mr.get("pointsTeam2")
    return None


# ---------------------------------------------------------------- Wikipedia
def wikipedia_standings(wiki_title, ttl_s=6 * 3600):
    """Current standings table from the league's Wikipedia season article."""
    def fetch():
        url = ("https://en.wikipedia.org/api/rest_v1/page/html/"
               + quote(wiki_title, safe=""))
        page = _get(url).decode("utf-8", "replace")
        rows_out = []
        for t in re.findall(r"<table[^>]*>.*?</table>", page, re.S):
            if "Pld" not in t or "Pts" not in t:
                continue
            rows = re.findall(r"<tr[^>]*>(.*?)</tr>", t, re.S)
            for r in rows[1:]:
                cells = [re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", c)).strip()
                         for c in re.findall(r"<t[dh][^>]*>(.*?)</t[dh]>", r, re.S)]
                if len(cells) >= 10 and cells[0].isdigit():
                    team = html.unescape(re.sub(r"\[.*?\]", "", cells[1]).strip())
                    pld = int(cells[2]); pts = int(cells[9])
                    if pld > 0:
                        rows_out.append((team, pts / pld, pld))
        if not rows_out:
            raise ValueError(f"no standings table found for {wiki_title}")
        return rows_out

    rows = list(_cache("wiki_" + re.sub(r"[^A-Za-z0-9]+", "_", wiki_title),
                       ttl_s, fetch))
    return {  # OrderedDict-ish: dict preserves insertion order
        k: (v, n) for k, v, n in rows
    }


def standings_with_avg(league_cfg):
    """Return {team: ppg, '_avg': avg_ppg, '_n': {team: games_played}}."""
    if league_cfg.get("openligadb"):
        shortcut = league_cfg["openligadb"]
        season = openligadb_current_season(shortcut)
        raw = _get_json(f"https://api.openligadb.de/getbltable/{shortcut}/{season}")
        st = {r["teamName"]: r["points"] / r["matches"]
              for r in raw if r.get("matches", 0) > 0}
        n_map = {r["teamName"]: r["matches"]
                 for r in raw if r.get("matches", 0) > 0}
    else:
        st = dict(wikipedia_standings(league_cfg["wiki_title"]))
        n_map = {k: v[1] for k, v in st.items()}
        st = {k: v[0] for k, v in st.items()}
    avg = sum(st.values()) / len(st) if st else 1.0
    st["_avg"] = avg
    st["_n"] = n_map
    return st


# ---------------------------------------------------------------- the-odds-api
def oddsapi_key():
    return os.environ.get("ODDS_API_KEY", "").strip()


def _oddsapi_fetch(odds_key):
    """Raw fetch for a league's h2h odds (called via _cache)."""
    key = oddsapi_key()
    url = ("https://api.the-odds-api.com/v4/sports/%s/odds/"
           "?apiKey=%s&regions=eu&markets=h2h&oddsFormat=decimal" % (odds_key, key))
    data = _get_json(url)
    out = []
    for e in data:
        book_lines = []
        for b in e.get("bookmakers", []):
            for m in b.get("markets", []):
                if m["key"] != "h2h":
                    continue
                o = {x["name"]: x["price"] for x in m["outcomes"]}
                if len(o) != 3 or any(v <= 1.0 for v in o.values()):
                    continue
                margin = sum(1.0 / v for v in o.values())
                if 0.98 <= margin <= 1.25:
                    book_lines.append(o)
        odds = None
        if book_lines:
            names = [e.get("home_team"), "Draw", e.get("away_team")]
            odds = {n: statistics.median(
                [bl[n] for bl in book_lines if n in bl]) for n in names}
        out.append({
            "league": odds_key,
            "home": e.get("home_team"),
            "away": e.get("away_team"),
            "kickoff_utc": e.get("commence_time"),
            "odds": odds,
            "n_books": len(book_lines),
        })
    return out


def oddsapi_fixtures(odds_key):
    """Upcoming fixtures for one league with bookmaker 1X2 odds.

    Aggregation: keep only books quoting all three h2h outcomes with a sane
    overround (margin 0.98-1.25), then take the MEDIAN price per outcome.
    Never merge best-prices per outcome across books — that fabricates
    impossible lines (e.g. 310.0/450.0/1000.0) and fake >5000% EV.

    Cached 30 min so one run (book EV + World block) uses a single API call.
    """
    if not oddsapi_key():
        return []
    return _cache("odds_" + odds_key, 1800, _oddsapi_fetch, odds_key)