import sys, bisect; sys.path.insert(0, "/root/fwa1")
from common import *
Q = 2 ** 192
FEE = 0.01 + 0.0001 + 0.0015   # GMGN 1% + pool 0.01% + gas ~$0.03 on $20

def prep(r):
    p = r["path"]
    if not p: return None
    bs = [x[0] for x in p]
    px = [Q / (int(x[2]) ** 2) if int(x[2]) else 0 for x in p]   # ETH per token
    fb = bs[0]
    pull = (r["pull_b"] - r["b0"]) if r.get("pull_b") else None
    return dict(bs=bs, px=px, fb=fb, pull=pull, b0=r["b0"], n=len(bs))

def px_at(c, off):
    i = bisect.bisect_right(c["bs"], off) - 1
    return c["px"][i] if i >= 0 else None, i

def entry(c, secs=10):
    e = c["fb"] + secs * 10
    if c["pull"] is not None and c["pull"] <= e + 20: return None, e
    p, i = px_at(c, e + 20)
    return p, e

def fill(c, trig_off, lag=30):
    """sell fill at pool state lag blocks after trigger; 0 if pulled before"""
    if c["pull"] is not None and c["pull"] <= trig_off + lag: return 0.0
    p, _ = px_at(c, trig_off + lag)
    return p

def run(c, plan, secs=10):
    """plan: list of (fraction, multiple or ('t', seconds)); returns net return per $1, entry block offset"""
    p0, e = entry(c, secs)
    if not p0: return None
    out = 0.0; left = 1.0
    i0 = bisect.bisect_right(c["bs"], e + 20)
    legs = sorted(plan, key=lambda x: (x[1][1] * 1e9 if isinstance(x[1], tuple) else x[1]))
    for frac, tgt in legs:
        if isinstance(tgt, tuple):
            to = e + tgt[1] * 10
            if c["pull"] is not None and c["pull"] <= to: f = 0.0
            else: f = fill(c, to, 30)
            out += frac * f / p0; left -= frac; continue
        hit = None
        for j in range(i0, c["n"]):
            if c["pull"] is not None and c["bs"][j] >= c["pull"]: break
            if c["px"][j] >= tgt * p0: hit = c["bs"][j]; break
        if hit is None:
            # never hit: pulled -> 0; not pulled -> mark at last price
            f = 0.0 if c["pull"] is not None else c["px"][-1]
        else:
            f = fill(c, hit, 30)
        out += frac * f / p0; left -= frac
    gross = out
    return gross * (1 - FEE) ** 2 - 1, e

def peak(c, secs=10):
    p0, e = entry(c, secs)
    if not p0: return None
    i0 = bisect.bisect_right(c["bs"], e + 20)
    m = p0
    for j in range(i0, c["n"]):
        if c["pull"] is not None and c["bs"][j] >= c["pull"]: break
        m = max(m, c["px"][j])
    return m / p0

PLANS = {
    "all2x": [(1, 2)], "all2.5x": [(1, 2.5)], "all3x": [(1, 3)], "all5x": [(1, 5)], "all10x": [(1, 10)],
    "half3_10": [(0.5, 3), (0.5, 10)], "half2_10": [(0.5, 2), (0.5, 10)],
    "t30s": [(1, ("t", 30))], "t60s": [(1, ("t", 60))], "t120s": [(1, ("t", 120))],
}
