#!/usr/bin/env python3
"""
New Market Notifier — Telegram bot that monitors Kalshi for new esports
and political mentions markets. Sends alerts when new events appear and
supports /today to list upcoming events in the next 24 hours.

Uses the LuckySt BTC RSI bot telegram credentials.
Uses mom's Kalshi API credentials.

Efficient polling: only calls GET /events per series each cycle (9 calls).
Only calls GET /events/{ticker} when a NEW event is discovered.
All event details are cached for /today lookups.

Usage:
    python new_market.py
"""

import base64
import os
import time
import requests
import threading
import concurrent.futures
from datetime import datetime, timezone, timedelta
from dotenv import load_dotenv

load_dotenv()

# ============================================================
# KALSHI API
# ============================================================

KALSHI_API_KEY = os.environ.get("KALSHI_MOM_API_KEY", "")
KALSHI_API_SECRET = os.environ.get("KALSHI_MOM_API_SECRET", "")
KALSHI_API_BASE = "https://api.elections.kalshi.com/trade-api/v2"

# Telegram — uses the BTC RSI bot
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")

# ============================================================
# SERIES TO MONITOR
# ============================================================

ESPORTS_SERIES = [
    "KXCODGAME",        # Call of Duty
    "KXLOLGAME",        # League of Legends
    "KXCS2GAME",        # CS2
    "KXVALORANTGAME",   # Valorant
    "KXR6GAME",         # Rainbow Six
    "KXDOTA2GAME",      # Dota 2
    "KXOWGAME",         # Overwatch
]

MENTIONS_SERIES = [
    "KXTRUMPMENTIONB",  # Trump Mentions
    "KXTRUMPMENTION",   # Trump Mentions (alt)
]

SERIES_NAMES = {
    "KXCODGAME": "Call of Duty",
    "KXLOLGAME": "League of Legends",
    "KXCS2GAME": "CS2",
    "KXVALORANTGAME": "Valorant",
    "KXR6GAME": "Rainbow Six",
    "KXDOTA2GAME": "Dota 2",
    "KXOWGAME": "Overwatch",
    "KXTRUMPMENTIONB": "Trump Mentions",
    "KXTRUMPMENTION": "Trump Mentions",
}

ALL_SERIES = ESPORTS_SERIES + MENTIONS_SERIES

# No cooldown between cycles — series are staggered 1/sec internally

# ============================================================
# KALSHI AUTH
# ============================================================

_private_key = None


def _load_private_key():
    secret = KALSHI_API_SECRET
    if not secret:
        return None
    try:
        from cryptography.hazmat.primitives import serialization
        from cryptography.hazmat.backends import default_backend
        key_data = open(secret, 'r').read() if os.path.isfile(secret) else secret
        return serialization.load_pem_private_key(
            key_data.encode() if isinstance(key_data, str) else key_data,
            password=None, backend=default_backend()
        )
    except Exception as e:
        print(f"Key load failed: {e}")
        return None


def _sign_request(timestamp: str, method: str, path: str) -> str:
    if not _private_key:
        return ""
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.asymmetric import padding
    path_clean = path.split('?')[0]
    msg = timestamp + method + "/trade-api/v2" + path_clean
    sig = _private_key.sign(
        msg.encode(),
        padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                     salt_length=padding.PSS.DIGEST_LENGTH),
        hashes.SHA256()
    )
    return base64.b64encode(sig).decode()


def kalshi_get(endpoint: str, params: dict = None) -> dict:
    ts = str(int(time.time() * 1000))
    sig = _sign_request(ts, "GET", endpoint)
    headers = {
        "KALSHI-ACCESS-KEY": KALSHI_API_KEY,
        "KALSHI-ACCESS-SIGNATURE": sig,
        "KALSHI-ACCESS-TIMESTAMP": ts,
    }
    try:
        r = requests.get(KALSHI_API_BASE + endpoint, headers=headers, params=params, timeout=15)
        if r.status_code == 200:
            return r.json()
        return {}
    except Exception:
        return {}


# ============================================================
# TELEGRAM
# ============================================================

_tg_pool = concurrent.futures.ThreadPoolExecutor(max_workers=2, thread_name_prefix="tg")
_tg_base_url = ""


def tg_send(text: str):
    def _do():
        try:
            requests.post(
                f"{_tg_base_url}/sendMessage",
                json={"chat_id": TELEGRAM_CHAT_ID, "text": text, "parse_mode": "HTML"},
                timeout=5,
            )
        except Exception:
            pass
    _tg_pool.submit(_do)


# ============================================================
# EVENT CACHE + HELPERS
# ============================================================

# Cache: event_ticker -> event detail dict (populated on new events + /today)
event_cache: dict[str, dict] = {}
# Known tickers: event_ticker -> series (populated on initial scan, no detail fetch)
known_tickers: dict[str, str] = {}


def scan_event_tickers(series_list: list[str], stagger: bool = True) -> dict[str, list[str]]:
    """Cheap scan: only GET /events per series, return {series: [event_tickers]}.
    Staggers 1 request per second across series to spread API load."""
    result = {}
    for i, series in enumerate(series_list):
        if stagger and i > 0:
            time.sleep(1)
        tickers = []
        cursor = None
        while True:
            params = {"series_ticker": series, "status": "open", "limit": 200}
            if cursor:
                params["cursor"] = cursor
            data = kalshi_get("/events", params)
            events = data.get("events", [])
            cursor = data.get("cursor")

            for event in events:
                tickers.append(event.get("event_ticker", ""))

            if not cursor or not events:
                break
        result[series] = tickers
    return result


def fetch_event_detail(event_ticker: str, series: str) -> dict | None:
    """Fetch full event detail (markets, timing). Only called for NEW events."""
    ev_detail = kalshi_get(f"/events/{event_ticker}")
    if not ev_detail:
        return None

    event = ev_detail.get("event", ev_detail)
    title = event.get("title", event_ticker)
    markets = ev_detail.get("markets", [])

    if not markets:
        return None

    m = markets[0]
    exp_str = m.get("expected_expiration_time") or m.get("close_time", "")
    if not exp_str:
        return None

    try:
        exp_dt = datetime.fromisoformat(exp_str.replace("Z", "+00:00"))
    except (ValueError, TypeError):
        return None

    exp_et = exp_dt + timedelta(hours=+16)
    market_tickers = [m2.get("ticker", "") for m2 in markets]
    category = "mentions" if series in MENTIONS_SERIES else "esports"

    # Sum volume across all markets in this event
    total_volume = sum(int(m2.get("volume", 0)) for m2 in markets)

    return {
        "series": series,
        "series_name": SERIES_NAMES.get(series, series),
        "category": category,
        "event_ticker": event_ticker,
        "title": title,
        "event_time": exp_et,
        "event_time_utc": exp_dt,
        "markets": market_tickers,
        "volume": total_volume,
    }


def format_event(ev: dict) -> str:
    """Format a single event for telegram display."""
    t = ev["event_time"].strftime("%I:%M %p ET")
    d = ev["event_time"].strftime("%m/%d")
    markets_str = "\n".join(f"  <code>{m}</code>" for m in ev["markets"][:6])
    tag = "🎮" if ev["category"] == "esports" else "📢"
    vol = ev.get("volume", 0)
    vol_str = f"${vol:,}" if vol else "$0"
    return (
        f"{tag} <b>{ev['series_name']}</b>\n"
        f"📅 {d} at {t}\n"
        f"🏷 {ev['title']}\n"
        f"📊 Volume: {vol_str}\n"
        f"Markets:\n{markets_str}"
    )


# ============================================================
# /today COMMAND — uses cache, supports filters
# ============================================================

# Filter shortcuts -> series tickers
TODAY_FILTERS = {
    "lol":      ["KXLOLGAME"],
    "cs":       ["KXCS2GAME"],
    "cod":      ["KXCODGAME"],
    "valo":     ["KXVALORANTGAME"],
    "ow":       ["KXOWGAME"],
    "dota":     ["KXDOTA2GAME"],
    "r6":       ["KXR6GAME"],
    "mentions": MENTIONS_SERIES,
}


def handle_today(filter_name: str = None):
    """Send list of events in next 24 hours, optionally filtered.
    Fetches details for any events not yet in cache."""
    now_utc = datetime.now(timezone.utc)
    cutoff_utc = now_utc + timedelta(hours=24)

    # Determine which series to include
    if filter_name and filter_name in TODAY_FILTERS:
        allowed_series = set(TODAY_FILTERS[filter_name])
        label = filter_name.upper()
    else:
        allowed_series = None  # all
        label = "All"

    tg_send(f"🔍 Scanning events ({label})...")

    # Fetch details for any known tickers not yet in cache
    for ticker, series in list(known_tickers.items()):
        if ticker not in event_cache:
            detail = fetch_event_detail(ticker, series)
            if detail:
                event_cache[ticker] = detail

    upcoming = [
        e for e in event_cache.values()
        if now_utc <= e["event_time_utc"] <= cutoff_utc
        and (allowed_series is None or e["series"] in allowed_series)
    ]
    upcoming.sort(key=lambda e: e["event_time"])

    if not upcoming:
        tg_send(f"📭 No {label} events in the next 24 hours.")
        return

    esports = [e for e in upcoming if e["category"] == "esports"]
    mentions = [e for e in upcoming if e["category"] == "mentions"]

    lines = [f"📋 <b>Events — Next 24 Hours ({label})</b> ({len(upcoming)} total)\n"]

    if esports:
        lines.append(f"━━ 🎮 <b>ESPORTS ({len(esports)})</b> ━━")
        for ev in esports:
            t = ev["event_time"].strftime("%I:%M %p")
            vol = ev.get("volume", 0)
            vol_str = f"${vol:,}" if vol else "$0"
            lines.append(f"  {t} — {ev['series_name']}: {ev['title']} | {vol_str} vol")
        lines.append("")

    if mentions:
        lines.append(f"━━ 📢 <b>MENTIONS ({len(mentions)})</b> ━━")
        for ev in mentions:
            t = ev["event_time"].strftime("%I:%M %p")
            vol = ev.get("volume", 0)
            vol_str = f"${vol:,}" if vol else "$0"
            lines.append(f"  {t} — {ev['series_name']}: {ev['title']} | {vol_str} vol")
        lines.append("")

    tg_send("\n".join(lines))


# ============================================================
# NEW MARKET MONITOR
# ============================================================

def monitor_new_markets():
    """Main loop: poll series for event tickers (cheap), only fetch
    details when a new event is discovered."""

    # Initial scan — just record known tickers (no detail fetch = fast)
    print("Initial scan...")
    global known_tickers
    all_tickers = scan_event_tickers(ALL_SERIES)
    esports_count = 0
    mentions_count = 0
    for series, tickers in all_tickers.items():
        for ticker in tickers:
            known_tickers[ticker] = series
            if series in MENTIONS_SERIES:
                mentions_count += 1
            else:
                esports_count += 1

    init_count = len(known_tickers)
    print(f"Found {init_count} events ({esports_count} esports, {mentions_count} mentions)")
    tg_send(
        f"🟢 <b>Market Notifier Started</b>\n"
        f"Monitoring {len(ESPORTS_SERIES)} esports + {len(MENTIONS_SERIES)} mentions series\n"
        f"Tracking {init_count} events ({esports_count} esports, {mentions_count} mentions)\n"
        f"Continuous scan: 1 series/sec ({len(ALL_SERIES)}s per cycle)"
    )

    while True:
        try:
            # Cheap scan: 1 GET /events per series = 9 API calls
            all_tickers = scan_event_tickers(ALL_SERIES)
            current_all: dict[str, str] = {}  # ticker -> series

            for series, tickers in all_tickers.items():
                for ticker in tickers:
                    current_all[ticker] = series

                    if ticker not in known_tickers:
                        # New event — fetch details (1 extra API call)
                        known_tickers[ticker] = series
                        detail = fetch_event_detail(ticker, series)
                        if detail:
                            event_cache[ticker] = detail
                            msg = f"🆕 <b>NEW MARKET</b>\n\n{format_event(detail)}"
                            tg_send(msg)
                            print(f"New: {detail['title']}")

            # Remove expired events
            expired = set(known_tickers.keys()) - set(current_all.keys())
            for ticker in expired:
                known_tickers.pop(ticker, None)
                event_cache.pop(ticker, None)

        except Exception as e:
            print(f"Monitor error: {e}")


# ============================================================
# TELEGRAM COMMAND LISTENER
# ============================================================

def start_command_listener():
    """Listen for /today and /help commands on telegram."""
    last_update_id = 0
    base_url = _tg_base_url

    def poll():
        nonlocal last_update_id
        while True:
            try:
                resp = requests.get(
                    f"{base_url}/getUpdates",
                    params={"offset": last_update_id + 1, "timeout": 10},
                    timeout=15,
                )
                if resp.status_code == 200:
                    for update in resp.json().get("result", []):
                        last_update_id = update["update_id"]
                        msg = update.get("message", {})
                        text = msg.get("text", "").strip().lower()
                        if text == "/today":
                            handle_today()
                        elif text.startswith("/today_"):
                            filter_name = text.split("/today_", 1)[1]
                            if filter_name in TODAY_FILTERS:
                                handle_today(filter_name)
                            else:
                                tg_send(f"Unknown filter: {filter_name}\nAvailable: {', '.join(TODAY_FILTERS.keys())}")
                        elif text == "/help":
                            filters = ", ".join(f"/today_{f}" for f in TODAY_FILTERS)
                            tg_send(
                                "📖 <b>Commands</b>\n"
                                "/today — All events in next 24 hours\n"
                                f"{filters}\n"
                                "/help — Show this message"
                            )
            except Exception:
                time.sleep(5)

    t = threading.Thread(target=poll, daemon=True)
    t.start()


# ============================================================
# MAIN
# ============================================================

def main():
    global _private_key, _tg_base_url

    _private_key = _load_private_key()
    _tg_base_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"

    if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
        print("Missing TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID in .env")
        return
    if not _private_key:
        print("Missing KALSHI_MOM_API_KEY / KALSHI_MOM_API_SECRET in .env")
        return

    print(f"\n{'='*50}")
    print(f"  NEW MARKET NOTIFIER")
    print(f"{'='*50}")
    print(f"  Esports:  {', '.join(ESPORTS_SERIES)}")
    print(f"  Mentions: {', '.join(MENTIONS_SERIES)}")
    print(f"  Scan:     continuous, 1 series/sec ({len(ALL_SERIES)}s per cycle)")
    print(f"  Telegram: BTC RSI bot")
    print(f"{'='*50}\n")

    start_command_listener()
    monitor_new_markets()


if __name__ == "__main__":
    main()
