"""
Kalshi live price checker — all supported European + MLS leagues.
At halftime, call check_halftime() to get the current away-win price
and whether there's edge to buy more contracts.
"""
import requests, re, time
from datetime import date
import maps

BASE = 'https://api.elections.kalshi.com/trade-api/v2'

# Series tickers and team abbreviations are loaded from maps.py (league_map.json / team_map.json).
# Edit those JSON files to add/change mappings — no restart needed for live_monitor.

COMEBACK_RATE = 0.114  # historical away win rate when down 1 at HT

def get_abr(team_name):
    """Get Kalshi abbreviation for a team. Reads from team_map.json via maps module."""
    return maps.kalshi_abr(team_name)

def _all_kalshi_series():
    """Return list of all unique Kalshi series tickers across all leagues."""
    return list(set(filter(None, [maps.league_kalshi_series(d) for d in maps.all_divs()])))

def _series_to_div():
    """Return reverse mapping: series ticker -> div code."""
    return {maps.league_kalshi_series(d): d for d in maps.all_divs() if maps.league_kalshi_series(d)}

def build_event_ticker(series, game_date, home_abr, away_abr):
    month = game_date.strftime('%b').upper()
    day   = game_date.strftime('%d')
    year  = game_date.strftime('%y')
    return f"{series}-{year}{month}{day}{home_abr}{away_abr}"


def find_event_by_abrs(series, home_abr, away_abr):
    """
    Search the entire series for an active event whose ticker ends with the two
    team abbreviations in either order, regardless of date.

    Useful when the game has been rescheduled in API-Football but Kalshi still
    has the event under its original date ticker.

    Returns the event_ticker string, or None.
    """
    if not (series and home_abr and away_abr):
        return None
    # Pattern: SERIES-DDMMMYY<ABR1><ABR2>
    pat = re.compile(rf'^{re.escape(series)}-\d{{2}}[A-Z]{{3}}\d{{2}}([A-Z0-9]+)$')
    targets = {(home_abr, away_abr), (away_abr, home_abr)}
    # Check open first, then unopened/closed as a last resort
    for status in ('open', 'unopened', 'closed'):
        try:
            # STATUS CODE MUST BE CHECKED. Without it a 429 -- which this
            # endpoint returns readily -- falls through as
            # r.json().get('events', []) == [] and this function returns None,
            # which the caller cannot tell apart from "the event does not
            # exist". That is how a live market got reported unresolvable on
            # 2026-09-11. Retry a transient answer before giving up.
            r = None
            for attempt in range(3):
                r = requests.get(f'{BASE}/events',
                                 params={'series_ticker': series, 'limit': 200,
                                         'status': status},
                                 timeout=10)
                if r.status_code == 200:
                    break
                time.sleep(1.5 * (attempt + 1))
            if r is None or r.status_code != 200:
                continue
            for ev in r.json().get('events', []):
                et = ev.get('event_ticker', '')
                m = pat.match(et)
                if not m:
                    continue
                rest = m.group(1)
                # rest is the concatenation of home_abr + away_abr. Try splitting at
                # every position to find a valid split matching our targets.
                for split in range(1, len(rest)):
                    a, b = rest[:split], rest[split:]
                    if (a, b) in targets:
                        return et
        except Exception:
            continue
    return None

def get_all_prices(series, game_date, home_team, away_team):
    """Fetch home, draw, and away prices from Kalshi. Returns dict or None."""
    home_abr = get_abr(home_team)
    away_abr = get_abr(away_team)

    # Discover event ticker (try both orderings)
    event = None
    if home_abr and away_abr:
        e1 = build_event_ticker(series, game_date, home_abr, away_abr)
        r = requests.get(f'{BASE}/markets/{e1}-{away_abr}', timeout=10)
        if r.status_code == 200:
            event = e1
        else:
            e2 = build_event_ticker(series, game_date, away_abr, home_abr)
            r = requests.get(f'{BASE}/markets/{e2}-{away_abr}', timeout=10)
            if r.status_code == 200:
                event = e2

    if not event:
        # Date-based ticker missed (e.g. rescheduled game). Search the series for
        # any event whose ticker pairs these two abbreviations, regardless of date.
        event = find_event_by_abrs(series, home_abr, away_abr)

    if not event:
        # Last-resort: title-based search across markets
        r = requests.get(f'{BASE}/markets', params={'series_ticker': series, 'limit': 200, 'status': 'open'}, timeout=10)
        for m in r.json().get('markets', []):
            title = m.get('title', '')
            if home_team.lower() in title.lower() and away_team.lower() in title.lower():
                t = m['ticker']
                match = re.match(rf'({re.escape(series)}-\d+[A-Z]+\d+[A-Z]+)-[A-Z]+$', t)
                if match:
                    event = match.group(1)
                    break

    if not event:
        return None

    result = {}
    for outcome, label in [(away_abr, 'away'), (home_abr, 'home'), ('TIE', 'draw')]:
        ticker = f"{event}-{outcome}"
        r = requests.get(f'{BASE}/markets/{ticker}', timeout=10)
        if r.status_code == 200:
            m = r.json().get('market', r.json())
            ask = float(m.get('yes_ask_dollars', 0) or 0)
            bid = float(m.get('yes_bid_dollars', 0) or 0)
            mid = (ask + bid) / 2 if bid > 0 else ask
            result[f'{label}_ask'] = round(ask, 4)
            result[f'{label}_bid'] = round(bid, 4)
            result[f'{label}_mid'] = round(mid, 4)

    return result if result else None


def get_market_price(series, game_date, home_team, away_team):
    home_abr = get_abr(home_team)
    away_abr = get_abr(away_team)

    if not home_abr or not away_abr:
        # Try to discover from API
        r = requests.get(f'{BASE}/markets', params={'series_ticker': series, 'limit': 200, 'status': 'open'}, timeout=10)
        for m in r.json().get('markets', []):
            title = m.get('title', '')
            if home_team.lower() in title.lower() and away_team.lower() in title.lower():
                ticker = m['ticker']
                # Extract away abr from ticker
                t_match = re.match(rf'{series}-\d+[A-Z]+\d+([A-Z]+)-([A-Z]+)$', ticker)
                if t_match and t_match.group(2) != 'TIE':
                    return _fetch_price(ticker)
        print(f"  Could not find abbreviation: {home_team}→{home_abr}, {away_team}→{away_abr}")
        return None

    event = build_event_ticker(series, game_date, home_abr, away_abr)
    away_ticker = f"{event}-{away_abr}"

    r = requests.get(f'{BASE}/markets/{away_ticker}', timeout=10)
    if r.status_code == 404:
        # Try reversed order
        event2 = build_event_ticker(series, game_date, away_abr, home_abr)
        away_ticker = f"{event2}-{away_abr}"
        r = requests.get(f'{BASE}/markets/{away_ticker}', timeout=10)
    if r.status_code == 404:
        # Date-based ticker missed — fall back to series-wide abbreviation search.
        found = find_event_by_abrs(series, home_abr, away_abr)
        if found:
            away_ticker = f"{found}-{away_abr}"
            r = requests.get(f'{BASE}/markets/{away_ticker}', timeout=10)
    if r.status_code != 200:
        print(f"  Market not found: {away_ticker}")
        return None

    return _fetch_price(away_ticker, r.json())

def _fetch_price(ticker, data=None):
    if data is None:
        r = requests.get(f'{BASE}/markets/{ticker}', timeout=10)
        data = r.json()
    m = data.get('market', data)
    yes_ask = float(m.get('yes_ask_dollars', 0) or 0)
    yes_bid = float(m.get('yes_bid_dollars', 0) or 0)
    mid = (yes_ask + yes_bid) / 2 if yes_bid > 0 else yes_ask
    return {'ticker': ticker, 'yes_ask': yes_ask, 'yes_bid': yes_bid, 'mid': mid,
            'title': m.get('title',''), 'status': m.get('status','')}

def check_halftime(game_date, home_team, away_team, ht_score_home, ht_score_away, league_code='E0'):
    """Prints HT analysis and returns dict with kalshi price data (or None)."""
    deficit = ht_score_away - ht_score_home
    series = maps.league_kalshi_series(league_code.upper()) or 'KXEPLGAME'

    print(f"\n[{league_code}] {home_team} {ht_score_home}-{ht_score_away} {away_team} at HT")

    if deficit > 0:
        print(f"  Away team winning — HOLD. No action needed.")
        return {'signal': 'HOLD_WINNING', 'kalshi_price': None, 'kalshi_bid': None, 'kalshi_ask': None, 'edge': None}
    if deficit == 0:
        print(f"  Level at HT — HOLD.")
        return {'signal': 'HOLD_LEVEL', 'kalshi_price': None, 'kalshi_bid': None, 'kalshi_ask': None, 'edge': None}

    result = get_market_price(series, game_date, home_team, away_team)
    if not result:
        return None

    price = result['mid'] if result['mid'] > 0 else result['yes_ask']
    if price <= 0:
        print(f"  No price available (market may not be live yet)")
        return None

    edge = COMEBACK_RATE - price
    has_edge = price < COMEBACK_RATE

    print(f"  Market:   {result['ticker']}")
    print(f"  Price:    {price*100:.1f}%  (bid {result['yes_bid']*100:.1f}% / ask {result['yes_ask']*100:.1f}%)")
    print(f"  Historical comeback rate (down {abs(deficit)}): {COMEBACK_RATE*100:.1f}%")
    print(f"  Edge:     {edge*100:+.1f}%  ->  {'[BUY MORE]' if has_edge else 'No edge - hold original only'}")

    if has_edge:
        b = 1/price - 1
        k = max((COMEBACK_RATE * b - (1 - COMEBACK_RATE)) / b * 0.5, 0)
        print(f"  -> Add {k*100:.1f}% of bankroll at {price*100:.1f}c  (fair value {COMEBACK_RATE*100:.1f}c)")
        print(f"  -> EV on additional bet: {edge/price*100:.0f}% return on investment")

    return {
        'signal':       'BUY' if has_edge else 'HOLD',
        'kalshi_price': round(price, 4),
        'kalshi_bid':   round(result['yes_bid'], 4),
        'kalshi_ask':   round(result['yes_ask'], 4),
        'edge':         round(edge, 4),
    }


def list_matches(target_date=None):
    """List all matches on a given date across all leagues."""
    maps.reload()
    if target_date is None:
        target_date = date.today()
    date_str = target_date.strftime('%y%b%d').upper()
    all_series = _all_kalshi_series()
    series_div = _series_to_div()
    seen_events = set()
    games = []
    # READS /events, NOT /markets. Until 2026-09-15 this walked markets and
    # parsed '<home> vs <away> Winner' out of market titles. Kalshi changed
    # those titles to per-outcome text ('Celtic wins', 'Tie is the result'),
    # so the regex stopped matching and this returned ZERO games for every
    # date -- silently, because an empty result looks exactly like a day with
    # no fixtures. It read "0 with Kalshi coverage" in daily_watchlist for 40
    # consecutive builds before anyone noticed. The event carries the pairing
    # as its own title ('Celtic vs Rangers'), which is what we actually want
    # and is one object per match instead of three.
    for series in all_series:
        cursor = ''
        for _ in range(20):          # page cap: never spin on a repeating cursor
            params = {'series_ticker': series, 'limit': 200}
            if cursor:
                params['cursor'] = cursor
            r = requests.get(f'{BASE}/events', params=params, timeout=10)
            payload = r.json()
            for e in payload.get('events', []) or []:
                ev = e.get('event_ticker', '')
                if date_str not in ev: continue
                if ev in seen_events: continue
                seen_events.add(ev)
                match = re.match(r'(.+?) vs (.+)$', (e.get('title') or '').strip())
                if match:
                    games.append((series, match.group(1).strip(), match.group(2).strip()))
            nxt = payload.get('cursor') or ''
            if not nxt or nxt == cursor:
                break
            cursor = nxt

    print(f"\nMatches on {target_date} ({len(games)} games):")
    cur_series = None
    for series, home, away in sorted(games, key=lambda x: x[0]):
        if series != cur_series:
            league = series_div.get(series, series)
            print(f"\n  [{league}]")
            cur_series = series
        print(f"    {home} vs {away}")
    return games


# ── Example ────────────────────────────────────────────────────────────────
if __name__ == '__main__':
    from datetime import timedelta
    # Show tomorrow's matches
    list_matches(date.today() + timedelta(days=1))
