"""Funded illustrative backtests on CrashMath's existing synthetic CSV.

No live casino data or population ruin estimates. All money fields use integer cents.
Usage: python ev_ledgers.py [dataset.csv] [--ledger output.csv]
"""
import argparse
import csv
from decimal import Decimal
from fractions import Fraction
import hashlib
import json
import logging
from pathlib import Path


def load_rows(path):
    expected='0f3341c5dc53667e4e7b36c80998461ad0d215ce8608dded2d72c27d9bf7ab10'
    if hashlib.sha256(path.read_bytes()).hexdigest()!=expected:
        raise ValueError('File differs from the documented synthetic corpus')
    with path.open(encoding='utf-8',newline='') as stream:
        return list(csv.DictReader(stream))


def simulate(rows,name,target_cents,fixed_stake_cents):
    balance=peak=100000
    peak_round=0
    max_drawdown=Fraction(0)
    max_drawdown_cents=turnover=gross_total=wins=0
    unfunded_round=None
    ledger=[]
    for row in rows:
        stake=fixed_stake_cents if fixed_stake_cents is not None else max(10,balance*2//1000)
        if balance<stake:
            unfunded_round=int(row['round_id'])
            break
        before=balance
        won=Decimal(row['multiplier'])>=Decimal(target_cents)/100
        gross=stake*target_cents//100 if won else 0
        balance=balance-stake+gross
        turnover+=stake
        gross_total+=gross
        wins+=won
        if balance>peak:
            peak=balance
            peak_round=int(row['round_id'])
        drawdown=Fraction(peak-balance,peak)
        max_drawdown=max(max_drawdown,drawdown)
        max_drawdown_cents=max(max_drawdown_cents,peak-balance)
        ledger.append(dict(strategy=name,roundId=int(row['round_id']),balanceBeforeCents=before,
            stakeCents=stake,grossCents=gross,balanceAfterCents=balance,peakCents=peak))
    assert balance==100000+gross_total-turnover
    summary=dict(strategy=name,scope='One fixed synthetic path, not an optimal strategy or ruin probability',
        target=target_cents/100,stakeRule=('fixed '+str(fixed_stake_cents/100)) if fixed_stake_cents is not None else '0.2% current balance, floored to cents, minimum 0.10',
        startingBalanceCents=100000,executedRounds=len(ledger),wins=wins,losses=len(ledger)-wins,
        turnoverCents=turnover,grossPayoutCents=gross_total,netCents=gross_total-turnover,
        finalBalanceCents=balance,peakBalanceCents=peak,peakRoundId=peak_round,
        maxDrawdownPercent=float(max_drawdown*100),maxDrawdownCents=max_drawdown_cents,
        realizedRtpPercent=float(Fraction(gross_total,turnover)*100) if turnover else None,
        unfundedAtRoundId=unfunded_round)
    return summary,ledger


def backtests(rows):
    return [simulate(rows,*case) for case in [('A',110,1000),('B',200,500),('C',500,None)]]


if __name__=='__main__':
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('dataset',nargs='?',type=Path,default=Path(__file__).with_name('crashmath_10000_rounds_dataset.csv'))
    parser.add_argument('--ledger',type=Path)
    args=parser.parse_args()
    results=backtests(load_rows(args.dataset))
    if args.ledger:
        with args.ledger.open('w',encoding='utf-8',newline='') as stream:
            writer=csv.DictWriter(stream,fieldnames=list(results[0][1][0]))
            writer.writeheader()
            for _,ledger in results:writer.writerows(ledger)
    logging.basicConfig(level=logging.INFO,format='%(message)s')
    logging.info(json.dumps(dict(scope='Existing public synthetic corpus; no live study',strategies=[summary for summary,_ in results]),indent=2))
