"""Independently rebuild exported sessions from raw bars, without fitting any model."""
if not __debug__:
    raise RuntimeError("Validation requires assertions; run Python without -O or PYTHONOPTIMIZE.")
from collections import deque, Counter
from datetime import datetime, timedelta
from pathlib import Path
import csv, json, math, hashlib

ROOT=Path(__file__).resolve().parents[1]
(ROOT/'results').mkdir(parents=True,exist_ok=True)
(ROOT/'results/export_audit.json').write_text(json.dumps({'status':'INCOMPLETE','reason':'Audit started; no success established by this attempt.'}))
def rows(name):
    with (ROOT/'data'/name).open() as f: return list(csv.DictReader(f))
def stamp(t): return t.strftime('%Y.%m.%d %H:%M') if t else ''
raw=rows('raw_bars.csv'); audit=rows('session_audit.csv'); sessions=rows('sessions.csv')
calendar=[datetime(2019,1,1)+timedelta(days=i) for i in range((datetime(2025,1,1)-datetime(2019,1,1)).days)]
assert [r['session_date'] for r in audit]==[d.strftime('%Y.%m.%d') for d in calendar if d.weekday()<5], 'Missing, duplicated or reordered audit date'
bars={datetime.strptime(r['bar_time'],'%Y.%m.%d %H:%M'): tuple(float(r[k]) for k in ('open','high','low','close')) for r in raw}
assert len(bars)==len(raw), 'Duplicate timestamps'
assert list(bars)==sorted(bars), 'Nonmonotone bars'
assert min(bars).year==2019 and max(bars).year==2024
# Reconstruct the cutoff actually used by the exporter, not a new cutoff.
manifest={}
for line in (ROOT/'data/native_manifest.txt').read_text().splitlines():
    key,separator,value=line.partition('=')
    assert separator and key and key not in manifest, 'Malformed or duplicate manifest field'
    manifest[key]=value
times={}
for key in ('first_bar','last_bar','closed_through','export_local'):
    times[key]=datetime.strptime(manifest[key],'%Y.%m.%d %H:%M')
    assert stamp(times[key])==manifest[key], 'Noncanonical manifest timestamp'
assert times['first_bar']==min(bars) and times['last_bar']==max(bars), 'Manifest/raw history endpoints differ'
bound=times['closed_through']
assert datetime(2019,1,1)<=min(bars)<=bound<=max(bars)<datetime(2025,1,1), 'Manifest closure outside copied study history'
assert bound<=times['export_local'], 'Closure cutoff follows recorded export time'
for key,count in (('bar_count',len(raw)),('weekday_candidates',len(audit)),('estimation_or_holdout_rows',len(sessions))):
    assert manifest[key]==str(count), 'Manifest/export counts differ'
def window(t):
    b=[bars[t+timedelta(hours=h)] for h in range(8) if t+timedelta(hours=h) in bars]
    valid=len(b)==8 and all(all(math.isfinite(x) and x>0 for x in q) and q[2]<=min(q[0],q[3])<=max(q[0],q[3])<=q[1] for q in b)
    return b,valid
rangeq=deque(maxlen=20); eventq=deque(maxlen=60)
expected=[]; counts=Counter();checks=0
for r in audit:
    d=datetime.strptime(r['session_date'],'%Y.%m.%d'); assert d.weekday()<5
    p,pv=window(d); t,tv=window(d+timedelta(hours=8)); decision=d+timedelta(hours=8); end=d+timedelta(hours=16)
    assert int(r['year'])==d.year and r['decision_time']==stamp(decision) and r['label_end']==stamp(end), 'Audit identity/timing mismatch'
    a=sum(x[1] for x in rangeq)/20 if len(rangeq)==20 else 0
    pre_range=max(x[1] for x in p)-min(x[2] for x in p) if p else 0
    f=pv and decision<=bound and len(rangeq)==20 and a>0 and pre_range>0
    e=f and tv and end<=bound; c=e and len(eventq)==60
    reason=('pre_missing_or_invalid' if not pv else 'pre_not_closed' if decision>bound else
            'zero_pre_range' if pre_range<=0 else 'insufficient_prior20' if len(rangeq)<20 else
            'nonpositive_scale' if a<=0 else 'target_missing_or_invalid' if not tv else
            'target_not_closed' if end>bound else 'insufficient_previous60' if len(eventq)<60 else 'eligible')
    assert r['reason']==reason, 'Audit exclusion reason mismatch'
    values={'pre_count':len(p),'target_count':len(t),'pre_valid':int(pv),'target_valid':int(tv),'closed_target':int(end<=bound),
            'prior20_count':len(rangeq),'prior20_mean':a,'previous60_count':len(eventq),'previous60_events':sum(x[1] for x in eventq),
            'forecast_eligible':int(f),'estimation_eligible':int(e),'comparison_eligible':int(c)}
    for k,v in values.items():
        assert abs(float(r[k])-v)<1e-13,(r['session_date'],k,r[k],v)
        checks+=1
    for q,label in ((rangeq,'scale'),(eventq,'baseline')):
        assert r[label+'_oldest_end']==stamp(q[0][0] if q else None)
        assert r[label+'_latest_end']==stamp(q[-1][0] if q else None)
        assert not q or q[-1][0]<decision
        checks+=3
    counts[(d.year,r['reason'])]+=1
    if e and d.year>=2020:
        expected.append(dict(session_date=r['session_date'],year=d.year,decision_time=stamp(decision),label_end=stamp(end),
             pre_open=p[0][0],pre_high=max(x[1] for x in p),pre_low=min(x[2] for x in p),pre_close=p[-1][3],prior20_mean=a,
             target_high=max(x[1] for x in t),target_low=min(x[2] for x in t),previous60_count=len(eventq),
             previous60_events=sum(x[1] for x in eventq),comparison_eligible=int(c)))
    if e: eventq.append((end,int(max(x[1] for x in t)-min(x[2] for x in t)>1.25*a)))
    if tv and end<=bound: rangeq.append((end,max(x[1] for x in t)-min(x[2] for x in t)))
assert len(expected)==len(sessions)
for e,r in zip(expected,sessions):
    for k,v in e.items():
        assert (r[k]==v if isinstance(v,str) else abs(float(r[k])-v)<1e-13),(r['session_date'],k)
        checks+=1
assert max(r['label_end'] for r in sessions if int(r['year'])<=2023)<min(r['decision_time'] for r in sessions if int(r['year'])==2024)
out={'input_sha256':{name:hashlib.sha256((ROOT/'data'/name).read_bytes()).hexdigest() for name in ('raw_bars.csv','sessions.csv','session_audit.csv','native_manifest.txt')},'status':'PASS','checks':checks,'raw_bars':len(raw),'audited_weekdays':len(audit),'study_rows':len(sessions),'closed_through':stamp(bound),
     'rows_by_year':dict(sorted(Counter(r['year'] for r in sessions).items())),
     'audit_by_year_and_reason':{f'{y}: {reason}':n for (y,reason),n in sorted(counts.items())},
     'forecast_with_missing_target':sum(int(r['forecast_eligible'])==1 and int(r['estimation_eligible'])==0 for r in audit),
     'chronology':'PASS: every queued outcome resolved before decision; no 2025 rows; train labels resolve before holdout'}
(ROOT/'results/export_audit.json').write_text(json.dumps(out,indent=2))
print(json.dumps(out,indent=2))
