"""Football-style divisions per league: Div 1-4 x 100 agents + open Qualifying pool. Weekly matchweeks (Mon-Fri),
top 2 promoted / bottom 2 relegated each week (Qualifying top 2 fill Div 4). Monthly seasons with champions.
Seeded with SIMULATED bots (clearly labelled) so the tables are full at launch. Paper trading only. Not investment advice.
Matchweek score = 100 x weekly return - 50 x |intra-week max drawdown| (risk-adjusted; gambling doesn't win).
Result per matchweek within a division: top 30% = W (3 pts), middle 40% = D (1), bottom 30% = L (0)."""
import json, os, sys, random, datetime as dt
import numpy as np
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); sys.path.insert(0, os.path.join(ROOT, "engine"))
from engine import load_market, LEAGUES, BACKFILL_DAYS
OUT = os.path.join(ROOT, "site", "data", "divisions.json")
DIV_SIZE, N_DIV, N_QUAL, MOVE = 100, 4, 40, 2
COUNTRIES = ["US"]*14 + ["CA"]*8 + ["GB"]*6 + ["DE"]*4 + ["FR"]*3 + ["JP"]*4 + ["KR"]*3 + ["CN"]*4 + ["IN"]*4 + ["SG"]*3 + ["AU"]*3 + ["CH"]*2 + ["NL"]*2 + ["SE"]*2 + ["BR"]*2 + ["AE"]*2 + ["IL"]*2 + ["HK"]*2 + ["NG"] + ["ZA"] + ["MX"] + ["ES"] + ["IT"] + ["PL"] + ["EE"] + ["NZ"] + ["IE"]
ADJ = "Quantum Neon Iron Silent Velvet Rapid Lunar Solar Arctic Crimson Golden Hyper Delta Sigma Atlas Vector Nova Orbit Cobalt Zephyr Onyx Titan Echo Prism Rogue Stoic Turbo Cipher Ember Polar".split()
NOUN = "Heron Falcon Badger Otter Lynx Mantis Raven Viper Orca Bison Kestrel Puma Wolf Hawk Shark Fox Owl Panther Gecko Marlin Condor Jackal Cobra Ibis Yak Moose Stag Wasp Koi Dingo".split()
STYLES = ["static", "momentum", "reversal", "yield", "minvar"]
flag = lambda c: "".join(chr(0x1F1E6 + ord(x) - 65) for x in c.upper()) if c and len(c) == 2 else "🏳️"
def returns_matrix(cal, mk, syms, start):
    days = cal[start - 1:]; R = np.zeros((len(days) - 1, len(syms))); Y = np.zeros(len(syms))
    for j, s in enumerate(syms):
        px = mk[s]["px"]; dv = mk[s]["div"]
        for i in range(1, len(days)):
            a, b = px.get(days[i - 1]), px.get(days[i])
            if a and b: R[i - 1, j] = (b + dv.get(days[i], 0)) / a - 1
        lp = px.get(days[-1]) or 1; Y[j] = sum(v for d, v in dv.items() if d > cal[max(0, len(cal) - 252)]) / lp
    return days[1:], np.clip(R, -0.5, 0.5), Y
def sim_bot_returns(R, Y, rng, style, k, freq):
    FEE = getattr(sim_bot_returns, "fee", np.zeros(R.shape[1]))
    T, n = R.shape; out = np.zeros(T); w = np.zeros(n); k = min(k, n)
    base = rng.choice(n, k, replace=False); dir_w = rng.dirichlet(np.ones(k))
    for t in range(T):
        if t % freq == 0:
            if style == "static": nw = np.zeros(n); nw[base] = dir_w
            else:
                look = R[max(0, t - 20):t].sum(0) if t else np.zeros(n)
                if style == "momentum": pick = np.argsort(-look)[:k]
                elif style == "reversal": pick = np.argsort(look)[:k]
                elif style == "yield": pick = np.argsort(-Y)[:k]
                else: pick = np.argsort(R[max(0, t - 20):t].std(0) if t > 2 else rng.random(n))[:k]
                nw = np.zeros(n); nw[pick] = 1 / k
            cost = (np.abs(nw - w) * FEE).sum(); w = nw  # commission-free; 0.5% FX fee on CAD-priced legs
        else: cost = 0
        r = float(w @ R[t]); out[t] = r - cost
        w = w * (1 + R[t]); s = w.sum(); w = w / s if s > 0 else w
    return out
def main():
    rng = np.random.default_rng(42); random.seed(42)
    cal, mk = load_market(); start = max(25, len(cal) - BACKFILL_DAYS)
    board = json.load(open(os.path.join(ROOT, "site", "data", "leaderboard.json")))
    result = {"generated": dt.datetime.now().isoformat(timespec="minutes"), "rules": {"division_size": DIV_SIZE, "divisions": N_DIV, "promoted": MOVE, "relegated": MOVE,
              "matchweek": "Mon-Fri", "score": "100 x weekly return - 50 x |intra-week max drawdown|", "points": "W (top 30%) 3, D (middle 40%) 1, L (bottom 30%) 0", "season": "calendar month"},
              "disclaimer": "Simulated bots are labelled SIMULATED. Paper trading only. Not investment advice. No prizes.", "leagues": {}}
    nations = {}
    for lid, L in LEAGUES.items():
        syms = [s for s in L["symbols"] if s in mk]
        days, R, Y = returns_matrix(cal, mk, syms, start)
        sim_bot_returns.fee = np.array([0.005 if mk[s].get("ccy") == "CAD" else 0.0 for s in syms])
        bots = []
        used = set()
        for i in range(DIV_SIZE * N_DIV + N_QUAL):
            while True:
                nm = f"{random.choice(ADJ)} {random.choice(NOUN)} {random.randint(1, 99):02d}"
                if nm not in used: used.add(nm); break
            st = random.choice(STYLES); k = random.randint(2, max(2, min(8, len(syms)))); fq = random.choice([1, 5, 10, 21])
            bots.append({"id": f"sim-{lid}-{i:03d}", "name": nm, "country": random.choice(COUNTRIES), "simulated": True, "style": st.capitalize(),
                         "company": "Simulated", "r": sim_bot_returns(R, Y, rng, st, k, fq)})
        # real agents (house + outside) enter via the Qualifying pool
        for a in [x for x in board["agents"] if x["league"] == lid]:
            v = np.array([p["value"] for p in a["curve"]]); r = v[1:] / v[:-1] - 1
            r = np.concatenate([np.zeros(max(0, len(days) - len(r))), r[-len(days):]])
            bots.append({"id": a["id"], "name": a["name"], "country": a.get("country") or ("CA" if a.get("author", "house") == "house" else "UN"),
                         "simulated": False, "icon": a.get("icon"), "retired": bool(a.get("retired")), "house": a.get("author", "house") == "house", "style": a["style"], "company": a.get("company") or "Agent Arena house bot", "r": r, "profile": True})
        rng.shuffle(bots[:DIV_SIZE * N_DIV]) if False else None
        div = {b["id"]: (i // DIV_SIZE + 1 if i < DIV_SIZE * N_DIV else 5) for i, b in enumerate(bots)}  # 5 = Qualifying
        by_id = {b["id"]: b for b in bots}
        weeks = {}
        for i, d in enumerate(days):
            y, w, _ = dt.date.fromisoformat(d).isocalendar(); weeks.setdefault(f"{y}-W{w:02d}", []).append(i)
        st = {b["id"]: {"P": 0, "W": 0, "D": 0, "L": 0, "pts": 0, "gs": 0.0, "form": [], "move": 0, "prev_pos": None, "pos": None} for b in bots}
        season_of = lambda wk: days[weeks[wk][-1]][:7]
        seasons, cur_season, movements = [], None, []
        for wk in sorted(weeks):
            idx = weeks[wk]; sea = season_of(wk)
            if sea != cur_season:
                if cur_season:
                    d1 = sorted([b for b in bots if div[b["id"]] == 1], key=lambda b: (-st[b["id"]]["pts"], -st[b["id"]]["gs"]))
                    seasons.append({"season": cur_season, "champion": {"id": d1[0]["id"], "name": d1[0]["name"], "country": d1[0]["country"], "flag": flag(d1[0]["country"]), "pts": st[d1[0]["id"]]["pts"], "simulated": d1[0]["simulated"]},
                                    "div_winners": {str(dv): (lambda t: {"name": t["name"], "flag": flag(t["country"])})(sorted([b for b in bots if div[b["id"]] == dv], key=lambda b: (-st[b["id"]]["pts"], -st[b["id"]]["gs"]))[0]) for dv in range(2, 6)}})
                for s in st.values(): s.update({"P": 0, "W": 0, "D": 0, "L": 0, "pts": 0, "gs": 0.0})
                cur_season = sea
            wkscore = {}
            for b in bots:
                r = b["r"][idx]; eq = np.cumprod(1 + r); mdd = float((eq / np.maximum.accumulate(np.concatenate([[1], eq]))[1:] - 1).min()) if len(eq) else 0
                wkscore[b["id"]] = round(100 * (eq[-1] - 1) - 50 * abs(min(0, mdd)), 3)
            for dv in range(1, 6):
                mem = sorted([b["id"] for b in bots if div[b["id"]] == dv], key=lambda i: -wkscore[i]); n = len(mem)
                for r_, i in enumerate(mem):
                    res = "W" if r_ < 0.3 * n else ("D" if r_ < 0.7 * n else "L"); s = st[i]
                    s["P"] += 1; s[res] += 1; s["pts"] += {"W": 3, "D": 1, "L": 0}[res]; s["gs"] = round(s["gs"] + wkscore[i], 3); s["form"] = (s["form"] + [res])[-5:]; s["last_score"] = wkscore[i]
            # table positions + promotion/relegation
            tables = {}
            for dv in range(1, 6):
                t = sorted([b["id"] for b in bots if div[b["id"]] == dv], key=lambda i: (-st[i]["pts"], -st[i]["gs"]))
                for p_, i in enumerate(t): st[i]["prev_pos"] = st[i]["pos"]; st[i]["pos"] = p_ + 1
                tables[dv] = t
            for s in st.values(): s["move"] = 0
            mv = []
            for dv in range(1, 5):
                down = tables[dv][-MOVE:]; up = tables[dv + 1][:MOVE]
                for i in down: div[i] = dv + 1; st[i]["move"] = -1; mv.append({"id": i, "name": by_id[i]["name"], "from": dv, "to": dv + 1})
                for i in up: div[i] = dv; st[i]["move"] = 1; mv.append({"id": i, "name": by_id[i]["name"], "from": dv + 1, "to": dv})
            movements.append({"matchweek": wk, "moves": mv})
        last_wk = sorted(weeks)[-1]
        tables_out = {}
        for dv in range(1, 6):
            t = sorted([b for b in bots if div[b["id"]] == dv], key=lambda b: (-st[b["id"]]["pts"], -st[b["id"]]["gs"]))
            tables_out["Q" if dv == 5 else str(dv)] = [{"pos": i + 1, "id": b["id"], "name": b["name"], "country": b["country"], "flag": flag(b["country"]), "icon": b.get("icon"), "retired": b.get("retired", False), "simulated": b["simulated"],
                "house": b.get("house", False), "profile": b.get("profile", False), "company": b["company"], "style": b["style"], **{k: st[b["id"]][k] for k in ("P", "W", "D", "L", "pts", "gs", "form", "move")},
                "last": st[b["id"]].get("last_score", 0)} for i, b in enumerate(t)]
            for row in tables_out["Q" if dv == 5 else str(dv)]:
                c = row["country"]; n_ = nations.setdefault(c, {"country": c, "flag": flag(c), "agents": 0, "div1": 0, "top": []})
                n_["agents"] += 1; n_["div1"] += dv == 1; n_["top"].append(row["gs"] + (5 - dv) * 1000)
        result["leagues"][lid] = {"name": L["name"], "featured": L.get("featured", False), "matchweek": last_wk, "season": cur_season,
            "matchweek_dates": [days[weeks[last_wk][0]], days[weeks[last_wk][-1]]], "tables": tables_out, "seasons": seasons[::-1], "movements": movements[-6:][::-1],
            "matchweeks_played": len(weeks)}
    nat = []
    for c, n_ in nations.items():
        top = sorted(n_["top"], reverse=True)[:5]
        nat.append({"country": c, "flag": n_["flag"], "agents": n_["agents"], "div1": n_["div1"], "strength": round(sum(top) / len(top), 1)})
    result["nations"] = sorted(nat, key=lambda x: (-x["div1"], -x["strength"]))
    idx = {k: v for k, v in result.items() if k != "leagues"}
    idx["leagues"] = {}
    for lid, v in result["leagues"].items():
        json.dump(v, open(os.path.join(ROOT, "site", "data", f"divisions-{lid}.json"), "w"), separators=(",", ":"))
        idx["leagues"][lid] = {k: v[k] for k in ("name", "featured", "matchweek", "season", "matchweek_dates", "matchweeks_played")} | {"top": v["tables"]["1"][:10], "champion": v["seasons"][0]["champion"] if v["seasons"] else None}
    json.dump(idx, open(OUT, "w"), separators=(",", ":"))
    for lid, v in result["leagues"].items():
        t = v["tables"]["1"][0]; print(lid, "matchweeks", v["matchweeks_played"], "Div1 leader:", t["flag"], t["name"], t["pts"], "pts; champions:", len(v["seasons"]))
    print("nations:", [(n["country"], n["div1"]) for n in result["nations"][:5]], "index KB", os.path.getsize(OUT) // 1024)
if __name__ == "__main__": main()
