"""AN4.1 winner anatomy, stage 1 (DC Analyst 4, 7 Oct 2026).
Inputs: A1 event pickles (ev_v4.pkl.gz, ev_v3.pkl.gz), dc-a1-1-universe.csv.gz, hookfp.csv, crewlaunch.csv.
Event = (t, cap, caplow, sell_usd, buy_usd, nbuy, sellable_cap or 0, R_usd or 0)."""
import pickle, gzip, csv, math, random, collections, datetime, bisect, json, sys
import pandas as pd, numpy as np
D = sys.argv[1] if len(sys.argv) > 1 else "."
BKK = datetime.timezone(datetime.timedelta(hours=7))
def bk(t): return datetime.datetime.fromtimestamp(t, BKK).strftime("%Y-%m-%d %H:%M") if t else ""
T_B = int(datetime.datetime(2026, 9, 29, tzinfo=BKK).timestamp())
H, H24, D7 = 3600, 86400, 7 * 86400
TICKET = 1000
uni = pd.read_csv(D + "/uni.csv.gz")
P = {}
for f in ("ev_v4.pkl.gz", "ev_v3.pkl.gz"):
    try: P.update(pickle.load(gzip.open(D + "/" + f, "rb")))
    except Exception as e: print("missing", f, e)
print("pools", len(P), flush=True)
DATAEND = {}
for v in ("v4", "v3", "pons"):
    pass
def Rfix(R, cap):
    if not R or R <= 0: R = 0.1 * cap
    return min(R, 0.5 * cap) if cap > 0 else R
def cost(x):
    """$ cost per unit of cap for a $1k buy at event x. Conservative: never buy below the bar's own low."""
    c = max(x[1], x[2]) if x[2] > 0 else x[1]
    if c <= 0: return None
    R = Rfix(x[7], c); return c * (R + TICKET) / R
def sellv(units, x):
    v = units * x[6]; R = Rfix(x[7], x[6]); return v * R / (R + v) * 0.99
def legs(ev, i0, pull):
    """Returns (leg_start_idx, first_5x_idx, best_mult). Running-low entry, A1 honest5x math."""
    best_c = None; bi = None; bestm = 0.0; first = None; leg = None
    for j in range(i0, len(ev)):
        x = ev[j]
        if x[0] >= pull: break
        if best_c is not None and x[6] > 0:
            m = sellv(TICKET * 0.99 / best_c, x) / TICKET
            if m > bestm: bestm = m
            if m >= 5 and first is None: first = j; leg = bi
        c = cost(x)
        if c is not None and (best_c is None or c < best_c): best_c = c; bi = j
    return leg, first, bestm
# hook families and crew roots
HF = {}
try:
    for r in csv.DictReader(open(D + "/hookfp.csv")): HF[r["hook"].lower()] = "%s_%s" % (r["size"], (r["md5"] or "")[:8])
except Exception as e: print("no hookfp", e)
ROOT = {}
try:
    for r in csv.DictReader(open(D + "/crewlaunch.csv")): ROOT[r["pool"].lower()] = (r["root"] or "").lower()
except Exception as e: print("no crewlaunch", e)
rows = []; pts_ctrl = collections.defaultdict(list)
U = uni.set_index("pool")
coins = []
for pool, u in U.iterrows():
    d = P.get(pool)
    if d is None: continue
    ev = d["ev"]; pull = d["pull"] or 9e18
    i100 = next((k for k, x in enumerate(ev) if x[6] >= 100_000), None)
    if i100 is None: continue
    vg = "v4" if d["venue"] == "v4" else "v3p"
    coins.append((pool, d, u, i100, pull, vg))
print("coins", len(coins), flush=True)
VEND = collections.defaultdict(float)
for pool, d, u, i100, pull, vg in coins: VEND[vg] = max(VEND[vg], d["ev"][-1][0])
print({k: bk(v) for k, v in VEND.items()})
def bucket(vg, age_s, cap):
    return (vg, int(math.floor(math.log2(max(age_s, 60) / 3600))), int(math.floor(math.log2(max(cap, 1)))))
W = []
nconf = 0
for pool, d, u, i100, pull, vg in coins:
    ev = d["ev"]
    if int(u["honest5x"]) == 1:
        leg, first, bm = legs(ev, i100, pull)
        if leg is None: continue  # did not survive the conservative entry guard
        nconf += 1
        W.append((pool, leg, first, bm))
    else:
        # hindsight lows: cost not beaten in the next 24 h (to pull or data end), within 7 d of launch
        t0 = d["t0"]; idx = []; cs = []
        for j in range(i100, len(ev)):
            x = ev[j]
            if x[0] >= pull or x[0] > t0 + D7: break
            c = cost(x)
            if c is None: continue
            idx.append(j); cs.append(c)
        if not idx: continue
        # sliding window min over the next 24 h, scanning backward
        T = [ev[j][0] for j in idx]; dq = collections.deque(); ok = [False] * len(idx)
        for k in range(len(idx) - 1, -1, -1):
            while dq and T[dq[0]] > T[k] + H24: dq.popleft()
            fut = cs[dq[0]] if dq else float("inf")
            ok[k] = cs[k] <= fut
            while dq and cs[dq[-1]] >= cs[k]: dq.pop()
            dq.append(k)
        for k in range(len(idx)):
            if ok[k]:
                j = idx[k]; x = ev[j]
                pts_ctrl[bucket(vg, x[0] - t0, max(x[1], x[2]))].append((pool, j))
print("winners confirmed", nconf, "of", int(uni.honest5x.sum()), flush=True)
CO = {c[0]: c for c in coins}
rng = random.Random(7)
# index control points by bucket and coin
B = {}
for k, lst in pts_ctrl.items():
    g = collections.defaultdict(list)
    for pool, j in lst: g[pool].append(j)
    B[k] = g
used = set(); pairs = []
order = list(W); rng.shuffle(order)
for pool, leg, first, bm in order:
    pool_, d, u, i100, pull, vg = CO[pool]; x = d["ev"][leg]
    k = bucket(vg, x[0] - d["t0"], max(x[1], x[2]))
    g = B.get(k, {})
    cand = [p for p in g if p not in used]
    rng.shuffle(cand)
    for p in cand[:3]:
        used.add(p); pairs.append((pool, p, rng.choice(g[p])))
    pairs.append((pool, pool, leg))
print("pairs", len(pairs), flush=True)
# creator / root history (coins launched before)
LA = sorted(((CO[p][1]["t0"], p) for p in CO))
WIN = {p: CO[p][1]["ev"][f][0] for p, l, f, bm in W}  # pool -> time of its first 5x
def history(key_of, pool, t):
    k = key_of(pool)
    if not k: return (np.nan, np.nan, np.nan)
    n = w = pl = 0
    for t0, p in LA:
        if t0 >= t: break
        if p != pool and key_of(p) == k:
            n += 1; w += (p in WIN and WIN[p] < t); pl += bool(CO[p][1]["pull"]) and CO[p][1]["pull"] < t
    return (n, w, pl / n if n else np.nan)
cre = lambda p: (CO[p][1]["creator"] or "").lower()
roo = lambda p: ROOT.get(p, "")
def feats(pool, i, is_w, wpool):
    _, d, u, i100, pull, vg = CO[pool]; ev = d["ev"]; x = ev[i]; t = x[0]; t0 = d["t0"]
    cap = max(x[1], x[2]); R = Rfix(x[7], cap)
    hi = 0.0; thi = t0; prior_hi = 0.0
    for y in ev[:i]:
        if y[6] > hi: hi = y[6]; thi = y[0]
    def win(h):
        b = s = nb = 0.0
        k = i - 1
        while k >= 0 and ev[k][0] >= t - h:
            b += ev[k][4]; s += ev[k][3]; nb += ev[k][5]; k -= 1
        return b, s, nb
    b1, s1, n1 = win(H); b6, s6, n6 = win(6 * H); b15, s15, n15 = win(900)
    ch = history(cre, pool, t); rh = history(roo, pool, t)
    hk = (d.get("hooks") or "").lower()
    r = dict(grp="W" if is_w else "C", wpool=wpool, pool=pool, token=d["token"], venue=d["venue"], vg=vg,
             t=int(t), when=bk(t), half="A" if t < T_B else "B", age_h=(t - t0) / H, cap=cap, R=R, R_cap=R / cap if cap else np.nan,
             dd=cap / hi if hi else np.nan, min_since_hi=(t - thi) / 60, had_1m=int(hi >= 1_000_000), had_3x_cap=int(hi >= 3 * cap),
             buy1h=b1, sell1h=s1, net1h=b1 - s1, net1h_cap=(b1 - s1) / cap, vol1h_cap=(b1 + s1) / cap, nbuy1h=n1,
             buy6h=b6, sell6h=s6, net6h_cap=(b6 - s6) / cap, vol6h_cap=(b6 + s6) / cap, nbuy6h=n6,
             net15_cap=(b15 - s15) / cap, buyshare1h=b1 / (b1 + s1) if b1 + s1 else np.nan,
             quiet1h=int(b1 + s1 < 0.01 * cap),
             hfam=HF.get(hk, "nohook" if hk in ("", "0x" + "0" * 40) else "other") if d["venue"] == "v4" else d["venue"],
             cr_n=ch[0], cr_w=ch[1], cr_pull=ch[2], rt_n=rh[0], rt_w=rh[1], rt_pull=rh[2],
             pulled_later=int(bool(d["pull"])), best_1k=float(u["best_1k_mult"]), x20=int(float(u["best_1k_mult"]) >= 20) if is_w else 0)
    return r
out = []
for wp, p, i in pairs:
    out.append(feats(p, i, p == wp, wp))
df = pd.DataFrame(out)
df.to_csv(D + "/an41_rows.csv.gz", index=False)
wl = pd.DataFrame([dict(pool=p, leg=bk(CO[p][1]["ev"][l][0]), x5=bk(CO[p][1]["ev"][f][0]), bm=bm) for p, l, f, bm in W])
wl.to_csv(D + "/an41_winners_legs.csv", index=False)
print(df.groupby(["grp", "half"]).size(), flush=True)
