import hashlib
import json
import os
from pathlib import Path
import statistics
import sys
import time
from inputs import OUT,evidence
from decision import Candidate
from seed_follow import SeedFollow

sys.path.insert(0,'/baseline')
from features import Settings
from state import Episode


def stats(rows):
    shown=[r for r in rows if r['displayed']];per={}
    for label in ('positive_reference','negative_reference','unknown','conflict_or_pending'):
        values=[r for r in shown if r['reference']==label]
        per[label]=dict(N=len(values),observable=sum(r['observable'] for r in values),
            hit=sum(r['hit'] for r in values),display=sum(r['episode']['display'] for r in values),
            events=sum(r['episode']['event'] for r in values))
    longest_gap=0.;gap_start=None;previous=None;hit_duration=0.;spans=[];span=None
    for r in shown:
        continuous=previous is not None and 0<r['seconds']-previous['seconds']<=.15+1e-8
        if continuous and r['hit'] and previous['hit']:hit_duration+=r['seconds']-previous['seconds']
        if r['reference']=='positive_reference':
            if not continuous or previous['reference']!='positive_reference':
                if span:spans.append(span)
                span=dict(start_frame=r['source_frame'],start_seconds=r['seconds'],first_hit_delay=None)
            if r['hit'] and span['first_hit_delay'] is None:span['first_hit_delay']=r['seconds']-span['start_seconds']
            if not r['hit']:
                if gap_start is None or not continuous:gap_start=r['seconds']
                longest_gap=max(longest_gap,r['seconds']-gap_start)
            else:gap_start=None
        else:
            gap_start=None
            if span:spans.append(span);span=None
        previous=r
    if span:spans.append(span)
    return dict(displayed=len(shown),observable=sum(r['observable'] for r in shown),
        raw_frames=sum(r['raw'] for r in shown),condition_frames=sum(r['hit'] for r in shown),
        display_frames=sum(r['episode']['display'] for r in shown),
        test_events=[dict(source_frame=r['source_frame'],seconds=r['seconds'],reference=r['reference']) for r in shown if r['episode']['event']],
        preceding_events=[dict(source_frame=r['source_frame'],seconds=r['seconds']) for r in rows if not r['displayed'] and r['episode']['event']],
        reference=per,condition_observed_interval_seconds=hit_duration,
        positive_longest_observed_no_hit_span_seconds=longest_gap,approximate_reference_spans=spans)


def main():
    clip=os.environ['CLIP'];dest=OUT/'download'/clip/'data';dest.mkdir(parents=True,exist_ok=True)
    candidates=json.loads((OUT/'candidates.json').read_text());spec=json.loads((OUT/'specs.json').read_text())[clip]
    raw=(OUT/'work'/clip/'features.jsonl').read_bytes()
    feature_hash=hashlib.sha256(raw).hexdigest();expected=json.loads((OUT/'work'/clip/'features_summary.json').read_text())
    assert feature_hash==expected['sha256']
    views=[json.loads(line) for line in raw.splitlines()]
    result=dict(clip=clip,feature_sha256=feature_hash,candidate_manifest_sha256=hashlib.sha256((OUT/'candidates.json').read_bytes()).hexdigest(),
        cache_sha256=spec['cache_sha256'],candidates={},reference_warning='User anchors are not exact GT. Display is not current evidence.')
    complete=dest/'summary.json'
    if complete.exists():
        existing=json.loads(complete.read_text())
        assert existing['candidate_manifest_sha256']==result['candidate_manifest_sha256'] and existing['feature_sha256']==feature_hash
        for c in existing['candidates'].values():assert hashlib.sha256((dest/c['trace']).read_bytes()).hexdigest()==c['sha256']
        print('REUSED',clip);return
    for candidate in candidates['candidates']:
        cls=SeedFollow if candidate['config'].get('family')=='seed_follow' else Candidate
        engine=cls(candidate['config']);episode=Episode(Settings());outputs=[];times=[]
        for r in views:
            start=time.perf_counter();v=engine.step(r)
            ep=episode.step(r['seconds'],v['hit'],v['quiet']);times.append((time.perf_counter()-start)*1000)
            outputs.append(dict(sample=r['sample'],source_frame=r['source_frame'],seconds=r['seconds'],displayed=r['displayed'],
                reference=r['reference'],case_ids=r['case_ids'],dedupe_key=r['dedupe_key'],review_frame=r['review_frame'],
                episode=ep,**v))
        name=candidate['id']+'_diagnostics.jsonl'
        with (dest/name).open('w') as f:
            for r in outputs:f.write(json.dumps(r,separators=(',',':'))+'\n')
        summary=stats(outputs);summary.update(trace=name,sha256=hashlib.sha256((dest/name).read_bytes()).hexdigest(),
            decision_p95_ms=statistics.quantiles(times,n=100,method='inclusive')[94],decision_max_ms=max(times))
        result['candidates'][candidate['id']]=summary
    if spec['baseline']:
        baseline=[json.loads(line) for line in evidence(spec['baseline']).read_text().splitlines()]
        assert [(r['sample'],r['source_frame'],r['seconds'],r['displayed']) for r in baseline]==[(r['sample'],r['source_frame'],r['seconds'],r['displayed']) for r in views]
        result['baseline']=dict(sha256=hashlib.sha256(evidence(spec['baseline']).read_bytes()).hexdigest(),
            condition_frames=sum(bool(r['current_rules']) for r in baseline if r['displayed']),
            display_frames=sum(r['episode']['display'] for r in baseline if r['displayed']),
            test_events=sum(len(r['events']) for r in baseline if r['displayed']))
    else:result['baseline']=None
    complete.write_text(json.dumps(result,ensure_ascii=False,indent=2))
    print(json.dumps(dict(clip=clip,candidates={k:{x:v[x] for x in ('displayed','observable','condition_frames','display_frames','test_events')} for k,v in result['candidates'].items()})),flush=True)


if __name__=='__main__':main()
