#!/usr/bin/env python3
"""exposure.py  (4 Oct 2026)  "Is this coin everywhere on crypto X right now?"

Measures EXPOSURE, not talk: the views of real posts with 50+ likes about the coin (CA or $TICKER),
in 2-day windows over the last 30 days, through the shared xsafe spam filter.

Rule (tested on 75 coins, RH + Solana + OKX listed, Oct 2026, see memory macro-direction v8):
  stretch = 3-day avg views / avg views of the 14 days before (skipping the last 3)
  EXPOSURE PEAK PASSED  -> stretch was 2x+ in the last 5 days and is now 30%+ below that high  -> SELL SOME
  EXPOSURE STRETCHED    -> stretch 2x+ right now                                              -> top zone, get ready to sell
  NORMAL                -> otherwise
  Receipts: peak-passed fired within 5 days of the price top 56% of the time (random day 41%), BEFORE or
  at the top 48% (price-stretch rule 9%), at a median 26% under the ATH (price rule 41%), both coin halves.
  Also reports new 100k+ follower accounts posting in the last 7 days (KOL cohort; its peak sits in the
  15 days before the top on 57% of coins).

Usage: python3 exposure.py --ca <contract> --ticker AI [--chain robinhood|solana] [--generic]
       --generic adds chain words to the ticker search (for tickers like AI, UP, MEME).
Cost: about 15 pages (~3k credits) per coin.
"""
import sys, json, time, argparse, datetime as dt
sys.path.insert(0, '/opt/common/xsafe'); import xsafe as xs
CHW = {'robinhood': '(robinhood OR rh OR long OR pons OR fomo)', 'solana': '(solana OR sol OR pump OR memecoin OR bonk OR fomo)'}
P = lambda s: dt.datetime.strptime(s, "%a %b %d %H:%M:%S %z %Y").timestamp()

def run(ca, ticker, chain='robinhood', generic=False, days=30):
    tq = f'(${ticker.upper()} {CHW.get(chain, "")})' if generic else f'${ticker.upper()}'
    q = (f'("{ca}" OR {tq})' if ca else tq) + ' min_faves:50 -filter:retweets'
    now = int(time.time()); start = now - days * 86400
    wins = []; seen = set(); a = start
    while a < now:
        b = min(a + 2 * 86400, now)
        r = xs.search(q + f' since_time:{a} until_time:{b}', pages=1)
        ps = r['real']; ts = [P(t['createdAt']) for t in r['posts']]
        w = (b - a) / max(3600, b - min(ts)) if (r['has_more'] and ts) else 1.0
        newk = {(t.get('author') or {}).get('userName') for t in ps if ((t.get('author') or {}).get('followers') or 0) >= 100000} - seen
        for t in ps: seen.add((t.get('author') or {}).get('userName'))
        wins.append(dict(a=a, b=b, views=sum((t.get('viewCount') or 0) for t in ps) * w / ((b - a) / 86400), posts=len(ps) * w, new_kol100k=len(newk)))
        a = b
    # daily series (each window spread over its days)
    daily = []
    for x in wins:
        for d in range(int((x['b'] - x['a']) // 86400) or 1): daily.append(x['views'])
    v = daily
    def avg(l): l = [y for y in l if y is not None]; return sum(l) / len(l) if l else 0
    stretch = []
    for i in range(len(v)):
        base = avg(v[max(0, i - 16):max(0, i - 2)]) if i >= 8 else None
        stretch.append(avg(v[max(0, i - 2):i + 1]) / base if base else None)
    s_now = stretch[-1] or 0; s5 = max([s for s in stretch[-5:] if s] or [0])
    if s5 >= 2 and s_now < 0.7 * s5: state, act = 'EXPOSURE PEAK PASSED', 'SELL SOME'
    elif s_now >= 2: state, act = 'EXPOSURE STRETCHED', 'TOP ZONE, GET READY TO SELL'
    else: state, act = 'NORMAL', 'NO SIGNAL'
    kol7 = sum(x['new_kol100k'] for x in wins[-4:])
    return dict(query=q, state=state, action=act, stretch_now=round(s_now, 2), stretch_max5d=round(s5, 2),
                views_per_day_last3=round(avg(v[-3:])), new_100k_accounts_last7d=kol7,
                windows=[dict(day=dt.datetime.fromtimestamp(x['a'] + 7 * 3600, dt.timezone.utc).strftime('%d %b'), views_per_day=round(x['views']), big_posts=round(x['posts'], 1), new_100k=x['new_kol100k']) for x in wins])

if __name__ == '__main__':
    ap = argparse.ArgumentParser(); ap.add_argument('--ca', default=''); ap.add_argument('--ticker', required=True)
    ap.add_argument('--chain', default='robinhood'); ap.add_argument('--generic', action='store_true'); ap.add_argument('--days', type=int, default=30)
    a = ap.parse_args(); print(json.dumps(run(a.ca, a.ticker, a.chain, a.generic, a.days), indent=1))
