"""
ai-casino.online casino simulations, September 2026.
Reproducible: run `python3 simulate.py` (needs numpy). Fixed seed 20260927.
Writes CSV files next to this script and prints a JSON summary.
"""
import numpy as np, json, csv
rng = np.random.default_rng(20260927)
RED = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36}
out = {}

# 1) One million European roulette spins
N = 1_000_000
spins = rng.integers(0, 37, N)
counts = np.bincount(spins, minlength=37)
exp = N/37
chi2 = float(((counts-exp)**2/exp).sum())
colour = np.array([1 if s in RED else (0 if s==0 else 2) for s in range(37)])[spins]  # 1 red, 2 black, 0 zero
# longest same-colour run (red/black only; zero breaks it)
best = cur = 0; prev = -1
for c in colour:
    if c != 0 and c == prev: cur += 1
    else: cur = 1 if c != 0 else 0
    prev = c; best = max(best, cur)
# red after 5 reds in a row
is_red = (colour == 1)
k = 5
win = np.lib.stride_tricks.sliding_window_view(is_red[:-1], k).all(axis=1)
nxt = is_red[k:]
after5 = float(nxt[win].mean()); n_after5 = int(win.sum())
# longest drought for any single number
last = {n:-1 for n in range(37)}; drought = {n:0 for n in range(37)}
for i, s in enumerate(spins):
    gap = i - last[s] - 1
    if gap > drought[s]: drought[s] = gap
    last[s] = i
for n in range(37):
    drought[n] = max(drought[n], N - last[n] - 1)
out['roulette'] = dict(spins=N, expected_per_number=round(exp,1), min_count=int(counts.min()), min_number=int(counts.argmin()),
    max_count=int(counts.max()), max_number=int(counts.argmax()), chi_square=round(chi2,2), chi_df=36,
    red_share=round(float(is_red.mean())*100,3), zero_share=round(float((spins==0).mean())*100,3),
    longest_colour_run=int(best), red_after_5_reds=round(after5*100,2), times_5_reds=n_after5,
    longest_drought=int(max(drought.values())), longest_drought_number=int(max(drought, key=drought.get)))
with open('roulette-number-counts.csv','w',newline='') as f:
    w = csv.writer(f); w.writerow(['number','colour','times_hit','expected','difference_pct','longest_gap_spins'])
    for n in range(37):
        w.writerow([n, 'green' if n==0 else ('red' if n in RED else 'black'), int(counts[n]), round(exp,1), round((counts[n]-exp)/exp*100,2), drought[n]])

# 2) Martingale vs flat betting on red: 10,000 sessions each
S = 10_000; BANK = 200; TARGET = 50; MAXSPINS = 500; TABLE_MAX = 500
def session(martingale):
    bank = BANK; bet = 1; spins_used = 0
    while spins_used < MAXSPINS:
        if martingale and bank - BANK >= TARGET: return bank-BANK, 'target', spins_used
        stake = min(bet, bank, TABLE_MAX) if martingale else 1
        if stake <= 0 or bank < 1: return bank-BANK, 'bust', spins_used
        r = rng.integers(0, 37); spins_used += 1
        if r in RED: bank += stake; bet = 1
        else: bank -= stake; bet = stake*2
    return bank-BANK, 'time', spins_used
rows = []
for strat in ('martingale','flat'):
    res = [session(strat=='martingale') for _ in range(S)]
    pnl = np.array([r[0] for r in res]); ends = [r[1] for r in res]; sp = np.array([r[2] for r in res])
    total_staked_est = None
    out[strat] = dict(sessions=S, reached_target_pct=round(ends.count('target')/S*100,2), bust_pct=round(ends.count('bust')/S*100,2),
        in_profit_pct=round(float((pnl>0).mean())*100,2), mean_result=round(float(pnl.mean()),2), median_result=round(float(np.median(pnl)),2),
        worst=int(pnl.min()), best=int(pnl.max()), avg_spins=round(float(sp.mean()),1))
    for i,r in enumerate(res): rows.append([strat, i+1, r[0], r[1], r[2]])
with open('martingale-vs-flat-sessions.csv','w',newline='') as f:
    w = csv.writer(f); w.writerow(['strategy','session','result_gbp','ended_by','spins_played']); w.writerows(rows)

# 3) Slot sessions: three model slots, all exactly 96% RTP, 100,000 sessions x 200 spins at £1
MODELS = {
 'low':    ([0, 0.5, 1, 2, 5, 20],          [0.52, 0.18, 0.15, 0.10, 0.04, None]),
 'medium': ([0, 0.5, 2, 5, 20, 100],        [0.70, 0.12, 0.08, 0.04, 0.015, None]),
 'high':   ([0, 1, 3, 10, 100, 1000],       [0.80, 0.10, 0.06, 0.02, 0.002, None]),
}
def finish(pays, probs, rtp=0.96):
    # choose the last probability so the RTP is exactly 96%, rest is a 0 pay
    known = sum(p*q for p,q in zip(pays[:-1], probs[:-1]))
    last = (rtp - known)/pays[-1]
    probs = probs[:-1] + [last]
    probs[0] = 1 - sum(probs[1:])
    return np.array(pays, float), np.array(probs)
SESS = 100_000; SP = 200
slot_rows = []
for name,(pays,probs) in MODELS.items():
    pays, probs = finish(pays, probs)
    assert abs((pays*probs).sum() - 0.96) < 1e-9 and probs.min() >= 0
    draws = rng.choice(len(pays), size=(SESS, SP), p=probs)
    res = pays[draws].sum(axis=1) - SP
    hit = float((pays[draws] > 0).mean())
    out['slot_'+name] = dict(sessions=SESS, spins=SP, stake=1, rtp=96, hit_rate_pct=round(hit*100,1),
        in_profit_pct=round(float((res>0).mean())*100,2), median=round(float(np.median(res)),1), mean=round(float(res.mean()),2),
        p10=round(float(np.percentile(res,10)),1), p90=round(float(np.percentile(res,90)),1), best=round(float(res.max()),1), worst=round(float(res.min()),1),
        top_prize=float(pays[-1]), top_prize_odds=round(1/probs[-1]))
    hist, edges = np.histogram(np.clip(res,-200,300), bins=25, range=(-200,300))
    out['slot_'+name]['hist'] = hist.tolist(); out['slot_'+name]['edges'] = edges.tolist()
    for lo,hi,c in zip(edges[:-1],edges[1:],hist): slot_rows.append([name, int(lo), int(hi), int(c)])
with open('slot-session-results.csv','w',newline='') as f:
    w = csv.writer(f); w.writerow(['model','result_from_gbp','result_to_gbp','sessions']); w.writerows(slot_rows)
out['roulette_counts'] = counts.tolist()
json.dump(out, open('summary.json','w'), indent=1)
print(json.dumps({k:v for k,v in out.items() if k!='roulette_counts'}, indent=1, default=str)[:6000])
