import json, subprocess, math
exec(open('/home/claude/run/api_test.py').read().split('# --- auth ---')[0])
tk=json.load(open('/tmp/tokens.json')); tok=tk['tok']
def sh(c): return subprocess.run(c,shell=True,capture_output=True,text=True,cwd='/home/claude/run/fiq').stdout
def sql(q): return sh("mariadb -uroot footballiq -N -e \"%s\""%q).strip()

# ---- settlement ----
fx=[r.split('\t') for r in sql("SELECT id FROM fixtures WHERE status='scheduled' ORDER BY kickoff_at LIMIT 6").split('\n')]
ids=[int(x[0]) for x in fx]
scores=[(2,0),(1,1),(0,3),(1,0),(2,2),(3,1)]
for i,(h,a) in zip(ids,scores):
    det = 'AWD' if i==ids[5] else 'FT'
    sql("UPDATE fixtures SET status='finished', status_detail='%s', home_score=%d, away_score=%d, kickoff_at=DATE_ADD(NOW(), INTERVAL -1 DAY) WHERE id=%d"%(det,h,a,i))
    sql("UPDATE prediction_runs SET as_of_at=DATE_ADD(NOW(), INTERVAL -2 DAY) WHERE fixture_id=%d"%i)  # forecast was made before kickoff
print(sh("php spark predict:settle 2>&1 | tail -1"))
n=int(sql("SELECT COUNT(*) FROM predictions_evaluations")); check('5 evaluated, awarded match (AWD) skipped', n==5, n)
print(sh("php spark predict:settle 2>&1 | tail -1"))
check('settle is idempotent', int(sql("SELECT COUNT(*) FROM predictions_evaluations"))==5)
# verify Brier/log loss independently
rows=sql("SELECT r.fixture_id, e.brier_score, e.log_loss FROM predictions_evaluations e JOIN prediction_runs r ON r.id=e.prediction_run_id").split('\n')
ok=True
for line in rows:
    f,b,l=line.split('\t'); f=int(f)
    p={k:float(v) for k,v in (x.split('\t') for x in sql("SELECT s.selection_code, p.probability FROM prediction_probabilities p JOIN prediction_runs r ON r.id=p.prediction_run_id JOIN market_selections s ON s.id=p.market_selection_id JOIN market_types t ON t.id=s.market_type_id WHERE r.fixture_id=%d AND t.code='1X2'"%f).split('\n'))}
    h,a=scores[ids.index(f)]; act='HOME' if h>a else 'DRAW' if h==a else 'AWAY'
    eb=sum((p[k]-(1 if k==act else 0))**2 for k in p); el=-math.log(p[act])
    ok &= abs(eb-float(b))<1e-6 and abs(el-float(l))<1e-6
check('Brier + log loss match independent recomputation', ok, rows)
s,j,_=call('GET','/models/performance',token=tok); d=j['data']
check('performance: n=5, flagged unreliable, vs uniform baseline', s==200 and d['sample_size']==5 and d['reliable'] is False and abs(d['baseline_uniform']['brier_score']-0.6667)<1e-3,(s,j))
s,j,_=call('GET','/predictions/history',token=tok); check('history lists 5 with actual outcome', s==200 and j['meta']['total']==5 and j['data'][0]['actual'] in ('HOME','DRAW','AWAY'),(s,j))
s,j,_=call('GET','/fixtures/%d/predictions'%ids[0],token=tok); check('finished fixture prediction carries evaluation, pre_match true', j['data']['evaluation'] is not None and j['data']['run']['pre_match'] is True,(s,j))
# frozen forecast unaffected by result
before=sql("SELECT SUM(probability) FROM prediction_probabilities")
sh("php spark predict:run --days=7 2>&1 | tail -1")
check('no new forecast made for finished fixtures', sql("SELECT COUNT(*) FROM prediction_runs r JOIN fixtures f ON f.id=r.fixture_id WHERE f.status='finished' AND r.as_of_at > f.kickoff_at")=='0')

# ---- value path once the model is 'proven' (synthetic evaluations) ----
sql("INSERT INTO prediction_runs (fixture_id, model_version_id, as_of_at, data_cutoff_at, status, feature_snapshot_hash, created_at) SELECT r.fixture_id, r.model_version_id, DATE_ADD(r.as_of_at, INTERVAL -(seq.n) HOUR), r.data_cutoff_at, 'complete', NULL, NOW() FROM prediction_runs r JOIN (SELECT a.N + b.N*10 + 1 n FROM (SELECT 0 N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a, (SELECT 0 N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 UNION SELECT 20 UNION SELECT 21 UNION SELECT 22 UNION SELECT 23 UNION SELECT 24) b) seq WHERE r.id=(SELECT MIN(id) FROM prediction_runs) LIMIT 250")
sql("INSERT INTO predictions_evaluations (prediction_run_id, evaluated_at, brier_score, log_loss, settled_at) SELECT r.id, NOW(), 0.55, 0.95, NOW() FROM prediction_runs r WHERE NOT EXISTS (SELECT 1 FROM predictions_evaluations e WHERE e.prediction_run_id=r.id)")
print('evaluations now', sql("SELECT COUNT(*) FROM predictions_evaluations"))
up=sql("SELECT id FROM fixtures WHERE status='scheduled' ORDER BY kickoff_at").split('\n')
allrows=[]
for i in up:
    s,j,_=call('GET','/fixtures/%s/insights'%i,token=tok); d=j['data']
    if d.get('state')!='available': continue
    check('model_reliable true after 200+ good evaluations (fixture %s)'%i, d['model_reliable'] is True, d)
    for r in d['rows']: r['_q']=d['quality']; allrows.append(r)
stat={}
for r in allrows: stat[r['status']]=stat.get(r['status'],0)+1
print('status counts', stat, 'quality', {r['_q'] for r in allrows})
bad=[]
for r in allrows:
    ev=r['model_probability']*r['best_odds']-1
    if abs(ev-r['expected_value'])>0.002: bad.append(('ev',r))
    need={'high':0.05,'medium':0.08}.get(r['_q'])
    if r['status']=='value' and not (need is not None and r['expected_value']>=need-0.002 and r['edge'] is not None and r['edge']>0 and abs(r['edge'])<=0.25): bad.append(('value-rule',r))
    if r['status']=='no_value' and need is not None and r['expected_value']>=need+0.002 and (r['edge'] or 0)>0 and abs(r['edge'] or 0)<=0.25 and r['expected_value']<=0.5: bad.append(('missed-value',r))
    if r['_q']=='low' and r['status'] not in ('low_quality',): bad.append(('low-quality-flagged',r))
check('every status obeys the documented rules; EV = p*odds-1', not bad, bad[:3])
s,j,_=call('GET','/scanner?status=value&days=3',token=tok); check('scanner status=value consistent with insights', s==200 and j['meta']['model_reliable'] is True and j['meta']['total']==stat.get('value',0), (j['meta'],stat))

# ---- logout / delete account ----
s,j,_=call('POST','/auth/login',{'email':'tester@example.com','password':'longenough1'}); t=j['data']['tokens']['access_token']
s,j,_=call('POST','/auth/logout',token=t); check('logout', s==200)
s,j,_=call('GET','/auth/me',token=t); check('token dead after logout', s==401)
s,j,_=call('POST','/auth/login',{'email':'tester@example.com','password':'longenough1'}); t=j['data']['tokens']['access_token']
s,j,_=call('DELETE','/auth/me',{'password':'nope'},token=t); check('delete account needs correct password (403)', s==403,(s,j))
s,j,_=call('DELETE','/auth/me',{'password':'longenough1'},token=t); check('delete account ok', s==200,(s,j))
check('account data cascaded away', sql("SELECT COUNT(*) FROM watchlists w JOIN users u ON u.id=w.user_id WHERE u.email='tester@example.com'")=='0' and sql("SELECT COUNT(*) FROM users WHERE email='tester@example.com'")=='0')
print('\n%d/%d passed'%(sum(res),len(res)))
