import json, gzip, collections, math, time
PM = '0x8366a39cc670b4001a1121b8f6a443a643e40951'; Z = '0x' + '0' * 40; DEAD = '0x000000000000000000000000000000000000dead'
AK = json.load(gzip.open('/tmp/run/ak.json.gz', 'rt'))
R = json.load(gzip.open('/tmp/run/rt.json.gz', 'rt')); RT = {t['address'].lower(): t for t in R['tokens']}
SUP = {k.lower(): v for k, v in R['supply'].items()}; KL = {k.lower(): v for k, v in R['kline'].items()}
BPS = 8.9   # blocks per second
def kind(a):
    k = AK.get(a) or AK.get(a.lower())
    return k or 'user?'
def is_user(a): return a not in (PM, Z, DEAD) and kind(a) in ('user', 'user?')
def tier_of(v):
    for t in (100e6, 20e6, 5e6, 2e6, 1e6, 5e5):
        if v >= t: return t
    return 0
def peak7(tok, L_ts):
    k = KL.get(tok); s = SUP.get(tok)
    if not k or not s or not L_ts: return None, None
    w = [x for x in k if L_ts - 3600 <= x[0] <= L_ts + 7 * 86400]
    if not w or w[0][0] > L_ts + 6 * 3600: return None, 'no_early_kline'
    cl = sorted((x[4] * s for x in w), reverse=True)
    pk = cl[1] if len(cl) > 1 else cl[0]
    t500 = next((x[0] for x in w if x[4] * s >= 5e5), None)
    return pk, (t500 - L_ts) / 3600 if t500 else None
def feats(o, smart_index=None):
    tok = o['tok']; L = o['launch_block']; tr = sorted(o.get('tr') or [], key=lambda x: (x[0], x[1]))
    pools = o.get('pools') or []
    f = dict(tok=tok, kind=o['kind'], L=L, ts=o.get('launch_ts'))
    p0 = pools[0] if pools else None
    if p0:
        if 'data' in p0:
            d = p0['data'][2:]; w = [d[i:i + 64] for i in range(0, len(d), 64)]
            f['hook'] = '0x' + w[2][-40:] if len(w) > 2 else None; f['fee'] = int(w[0], 16) if w else None
        else: f['hook'] = p0.get('hook'); f['fee'] = p0.get('fee')
        f['quote'] = p0['c1'] if p0['c0'] == tok else p0['c0']
    f['factory'] = o.get('ptx_to'); f['creator'] = o.get('ptx_from'); f['n_pools'] = len(pools)
    f['mint_gap_s'] = (L - o['mint_block']) / BPS if o.get('mint_block') else None
    supply = sum(int(m[2], 16) for m in o.get('mints') or []) or 1
    f['hour'] = time.gmtime((o.get('launch_ts') or 0) + 7 * 3600).tm_hour
    # premint: tokens minted to user wallets
    f['premint_user_share'] = sum(int(m[2], 16) for m in o.get('mints') or [] if is_user(m[1])) / supply
    bal = collections.defaultdict(int)
    for m in o.get('mints') or []: bal[m[1]] += int(m[2], 16)
    bytx = collections.defaultdict(list)
    for x in tr: bytx[x[5]].append(x)
    first_buy = {}; buys = []; sells = 0; sellers = set(); w2w = 0; creator = (o.get('ptx_from') or '').lower()
    first_sell_s = None
    for txh, xs in sorted(bytx.items(), key=lambda kv: (kv[1][0][0], kv[1][0][1])):
        net = collections.defaultdict(int); b = xs[0][0]
        for (_, _, fr, to, val, _) in xs:
            v = int(val, 16) if val and val != '0x' else 0
            if fr == Z: continue    # mints already counted
            net[fr] -= v; net[to] += v
            bal[fr] -= v; bal[to] += v
        pm = net.get(PM, 0); sec = (b - L) / BPS
        if pm < 0:      # pool paid out tokens: buy; the final holders are the users with a positive net
            for a, v in net.items():
                if v > 0 and is_user(a):
                    buys.append((sec, a, v))
                    first_buy.setdefault(a, sec)
        elif pm > 0:
            for a, v in net.items():
                if v < 0 and is_user(a):
                    sells += 1; sellers.add(a)
                    if first_sell_s is None: first_sell_s = sec
        else:
            us = [a for a, v in net.items() if v != 0 and is_user(a)]
            if len(us) >= 2: w2w += 1
    tot = supply
    def nb(s): return len({a for t, a, v in buys if t <= s})
    f.update(buyers_10s=nb(10), buyers_1m=nb(60), buyers_5m=nb(300), buyers_30m=nb(1800),
             buys_30m=len(buys), sells_30m=sells, sellers_30m=len(sellers), w2w_30m=w2w,
             first_sell_s=first_sell_s)
    blk_buys = [(t, a, v) for t, a, v in buys if t <= 3 * 1 / BPS + 0.01]   # first 3 blocks
    f['bundle_buyers_3blk'] = len({a for t, a, v in blk_buys}); f['bundle_share_3blk'] = sum(v for t, a, v in blk_buys) / tot
    f['share_bought_10s'] = sum(v for t, a, v in buys if t <= 10) / tot
    f['share_bought_1m'] = sum(v for t, a, v in buys if t <= 60) / tot
    first20 = [v for t, a, v in buys[:20]]
    if len(first20) >= 5:
        m = sum(first20) / len(first20); sd = (sum((x - m) ** 2 for x in first20) / len(first20)) ** .5
        f['cv_first20'] = sd / m if m else None
    hb = sorted((v for a, v in bal.items() if is_user(a) and v > 0), reverse=True)
    f['top1_30m'] = hb[0] / tot if hb else 0; f['top10_30m'] = sum(hb[:10]) / tot; f['holders_30m'] = len(hb)
    f['creator_share_30m'] = max(0, bal.get(creator, 0)) / tot if creator else None
    f['creator_bought'] = int(any(a == creator for t, a, v in buys))
    f['early5m'] = sorted({a for t, a, v in buys if t <= 300})
    return f
