"""Audit frozen traces and assemble reference counts without changing rules."""
from collections import Counter,defaultdict
import csv
import json
import math
import shutil
from common import OUT,load,read,save,sha
from fall_candidate import features

specs=read(OUT/'specs.json');config=read(OUT/'conditions.json');table=[]
self_unique={};self_regions=defaultdict(Counter);case_counts=defaultdict(Counter)
totals=defaultdict(Counter);tracking_totals=Counter();audited=0;full=0;identifier_corrections=0
for clip,spec in specs.items():
    rows,selected,_,cache,mapping=load(clip);dest=OUT/'download'/clip/'data'
    summary=read(dest/'summary.json');rawtrace=dest/'diagnostics.jsonl'
    assert summary['conditions_sha256']==sha(OUT/'conditions.json') and summary['trace_sha256']==sha(rawtrace)
    derived=read(OUT/'work'/clip/'tracking30.json');tv=read(OUT/'work'/clip/'tracking_validation.json')
    assert tv['status']=='PASS' and sha(OUT/'work'/clip/'tracking30.json')==tv['derived_sha256']
    tracking_totals['replay30' if spec['tracking_mode']=='replay30' else 'stored10']+=1
    tracking_totals['changed_person_id_slots']+=tv['changes']
    if spec['tracking_mode']=='replay30':tracking_totals['verified_original_candidate_ids']+=tv['replay10_candidates']
    counts=Counter();old_pts=None;last_event=None;last_hit=None;event_ids=set();removed_causes=Counter();positive_details=Counter();matches=0
    output=dest/'diagnostics_checked.jsonl';assert not output.exists()
    with rawtrace.open() as source,output.open('x') as target:
        for line,row in zip(source,rows):
            r=json.loads(line);full+=1
            assert (r['sample'],r['source_frame'],r['seconds'])==(row['sample'],row['source_frame'],row['seconds'])
            assert old_pts is None or r['seconds']>old_pts;old_pts=r['seconds']
            assert r['displayed']==(r['sample'] in selected)
            assert len(r['people'])==len(row['persons'])
            for before,p in zip(row['persons'],r['people']):
                expected=features(dict(before,track_id=derived[str(row['sample'])][str(before['query'])]),config)
                assert json.loads(json.dumps(expected))==p
            # First raw trace used 'query' for baseline source-order indices. Preserve
            # that raw evidence and publish explicit raw_order plus the original q.
            for baseline in r['baseline'].values():
                for p in baseline['people']:
                    order=p.pop('query');p['raw_order']=order;p['query']=row['persons'][order]['query']
                    identifier_corrections+=order!=p['query']
            c=r['candidate'];a=r['baseline']['matched_scores']
            assert c['hit']==any(p['hit'] for p in r['people'])
            assert c['observable']==any(p['observable'] for p in r['people'])
            if c['hit']:last_hit=r['seconds']
            assert c['last_hit']==last_hit
            assert c['display']==(last_hit is not None and r['seconds']-last_hit<=1+1e-9)
            assert c['display_hold']==(c['display'] and not c['hit'])
            for colored in c['colors']:
                person=next(p for p in r['people'] if p['query']==colored['query'])
                assert colored['bbox']==person['bbox'] and person['eligible']
                if colored['state']=='RED_CURRENT':assert person['hit']
                elif colored['state']=='YELLOW_HOLD':assert not person['hit'] and c['display']
            if c['event']:
                assert c['hit'] and c['event']['mock'] and c['event']['id'] not in event_ids
                assert last_event is None or r['seconds']-last_event>=config['cooldown_seconds']-1e-9
                event_ids.add(c['event']['id']);last_event=r['seconds']
            target.write(json.dumps(r,ensure_ascii=False,separators=(',',':'),allow_nan=False)+'\n')
            if not r['displayed']:continue
            audited+=1;matches+=1;label=r['annotation']['reference']
            for key,value in [('observations',1),('candidate',c['hit']),('matched',a['current_hit']),('site',r['baseline']['site_scores']['current_hit']),('added',c['hit'] and not a['current_hit']),('removed',a['current_hit'] and not c['hit']),('events',bool(c['event'])),('observable',c['observable'])]:counts[key]+=value
            domain='self' if clip.startswith('SELF_') else 'legacy';totals[domain].update({k:v for k,v in [('observations',1),('candidate',c['hit']),('matched',a['current_hit']),('added',c['hit'] and not a['current_hit']),('removed',a['current_hit'] and not c['hit']),('events',bool(c['event']))]})
            if domain=='self':
                key=r['annotation']['dedupe_key'];entry=self_unique.setdefault(key,dict(base=False,candidate=False,copies=0))
                entry['base']|=a['current_hit'];entry['candidate']|=c['hit'];entry['copies']+=1
                for k,v in [('observations',1),('base',a['current_hit']),('candidate',c['hit'])]:self_regions[r['annotation']['region']][k]+=v
            if label=='positive_reference':
                for k,v in [('observations',1),('base',a['current_hit']),('candidate',c['hit']),('removed',a['current_hit'] and not c['hit']),('added',c['hit'] and not a['current_hit']),('observable',c['observable'])]:positive_details[k]+=v;totals['positive_reference'][k]+=v
                for p in r['people']:
                    if p['eligible']:
                        positive_details['eligible_person_slots']+=1
                        for reason in p['reasons']:positive_details['reason_'+reason]+=1
                if a['current_hit'] and not c['hit']:
                    active={p['query'] for p in a['people'] if p['current_hit']}
                    for p in r['people']:
                        if p['query'] in active:
                            for reason in p['reasons']:removed_causes[reason]+=1
            for case_id in r['annotation']['case_ids']:
                for k,v in [('observations',1),('base',a['current_hit']),('candidate',c['hit'])]:case_counts[case_id][k]+=v
    assert matches==len(selected)
    for k in ('observations','candidate','matched','site','added','removed','events','observable'):assert counts[k]==summary['counts'].get(k,0),(clip,k)
    pv=read(cache.parent/'reports/pose_summary.json')
    data=dict(status='PASS',observations=matches,source_map_preserved=True,score_and_rule_recomputation='all current observations',
        time_and_event_separation=True,diagnostics_sha256=sha(output),raw_diagnostics_sha256=sha(rawtrace),
        identifier_contract='checked trace: baseline raw_order is source slot; query is original model q',
        positive_reference=dict(positive_details),positive_removed_causes=dict(removed_causes),
        model_fingerprint={k:pv[k] for k in ('model','engine_sha256','source_files','tracking','preprocess') if k in pv},
        input_fingerprint=dict(input_sha256=spec['input_sha256'],cache_sha256=spec['cache_sha256'],rgb_hashes=spec['rgb_hashes_remote']))
    save(dest/'technical_validation.json',data)
    for name in ('tracking_changes.json',):shutil.copy2(OUT/'work'/clip/name,dest/name)
    item=dict(input=clip,tracking=spec['tracking_mode'],**dict(counts),positive_reference_observations=positive_details['observations'],positive_baseline=positive_details['base'],positive_candidate=positive_details['candidate'],positive_removed=positive_details['removed'],positive_added=positive_details['added'])
    table.append(item)
assert audited==read(OUT/'execution_contract.json')['displayed_observations']==9981 and len(table)==28
root=OUT/'download'
with (root/'input_comparison.csv').open('x',encoding='utf-8-sig',newline='') as f:
    writer=csv.DictWriter(f,fieldnames=table[0]);writer.writeheader();writer.writerows(table)
save(root/'case_comparison.json',dict(case_counts))
save(root/'comparison.json',dict(status='COMPARED_NOT_ACCEPTED',inputs=table,totals={k:dict(v) for k,v in totals.items()},
    self_reference_regions={k:dict(v) for k,v in self_regions.items()},
    self_deduplicated=dict(unique_selected_review_frames=len(self_unique),duplicate_input_observations=sum(v['copies']-1 for v in self_unique.values()),
        baseline_hit_union=sum(v['base'] for v in self_unique.values()),candidate_hit_union=sum(v['candidate'] for v in self_unique.values()),
        limitation='Deduplication of identical review Frame only; temporally nearby/overlapping action cases remain correlated'),
    tracking=dict(tracking_totals),accuracy_claim=False,render_gate='USER_DECISION_PENDING'))
save(root/'validation/diagnostic_validation.json',dict(status='PASS',inputs=28,displayed_observations=audited,
    full_cache_observations=full,current_rule_recomputation=True,frame_pts_alignment=True,
    current_display_event_separation=True,identifier_schema_corrected_slots=identifier_corrections,
    source_cache_modified=False,model_reexecution=False,video_validation='NOT_STARTED'))
for name in ('conditions.json','execution_contract.json','references.json','DESIGN.ko.md'):
    shutil.copy2(OUT/name,root/name)
shutil.copy2(OUT/'reports/synthetic_validation.json',root/'validation/synthetic_validation.json')
print(json.dumps(dict(status='PASS',displayed=audited,full=full,totals={k:dict(v) for k,v in totals.items()},tracking=dict(tracking_totals),self_unique=len(self_unique)),ensure_ascii=False),flush=True)
