"""Educational step model and a separately specified toy verifier.

No named game's algorithm or RTP is implemented. Model q=411/500, ideal
gross .97/q^n, actual toy payout floored to cents. One unit paid at round
entry, no extra stakes at steps. Synthetic paths use seed20261024.
Toy HMAC stream: UTF-8 client:nonce:block,32-bit big-endian words, rejection
above floor(2^32/500)*500, then modulo500. Values0..410 survive. Not a
production protocol or the site's general hash verifier.
"""
from fractions import Fraction as F
import hashlib
import hmac
import json
import logging
import random

Q=F(411,500)
RHO=F(97,100)


def paytable():
    result=[]
    for n in range(1,11):
        survival=Q**n;ideal=RHO/survival;cents=(ideal*100).numerator//(ideal*100).denominator
        result.append(dict(step=n,survival=float(survival),idealGross=float(ideal),
            paidCents=cents,expectedGross=float(survival*F(cents,100))))
    return result


def simulate():
    rng=random.Random(20261024);paths=[]
    for round_id in range(1,10001):
        flags=[rng.random()<float(Q) for _ in range(10)];reached=0
        for flag in flags:
            if not flag:break
            reached+=1
        paths.append(dict(round=round_id,bits=''.join('1' if x else '0' for x in flags),reached=reached))
    result=[];payouts=paytable()
    for target in (1,3,5,10):
        cash=1000000;wins=gross=run=maximum=0;cents=payouts[target-1]['paidCents']
        for row in paths:
            assert cash>=100;won=row['reached']>=target;paid=cents if won else 0
            cash+=paid-100;gross+=paid;wins+=won;run=0 if won else run+1;maximum=max(maximum,run)
        assert cash==1000000+gross-1000000
        result.append(dict(target=target,payoutCents=cents,wagers=10000,wins=wins,
            longestLossRun=maximum,turnover=10000,gross=gross/100,net=(gross-1000000)/100,final=cash/100))
    return dict(paths=paths,results=result)


def map_word(word):
    if isinstance(word,bool) or not isinstance(word,int) or not 0<=word<2**32:
        raise ValueError('Unsigned32-bit integer required')
    limit=(2**32//500)*500
    return word%500 if word<limit else None


def educational_sequence(server,client,nonce,steps=10):
    if not isinstance(server,str) or not server or not isinstance(client,str) or not client:
        raise ValueError('Nonempty UTF-8 seed strings required')
    if isinstance(nonce,bool) or not isinstance(nonce,int) or not 0<=nonce<=2**53-1:
        raise ValueError('Nonnegative safe integer nonce required')
    if isinstance(steps,bool) or not isinstance(steps,int) or not 1<=steps<=100:
        raise ValueError('Steps1..100 required')
    values=[];digests=[];block=0
    while len(values)<steps:
        digest=hmac.new(server.encode('utf-8'),f'{client}:{nonce}:{block}'.encode('utf-8'),hashlib.sha256).digest()
        digests.append(digest.hex());block+=1
        for index in range(8):
            value=map_word(int.from_bytes(digest[4*index:4*index+4],'big'))
            if value is not None:values.append(value)
            if len(values)==steps:break
    return dict(commitment=hashlib.sha256(server.encode('utf-8')).hexdigest(),digests=digests,values=values)


if __name__=='__main__':
    logging.basicConfig(level=logging.INFO,format='%(message)s')
    logging.info(json.dumps(dict(paytable=paytable(),syntheticResults=simulate()['results'],
        toy=educational_sequence('crashmath_chicken_demo_server','crashmath_chicken_demo_client',7)),indent=2))
