"""
Polymarket live price checker (read-only) — soccer match-winner markets.

Parallel to kalshi_live.py. Polymarket structures each match as a Gamma "event"
titled "Home vs. Away" containing three separate Yes/No markets:
    "Will {Home} win on {date}?"     -> YES price = P(home win)
    "Will {Away} win on {date}?"     -> YES price = P(away win)
    "Will {Home} vs. {Away} end in a draw?" -> YES price = P(draw)
Each market carries bestBid / bestAsk (the YES side). We expose the same
{home,draw,away} x {bid,ask,mid} dict shape that kalshi_live.get_all_prices returns,
so the rest of the pipeline can treat the two books uniformly.

Coverage note: Polymarket's soccer tag is dominated by big tournaments (World Cup)
and a rotating subset of club leagues; it does NOT list every league we track.
get_all_prices returns None when the fixture is not on Polymarket.

NOTHING is traded here. Pure price reads against the public Gamma API (no key).
"""
import re, time, json, os, unicodedata
import requests

GAMMA = "https://gamma-api.polymarket.com"
SOCCER_TAG = "soccer"
_CACHE_TTL = 60          # seconds; each tag's event list is reused within a poll cycle
LEAGUE_MAP_PATH = r"E:\soccer\data\league_map.json"

# Polymarket scatters league games across ~24 SEPARATE per-league tags; the generic
# 'soccer' tag only carries internationals + a fraction of top-league/cup games (most
# league fixtures are ABSENT from it). So we fetch each tracked league's own tag and
# union the results. The slug for each league lives in league_map.json's poly_tag field
# (mapped 2026-06-18 by reading real event tags off the Gamma API; see project memory).
def _load_league_tags():
    """{league_code: poly_tag}, {league_code: poly_prefix}, and the de-duped tag list.

    poly_prefix (added 2026-07-11): Poly's event slugs carry league prefixes
    ('per1-cal-csh-2026-07-19', 'mex-nec-atla-...'). Some leagues' MATCH events
    carry NO league tag at all (Peru/Chile/Romania are only tagged
    sports/games/soccer), so the slug prefix is the only reliable league
    discriminator for them — discovered when tag-guess audits wrongly called
    Liga MX and Peru "not on Poly" while the site showed 66-68 markets each."""
    try:
        with open(LEAGUE_MAP_PATH, encoding='utf-8') as fh:
            lm = json.load(fh)
    except (OSError, ValueError):
        return {}, {}, []
    by_code = {code: (info.get('poly_tag') if isinstance(info, dict) else None)
               for code, info in lm.items()}
    prefix_by_code = {code: (info.get('poly_prefix') if isinstance(info, dict) else None)
                      for code, info in lm.items()}
    tags = sorted({t for t in by_code.values() if t})
    return by_code, prefix_by_code, tags

_LEAGUE_TAG_BY_CODE, _LEAGUE_PREFIX_BY_CODE, _ALL_POLY_TAGS = _load_league_tags()
_cache = {}              # tag_slug -> {"ts": float, "events": list}


def _norm(s):
    """Lowercase, strip accents/punctuation -> comparable team-name key."""
    s = unicodedata.normalize('NFKD', s or '').encode('ascii', 'ignore').decode().lower()
    return re.sub(r'[^a-z0-9 ]', ' ', s).strip()


def _event_date(e):
    """Game date 'YYYY-MM-DD' for an event. Prefers the clean eventDate field,
    falls back to gameStartTime / a 'win on {date}' question (World Cup format)."""
    d = e.get('eventDate')
    if d and re.match(r'\d{4}-\d{2}-\d{2}', str(d)):
        return str(d)[:10]
    for m in (e.get('markets') or []):
        gst = m.get('gameStartTime') or m.get('endDateIso') or ''
        mm = re.match(r'(\d{4}-\d{2}-\d{2})', str(gst))
        if mm:
            return mm.group(1)
        qm = re.search(r'win on (\d{4}-\d{2}-\d{2})', m.get('question', '') or '')
        if qm:
            return qm.group(1)
    return None


def _strip_league_prefix(title):
    """'EPL: Southampton vs. Man United' -> 'Southampton vs. Man United'.
    Only strips a leading 'Something: ' when the remainder still looks like 'X vs Y'."""
    m = re.match(r'^[^:]{1,40}:\s*(.+)$', title)
    if m and ' vs' in m.group(1).lower():
        return m.group(1)
    return title


def _fetch_tag(tag, force=False):
    """All open events for a single Gamma tag_slug (paginated), cached per tag."""
    now = time.time()
    c = _cache.get(tag)
    if not force and c is not None and now - c["ts"] < _CACHE_TTL:
        return c["events"]
    events, off = [], 0
    while True:
        try:
            r = requests.get(f"{GAMMA}/events",
                             params={"closed": "false", "tag_slug": tag,
                                     "limit": 100, "offset": off}, timeout=20)
        except requests.RequestException:
            break
        if not r.ok:
            break
        batch = r.json()
        if not batch:
            break
        events += batch
        off += 100
        # 6000 cap (was 2000, raised 2026-07-11): tagless-league matches are only
        # reachable through the generic 'soccer' tag, which now spans 40+ leagues
        # of events — truncation here silently hides whole leagues.
        if len(batch) < 100 or off >= 6000:
            break
    _cache[tag] = {"ts": now, "events": events}
    return events


def _tags_for_league(league):
    """Tags to query for a fixture. With a known league -> just its poly_tag (+ soccer
    for cup/intl spillover); without -> every tracked league tag + soccer (full union)."""
    t = _LEAGUE_TAG_BY_CODE.get(league) if league else None
    if t:
        return [t, SOCCER_TAG]
    return _ALL_POLY_TAGS + [SOCCER_TAG]


def fetch_soccer_events(force=False, tags=None):
    """Open events across the given tags (default: all tracked league poly_tags +
    'soccer'), de-duped by event id. Each tag is paginated and cached _CACHE_TTL sec,
    so repeated calls within a poll cycle reuse the per-tag caches."""
    if tags is None:
        tags = _ALL_POLY_TAGS + [SOCCER_TAG]
    seen, out = set(), []
    for tag in tags:
        for e in _fetch_tag(tag, force=force):
            eid = e.get('id')
            if eid is not None and eid in seen:
                continue
            seen.add(eid)
            out.append(e)
    return out


def _names_match(a, b):
    """True if normalized team names a, b refer to the same club (substring either way)."""
    na, nb = _norm(a), _norm(b)
    if not na or not nb:
        return False
    if na == nb:
        return True
    # require a reasonably long common token-run to avoid false hits
    return (na in nb or nb in na) and min(len(na), len(nb)) >= 5


# team_map.json aliases (via maps.py) bridge API-Football <-> Polymarket naming
# drift, e.g. API-Football 'Henan Jianye' vs Poly 'Henan FC' (found 2026-07-03:
# raw substring matching missed the whole CSL despite full Poly coverage).
try:
    import maps as _maps
except ImportError:
    _maps = None

def _variants(team):
    """All known names for a team: itself + its team_map key / fdco / aliases."""
    out = [team]
    if _maps is not None:
        try:
            key, info = _maps._lookup(team)
            if key:
                out.append(key)
            if info:
                if info.get('fdco'):
                    out.append(info['fdco'])
                out.extend(info.get('aliases') or [])
        except Exception:
            pass
    return out

def _team_match(name, team):
    """_names_match against every known variant of `team`."""
    return any(_names_match(name, v) for v in _variants(team))


def find_event(game_date, home_team, away_team, league=None):
    """Return the Gamma event dict for this fixture, or None.
       game_date: 'YYYY-MM-DD' string OR datetime.date (normalized here — live_monitor
                  passes a date object; comparing that against Gamma's string dates
                  silently matched nothing until 2026-07-03).
       league:    optional tracked league code (e.g. 'E0') -> fetch only that league's
                  Polymarket tag instead of the full union (much cheaper)."""
    if hasattr(game_date, 'isoformat'):
        game_date = game_date.isoformat()
    game_date = str(game_date)[:10]
    # slug-prefix filter: when the league has a known Poly slug prefix
    # ('per1', 'mex', ...), require it — precise league discrimination for
    # leagues whose match events carry no league tag.
    prefix = _LEAGUE_PREFIX_BY_CODE.get(league) if league else None
    cands = []
    for e in fetch_soccer_events(tags=_tags_for_league(league)):
        title = e.get('title', '')
        if ' vs' not in title.lower() or 'more market' in title.lower():
            continue
        if prefix and not str(e.get('slug', '')).startswith(prefix + '-'):
            continue
        parts = re.split(r'\s+vs\.?\s+', _strip_league_prefix(title))
        if len(parts) != 2:
            continue
        h, a = parts
        # title order is Home vs. Away; accept either ordering defensively
        ok = (_team_match(h, home_team) and _team_match(a, away_team)) or \
             (_team_match(h, away_team) and _team_match(a, home_team))
        if not ok:
            continue
        cands.append((e, _event_date(e)))
    if not cands:
        return None
    # Poly now lists several sub-events per match ("X vs. Y - Halftime Result",
    # "- Exact Score", ...). The main match-winner event is the one WITHOUT a
    # " - <suffix>" tail — prefer it (shortest title among matches as tie-break).
    def _is_main(e):
        return ' - ' not in _strip_league_prefix(e.get('title', ''))
    cands.sort(key=lambda t: (not _is_main(t[0]), len(t[0].get('title', ''))))
    # prefer the candidate whose game date equals game_date; else, if the main
    # event is unambiguous, take it
    for e, d in cands:
        if d == game_date:
            return e
    mains = [e for e, _ in cands if _is_main(e)]
    if len(mains) == 1:
        return mains[0]
    return cands[0][0] if len(cands) == 1 else None


def _yes_prices(market):
    """(bid, ask, mid) for the YES side of a Yes/No market. mid mirrors kalshi_live:
       (ask+bid)/2 when bid>0 else ask."""
    def _f(x):
        try: return float(x)
        except (TypeError, ValueError): return 0.0
    ask = _f(market.get('bestAsk'))
    bid = _f(market.get('bestBid'))
    mid = (ask + bid) / 2 if bid > 0 else ask
    return round(bid, 4), round(ask, 4), round(mid, 4)


def get_all_prices(game_date, home_team, away_team, league=None):
    """Fetch home/draw/away YES prices from Polymarket. Returns dict or None.
       Shape matches kalshi_live.get_all_prices:
         {home_bid,home_ask,home_mid, draw_*, away_*}
       league: optional tracked league code to narrow the Gamma fetch to one tag."""
    ev = find_event(game_date, home_team, away_team, league=league)
    if not ev:
        return None
    result = {}
    for m in (ev.get('markets') or []):
        q = m.get('question', '')
        ql = q.lower()
        bid, ask, mid = _yes_prices(m)
        if 'end in a draw' in ql:
            label = 'draw'
        else:
            # winner side = first team named. Two question dialects:
            #   league: "Will {X} beat {Y}?"      WC: "Will {X} win on {date}?"
            mm = re.search(r'will (.+?) (?:beat|win on)', ql)
            if not mm:
                continue   # spreads / O-U / BTTS — not match-winner
            who = mm.group(1)
            if _team_match(who, home_team):
                label = 'home'
            elif _team_match(who, away_team):
                label = 'away'
            else:
                continue
        result[f'{label}_bid'] = bid
        result[f'{label}_ask'] = ask
        result[f'{label}_mid'] = mid
    return result or None


def list_matches(target_date=None):
    """List soccer matches Polymarket currently has (optionally filtered to a date)."""
    out = []
    for e in fetch_soccer_events():
        title = e.get('title', '')
        if ' vs' not in title.lower() or 'more market' in title.lower():
            continue
        d = _event_date(e)
        if target_date and d != target_date:
            continue
        if d:
            out.append((d, title))
    for d, t in sorted(out):
        print(f"  {d}  {t}")
    return out


if __name__ == '__main__':
    import sys
    sys.stdout.reconfigure(encoding='utf-8', errors='replace')
    print(f"querying {len(_ALL_POLY_TAGS)} league tags + 'soccer'")
    evs = fetch_soccer_events()
    print(f"open events (union, deduped): {len(evs)}")
    print("\nupcoming matches:")
    list_matches()
