#!/usr/bin/env python3
"""
Polymarket 15-Min Crypto Arbitrage Scanner
Scans active 15-minute crypto markets for arbitrage opportunities.
Alerts via Telegram when combined Up+Down cost < $1.00 (after fees).

Zero AI tokens — runs as pure Python via systemd timer.
"""

import json
import os
import sys
import time
import urllib.request
from datetime import datetime, timezone, timedelta
from pathlib import Path

# Config
DATA_DIR = Path(__file__).parent.parent / "data" / "arb-scanner"
DATA_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = DATA_DIR / "scan_log.json"
PAPER_TRADES_FILE = DATA_DIR / "paper_trades.json"

TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "6443752046")

# Polymarket fee formula for 15-min markets
def calc_taker_fee(shares, price):
    """Calculate taker fee in USDC."""
    if price <= 0 or price >= 1:
        return 0
    return shares * price * 0.25 * (price * (1 - price)) ** 2

def calc_fee_rate(price):
    """Effective fee rate at a given price."""
    if price <= 0 or price >= 1:
        return 0
    return 0.25 * (price * (1 - price)) ** 2


def get_active_15min_markets():
    """Fetch active 15-minute crypto markets from Polymarket."""
    markets = []
    
    # 15-min markets are scattered across pagination — scan broadly
    for offset in range(0, 3000, 200):
        url = (
            f"https://gamma-api.polymarket.com/markets?"
            f"active=true&closed=false&limit=200&offset={offset}"
            f"&order=volume&ascending=false"
        )
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        try:
            resp = urllib.request.urlopen(req, timeout=15)
            batch = json.loads(resp.read())
            for m in batch:
                q = m.get("question", "").lower()
                if "up or down" in q:
                    markets.append(m)
            if len(batch) < 200:
                break
        except Exception as e:
            print(f"Error fetching markets (offset={offset}): {e}")
            break
        time.sleep(0.1)
    
    # Only keep markets ending within the next 4 hours (tradeable window)
    now = datetime.now(timezone.utc)
    tradeable = []
    for m in markets:
        end_str = m.get("endDate", "")
        if not end_str:
            continue
        try:
            end_dt = datetime.fromisoformat(end_str.replace("Z", "+00:00"))
            hours_until = (end_dt - now).total_seconds() / 3600
            if 0.25 < hours_until <= 24:  # Skip markets < 15min to expiry (already resolved)
                m["_hours_until_end"] = round(hours_until, 2)
                tradeable.append(m)
        except:
            pass
    
    # Deduplicate
    seen = set()
    unique = []
    for m in tradeable:
        cid = m.get("conditionId", m.get("id", ""))
        if cid not in seen:
            seen.add(cid)
            unique.append(m)
    
    return unique


def get_orderbook_prices(token_id):
    """Get best bid/ask from the CLOB API."""
    url = f"https://clob.polymarket.com/book?token_id={token_id}"
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        book = json.loads(resp.read())
        bids = book.get("bids", [])
        asks = book.get("asks", [])
        best_bid = float(bids[0]["price"]) if bids else 0
        best_ask = float(asks[0]["price"]) if asks else 1
        bid_size = float(bids[0].get("size", 0)) if bids else 0
        ask_size = float(asks[0].get("size", 0)) if asks else 0
        return {
            "best_bid": best_bid,
            "best_ask": best_ask, 
            "bid_size": bid_size,
            "ask_size": ask_size,
            "spread": best_ask - best_bid
        }
    except Exception as e:
        return None


def scan_for_arbs():
    """Scan all active 15-min markets for arbitrage opportunities."""
    markets = get_active_15min_markets()
    print(f"Found {len(markets)} active 15-min crypto markets")
    
    opportunities = []
    
    for market in markets:
        question = market.get("question", market.get("title", ""))
        hours_left = market.get("_hours_until_end", "?")
        
        # Get token IDs for both outcomes
        tokens = market.get("clobTokenIds", "")
        if isinstance(tokens, str):
            try:
                tokens = json.loads(tokens) if tokens.startswith("[") else tokens.split(",")
            except:
                tokens = []
        
        if len(tokens) < 2:
            continue
        
        # Get orderbook for both tokens (ask = price to buy)
        book_up = get_orderbook_prices(tokens[0])
        book_down = get_orderbook_prices(tokens[1])
        time.sleep(0.15)
        
        if not book_up or not book_down:
            continue
        
        # For arb: we BUY both sides at the ASK price
        up_ask = book_up["best_ask"]
        down_ask = book_down["best_ask"]
        combined = up_ask + down_ask
        
        # Calculate fees on 100 shares
        fee_up = calc_taker_fee(100, up_ask)
        fee_down = calc_taker_fee(100, down_ask)
        total_cost_100 = (up_ask + down_ask) * 100 + fee_up + fee_down
        net_profit_100 = 100 - total_cost_100
        net_profit_pct = net_profit_100 / total_cost_100 * 100 if total_cost_100 > 0 else 0
        
        # Fillable size (limited by smaller side)
        fillable_size = min(book_up["ask_size"], book_down["ask_size"])
        if fillable_size > 0:
            fill_fee_up = calc_taker_fee(fillable_size, up_ask)
            fill_fee_down = calc_taker_fee(fillable_size, down_ask)
            fill_cost = (up_ask + down_ask) * fillable_size + fill_fee_up + fill_fee_down
            fill_profit = fillable_size - fill_cost
        else:
            fill_profit = 0
        
        opp = {
            "question": question,
            "hours_left": hours_left,
            "up_ask": up_ask,
            "down_ask": down_ask,
            "up_ask_size": book_up["ask_size"],
            "down_ask_size": book_down["ask_size"],
            "combined": round(combined, 4),
            "fee_up_per_100": round(fee_up, 4),
            "fee_down_per_100": round(fee_down, 4),
            "total_fees_per_100": round(fee_up + fee_down, 4),
            "net_profit_per_100": round(net_profit_100, 2),
            "net_profit_pct": round(net_profit_pct, 2),
            "fillable_shares": fillable_size,
            "fillable_profit": round(fill_profit, 2),
            "is_arb": net_profit_100 > 0,
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }
        opportunities.append(opp)
    
    return opportunities


def paper_trade(opp):
    """Record a paper trade for an arb opportunity."""
    trades = []
    if PAPER_TRADES_FILE.exists():
        trades = json.loads(PAPER_TRADES_FILE.read_text())
    
    trade = {
        "id": len(trades) + 1,
        "timestamp": opp["timestamp"],
        "question": opp["question"],
        "up_price": opp.get("up_ask", opp.get("up_price", 0)),
        "down_price": opp.get("down_ask", opp.get("down_price", 0)),
        "combined": opp["combined"],
        "fees_per_100": opp["total_fees_per_100"],
        "net_profit_per_100": opp["net_profit_per_100"],
        "net_profit_pct": opp["net_profit_pct"],
        "status": "open",  # Will be "won" when market resolves (always wins if real arb)
        "paper_size_usd": 50,  # Paper trade $50 per arb
    }
    
    expected_profit = 50 * opp["net_profit_pct"] / 100
    trade["expected_profit_usd"] = round(expected_profit, 2)
    
    trades.append(trade)
    PAPER_TRADES_FILE.write_text(json.dumps(trades, indent=2))
    return trade


def send_telegram_alert(message):
    """Send alert via Telegram bot API (zero tokens)."""
    if not TELEGRAM_BOT_TOKEN:
        print(f"[ALERT] {message}")
        return
    
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    data = json.dumps({
        "chat_id": TELEGRAM_CHAT_ID,
        "text": message,
        "parse_mode": "HTML"
    }).encode()
    req = urllib.request.Request(url, data=data, headers={
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0"
    })
    try:
        urllib.request.urlopen(req, timeout=10)
    except Exception as e:
        print(f"Telegram alert failed: {e}")


def main():
    print(f"=== Polymarket 15-Min Arb Scanner ===")
    print(f"Time: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
    print()
    
    opps = scan_for_arbs()
    
    arbs = [o for o in opps if o["is_arb"]]
    non_arbs = [o for o in opps if not o["is_arb"]]
    
    print(f"\nResults: {len(opps)} markets scanned, {len(arbs)} arb opportunities\n")
    
    for o in sorted(opps, key=lambda x: x.get("net_profit_pct", 0), reverse=True):
        emoji = "✅" if o["is_arb"] else "❌"
        print(f"{emoji} {o['question'][:65]}")
        up = o.get('up_ask', o.get('up_price', '?'))
        down = o.get('down_ask', o.get('down_price', '?'))
        print(f"   Up: ${up} | Down: ${down} | Combined: ${o['combined']}")
        print(f"   Fees/100: ${o['total_fees_per_100']} | Net profit/100: ${o['net_profit_per_100']} ({o['net_profit_pct']}%)")
        if o.get('fillable_shares'):
            print(f"   Fillable: {o['fillable_shares']:.0f} shares | Fillable profit: ${o.get('fillable_profit', '?')}")
        print()
    
    # Paper trade any arbs found
    for arb in arbs:
        trade = paper_trade(arb)
        print(f"📝 Paper trade #{trade['id']}: {trade['question'][:50]} | Expected: +${trade['expected_profit_usd']}")
        
        # Send Telegram alert
        msg = (
            f"🔔 <b>Arb Found!</b>\n\n"
            f"<b>{arb['question']}</b>\n"
            f"Up: ${arb.get('up_ask', arb.get('up_price', '?'))} | "
            f"Down: ${arb.get('down_ask', arb.get('down_price', '?'))}\n"
            f"Combined: ${arb['combined']} (after fees)\n"
            f"Net profit: {arb['net_profit_pct']}%\n\n"
            f"📝 Paper traded $50 → expected +${trade['expected_profit_usd']}"
        )
        send_telegram_alert(msg)
    
    # Save scan log
    log = []
    if LOG_FILE.exists():
        try:
            log = json.loads(LOG_FILE.read_text())
        except:
            pass
    
    log.append({
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "markets_scanned": len(opps),
        "arbs_found": len(arbs),
        "opportunities": opps,
    })
    
    # Keep last 1000 scans
    log = log[-1000:]
    LOG_FILE.write_text(json.dumps(log, indent=2))
    
    # Summary of paper trades
    if PAPER_TRADES_FILE.exists():
        trades = json.loads(PAPER_TRADES_FILE.read_text())
        total_expected = sum(t.get("expected_profit_usd", 0) for t in trades)
        print(f"\n📊 Paper trade total: {len(trades)} trades, expected profit: ${total_expected:.2f}")


if __name__ == "__main__":
    main()
