"""
Polymarket US — market lookup (public gateway API, no auth needed).

Maps our internal league codes to Polymarket US league slugs and finds the
specific per-team market for a fixture, mirroring polymarket_live.py's role
for polymarket.com but against the DIFFERENT gateway.polymarket.us catalog.

LEAGUE_SLUG only lists codes VERIFIED against live events -- unmapped leagues
return None and are silently skipped by the auto-trade hook (no market
attempted), not guessed at.

PAGE THE CATALOG. /v2/leagues takes limit+offset and returns 221 leagues; the
original 2026-07-23 pass believed it capped at 50 and so missed nine leagues we
trade. Verify a slug by reading TEAM NAMES out of its events, never by matching
the league name -- 'Serie B' is ambiguous between Italy and Brazil.
"""
import re
import time
import unicodedata
from typing import Optional

import requests

GATEWAY_BASE = "https://gateway.polymarket.us"

# div code -> Polymarket US league slug (verified against live /v2/leagues, 2026-07-23).
# 22 of our 40 tracked leagues found; ambiguous name matches (RO1/DK1/CZ1's slugs
# don't contain the country name) were confirmed by checking real team names in
# their events before trusting them for real money.
LEAGUE_SLUG = {
    'E0': 'epl', 'SP1': 'lal', 'D1': 'bun', 'I1': 'sea',
    'MLS': 'mls', 'BSA': 'bra', 'BR2': 'brb', 'AR1': 'lpa',
    'PE1': 'pl1', 'CO1': 'lco', 'KO1': 'kl1', 'CN1': 'csl',
    'SE1': 'alsv', 'NO1': 'els', 'MX1': 'lmx', 'USL': 'uslc',
    'NWSL': 'nwsl', 'CL1': 'pdc',
    # -- ADDED 2026-09-01 (user: "Cardiff is also on there") -------------
    # The map was built believing /v2/leagues "ignores paging and returns the
    # same 50 rows". IT DOES PAGE -- walking offset in steps of 50 returns 221
    # leagues. That wrong belief is why nine leagues we actually trade had no
    # slug and silently fell through to Kalshi: xlow bought QPR/Cardiff on
    # Kalshi while Poly-US listed the same fixture.
    #
    # Each was verified the way this file requires -- pulling the league live
    # events and reading real TEAM NAMES -- not by matching the catalog league
    # name, which is how 'Serie B' would have become Brazil.
    'E1': 'eflch',   # EFL Championship -- Millwall/Wrexham, QPR/Cardiff
    'F1': 'lg1',     # Ligue 1 -- PSG/Monaco, Lyon/Auxerre, Lens/Lorient
    'SP2': 'lal2',   # LaLiga 2 -- Las Palmas/Leganes
    'P1': 'ligpor',  # Liga Portugal -- Porto, Benfica, Sporting, Braga
    'T1': 'tsl',     # Super Lig -- Basaksehir/Galatasaray
    # DELIBERATELY NOT ADDED:
    #   I2  slug 'srb' is named "Serie B" but returns ZERO events, so it
    #       cannot be verified as Italy rather than Brazil (BR2 already maps
    #       to 'brb'). An unverified slug on a real-money path is what the
    #       docstring forbids. Revisit when it lists fixtures.
    #   N1 (Eredivisie), D2 (2. Bundesliga), E2 (EFL League One) are absent
    #       from the 221-league catalog entirely -- Poly-US does not carry
    #       them, so those stay Kalshi-only. Not a gap.
    'CH1': 'swsl',   # Swiss Super League. Slug read off a live event URL
                     # rather than the catalog.
    'SC0': 'scp',   # Scottish Prem
    'CZ1': 'flc',   # Fortuna Liga -- verified Czech teams (Zlin, Banik Ostrava, Plzen)
    'DK1': 'sld',   # bare "Superliga" slug -- verified Danish teams (Viborg, Odense, Brondby)
    'PL1': 'ekst',  # Ekstraklasa -- BUG FOUND 2026-07-24: this entry was discussed
                    # and reported as added when Poland was wired, but never
                    # actually written. Every Poly-US lookup for Poland (both the
                    # GAP_LEAGUES signal fetch and real execution) silently
                    # returned None this whole time as a result.
    # NOTE: RO1 (Romania) matches Poly-US slug 'slr' (verified: UTA Arad, Otelul,
    # Arges) but is deliberately OMITTED here -- RO1 has no Kalshi series
    # (league_map.json), so xlow can never fire a signal for it in the first
    # place (needs Kalshi+Poly.com both quoting to log a pending entry). This
    # hook only ever sees entries xlow already logged, so mapping RO1 here would
    # be dead code. Revisit only if RO1 ever gets a Kalshi series.
}

# Confirmed NOT on Polymarket US as of 2026-07-23 (checked the full 61-league
# soccer catalog for country/competition names and near-miss substrings; these
# came back genuinely absent, not just named differently). Re-check periodically
# -- the catalog has been growing (new leagues added most weeks).
LEAGUE_NOT_FOUND = {
    'E1', 'E2', 'E3', 'EC', 'SP2', 'I2', 'D2', 'F1', 'F2', 'N1', 'B1',
    'P1', 'T1', 'G1', 'SC1', 'SC2', 'SC3', 'JP1', 'CAN', 'HR1',
}

_events_cache = {}   # league_slug -> (ts, events)
_CACHE_TTL = 120

# team_map.json aliases bridge API-Football <-> Polymarket-US naming drift
# (e.g. "Club America" vs Poly-US's "CF America"; "Vasteras SK FK" vs Poly-US's
# "Vasteraas SK" -- both already had the needed alias in team_map.json, this
# module just wasn't consulting it). Same pattern as polymarket_live.py.
try:
    import maps as _maps
except ImportError:
    _maps = None


def _variants(team):
    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 _norm(s):
    s = unicodedata.normalize('NFKD', s or '').encode('ascii', 'ignore').decode().lower()
    return re.sub(r'[^a-z0-9 ]', ' ', s).strip()


def _names_match_raw(a, b):
    na, nb = _norm(a), _norm(b)
    if not na or not nb:
        return False
    if na == nb:
        return True
    return (na in nb or nb in na) and min(len(na), len(nb)) >= 5


def _names_match(a, b):
    """a: raw name from Polymarket US. b: our team name -- checked against every
    known variant (team_map key/fdco/aliases), not just the literal string."""
    return any(_names_match_raw(a, v) for v in _variants(b))


def league_events(league_slug: str, force=False) -> list:
    now = time.time()
    cached = _events_cache.get(league_slug)
    if cached and not force and now - cached[0] < _CACHE_TTL:
        return cached[1]
    events = []
    offset = 0
    while True:
        r = requests.get(f"{GATEWAY_BASE}/v2/leagues/{league_slug}/events",
                          params={'limit': 50, 'offset': offset}, timeout=15)
        if r.status_code != 200:
            break
        batch = r.json().get('events', [])
        events += batch
        if len(batch) < 50:
            break
        offset += 50
        if offset > 300:
            break
    _events_cache[league_slug] = (now, events)
    return events


def find_event(div: str, home: str, away: str) -> Optional[dict]:
    league_slug = LEAGUE_SLUG.get(div)
    if not league_slug:
        return None
    for e in league_events(league_slug):
        title = e.get('title', '')
        parts = re.split(r'\s+vs\.?\s+', title)
        if len(parts) != 2:
            continue
        eh, ea = parts
        if _names_match(eh, home) and _names_match(ea, away):
            return e
    return None


def find_market(div: str, home: str, away: str, side: str) -> Optional[dict]:
    """side: 'home', 'draw' or 'away'.

    Returns {'slug':..., 'price':..., 'event_title':...} or None if no
    confident match.

    DRAW SUPPORT ADDED 2026-08-22. This used to read

        team_side_name = home if side == 'home' else away

    so ANY side that was not exactly 'home' -- including 'draw' -- silently
    resolved to the AWAY market. Callers got a real slug and a real price back,
    just for the wrong outcome, with no error. Verified live: the draw and away
    lookups for one Argentine fixture both returned `...-gye`.

    Nothing in production was corrupted by it: live_monitor only ever passes
    home/away (xlow is home/away only), and mm_quoter_polyus explicitly works
    around the limitation. But a three-leg maker asking for a draw would have
    quoted one outcome twice and never quoted the draw at all -- collecting two
    legs of a book that can never complete, which is the worst state in the
    strategy.

    The draw market is the one whose Yes side carries NO team; team-win markets
    always have one. That is the same rule get_all_prices already uses, so the
    two functions now agree instead of disagreeing.

    Unknown side values now return None rather than falling through to away --
    the fallthrough was the actual defect, not the missing draw branch.
    """
    if side not in ('home', 'draw', 'away'):
        return None
    e = find_event(div, home, away)
    if not e:
        return None
    want_team = {'home': home, 'away': away}.get(side)
    for m in e.get('markets', []):
        yes_side = next((s for s in m.get('marketSides', []) if s.get('description') == 'Yes'), None)
        if not yes_side:
            continue
        team = (yes_side.get('team') or {}).get('name', '')
        hit = (not team) if side == 'draw' else (team and _names_match(team, want_team))
        if hit:
            return {'slug': m['slug'], 'event_title': e.get('title', ''),
                    'price': float(yes_side.get('price') or 0)}
    return None


def get_all_prices(game_date, home: str, away: str, league: str = None) -> Optional[dict]:
    """Same shape as polymarket_live.get_all_prices: {home_mid,home_bid,home_ask,
    draw_mid,draw_bid,draw_ask,away_mid,away_bid,away_ask}. One event lookup,
    three BBO calls. Draw market identified by its Yes-side having no team
    (team-win markets always have one)."""
    if not league:
        return None
    e = find_event(league, home, away)
    if not e:
        return None
    out = {}
    for m in e.get('markets', []):
        yes_side = next((s for s in m.get('marketSides', []) if s.get('description') == 'Yes'), None)
        if not yes_side:
            continue
        team = (yes_side.get('team') or {}).get('name', '')
        if not team:
            key = 'draw'
        elif _names_match(team, home):
            key = 'home'
        elif _names_match(team, away):
            key = 'away'
        else:
            continue
        bbo = get_bbo(m['slug'])
        if not bbo:
            continue
        out[f'{key}_bid'] = bbo.get('bestBid')
        out[f'{key}_ask'] = bbo.get('bestAsk')
        out[f'{key}_mid'] = bbo.get('currentPx') or bbo.get('bestAsk')
    return out if len(out) >= 4 else None   # require at least 2 sides quoted


def get_bbo(market_slug: str) -> Optional[dict]:
    r = requests.get(f"{GATEWAY_BASE}/v1/markets/{market_slug}/bbo", timeout=15)
    if r.status_code != 200:
        return None
    d = r.json().get('marketData', {})
    def _v(k):
        try: return float(d.get(k, {}).get('value'))
        except (TypeError, ValueError, AttributeError): return None
    return {'bestBid': _v('bestBid'), 'bestAsk': _v('bestAsk'), 'currentPx': _v('currentPx'),
            'askDepth': d.get('askDepth'), 'bidDepth': d.get('bidDepth')}
