"""Known-answer fixtures and a separate direct reconstruction from raw records."""
import datetime as dt
import gzip
import hashlib
import json
from collections import defaultdict
from fractions import Fraction
from pathlib import Path

ROOT=Path(__file__).resolve().parents[1]

def fixtures():
    import analyze as main
    done=[]
    def check(name,condition):
        assert condition,name
        done.append(name)
    check('constant_profile_earliest_tie',main.choose([5]*8,2,4)==2)
    check('known_beneficial_wait',main.choose([10,1],0,1)==1)
    check('wrong_profile_can_harm',[1,10][main.choose([10,1],0,1)]>[1,10][0])
    check('zero_delay',all(main.choose([9,1,5],i,0)==i for i in range(3)))
    check('direct_duration_sums',main.window_sums([2,3,7,11],2)==[5,10,18])
    check('midnight_crossing',main.window_sums([10]*48+[0]*48,2)[47]==10)
    check('season_boundaries',[main.season(dt.date(2024,m,1)) for m in range(1,13)]==['DJF','DJF','MAM','MAM','MAM','JJA','JJA','JJA','SON','SON','SON','DJF'])
    date=dt.date(2024,3,1)
    data={date-dt.timedelta(days=i):[i]*48 for i in range(-2,40)}
    selected=main.prior_dates(date,data)
    check('rolling_lag_and_calendar_window',len(selected)==28 and min(selected)==date-dt.timedelta(days=29) and max(selected)==date-dt.timedelta(days=2))
    before=main.mean_profile([data[d] for d in selected])
    for d in [date-dt.timedelta(days=1),date,date+dt.timedelta(days=1)]:data[d]=[99999]*48
    check('current_future_values_excluded',main.mean_profile([data[d] for d in main.prior_dates(date,data)])==before)
    check('exact_profile_arithmetic',main.mean_profile([[0]*48,[1]*48,[1]*48])==[Fraction(2,3)]*48)
    start=dt.datetime(2024,1,1,tzinfo=dt.timezone.utc)
    def row(i,value=1):
        a=start+dt.timedelta(minutes=30*i)
        return {'from':a.isoformat(),'to':(a+dt.timedelta(minutes=30)).isoformat(),'intensity':{'actual':value}}
    rows=[row(i) for i in range(48)]
    complete,audit=main.merge_records(rows+[row(0)])
    check('identical_duplicate',len(complete)==1 and audit['duplicate_intervals']==1)
    complete,audit=main.merge_records(rows[:-1]+[row(47,None)])
    check('missing_actual_excludes_day',not complete and len(audit['invalid_intervals'])==1)
    try:main.merge_records(rows+[row(0,7)])
    except ValueError:done.append('conflicting_duplicate_rejected')
    else:raise AssertionError('conflicting_duplicate_rejected')
    malformed=row(0);malformed['to']=(start+dt.timedelta(minutes=31)).isoformat()
    try:main.merge_records([malformed])
    except ValueError:done.append('unaligned_interval_rejected')
    else:raise AssertionError('unaligned_interval_rejected')
    return done

def raw_days():
    # Independent loader: timestamp keys and exact numeric values, no main-module calls.
    by_time={}
    for source in json.loads((ROOT/'data/SOURCES.json').read_text()):
        zipped=(ROOT/source['path']).read_bytes()
        assert hashlib.sha256(zipped).hexdigest()==source['compressed_sha256'].split(':')[1]
        blob=gzip.decompress(zipped)
        assert hashlib.sha256(blob).hexdigest()==source['sha256'].split(':')[1]
        for record in json.loads(blob)['data']:
            key=dt.datetime.fromisoformat(record['from'].replace('Z','+00:00'))
            if key in by_time:assert by_time[key]==record
            by_time[key]=record
    days={}
    day=dt.date(2023,1,1)
    while day<dt.date(2026,1,1):
        values=[]
        for slot in range(48):
            timestamp=dt.datetime.combine(day,dt.time(tzinfo=dt.timezone.utc))+dt.timedelta(minutes=slot*30)
            record=by_time.get(timestamp,{})
            value=record.get('intensity',{}).get('actual')
            if isinstance(value,bool) or not isinstance(value,(int,float)) or value<0:break
            values.append(Fraction(str(value)))
        if len(values)==48:days[day]=values
        day+=dt.timedelta(days=1)
    return days

def group(day):
    return {12:0,1:0,2:0,3:1,4:1,5:1,6:2,7:2,8:2,9:3,10:3,11:3}[day.month]

def profile(dates,days):
    return [sum(days[d][slot] for d in dates)/len(dates) for slot in range(48)]

def reconstruct():
    days=raw_days()
    year_dates=[d for d in days if d.year==2023]
    annual=profile(year_dates,days)
    seasons={g:profile([d for d in year_dates if group(d)==g],days) for g in range(4)}
    summaries=json.loads((ROOT/'results/R1.json').read_text())
    samples=json.loads((ROOT/'results/verification-sample.json').read_text())
    sample_by_day=defaultdict(list)
    for sample in samples:sample_by_day[dt.date.fromisoformat(sample['date'])].append(sample)
    total=defaultdict(lambda:{'cost':Fraction(),'base':Fraction(),'oracle':Fraction(),'delay':0,'max':0,'n':0,'higher':0,'equal':0,'lower':0})
    samples_checked=primary_checks=0
    for day in sorted(d for d in days if d.year in (2024,2025)):
        after=day+dt.timedelta(days=1)
        history=[d for d in days if day-dt.timedelta(days=29)<=d<=day-dt.timedelta(days=2)]
        if after not in days or len(history)<21:continue
        recent=profile(history,days)
        timeline=days[day]+days[after]
        predicted={'annual':annual*2,'seasonal':seasons[group(day)]+seasons[group(after)],'recent':recent*2}
        # Direct interval summation with sorted (cost,start) pairs is separate from
        # main's prefix-sum and stable-min implementation.
        def decision(duration,release,wait):
            costs={start:sum(timeline[start:start+duration]) for start in range(release,release+wait+1)}
            choices={'immediate':release,'hindsight':sorted((cost,start) for start,cost in costs.items())[0][1]}
            for name,values in predicted.items():
                choices[name]=sorted((sum(values[start:start+duration]),start) for start in costs)[0][1]
            return choices,{name:costs[start] for name,start in choices.items()}
        for sample in sample_by_day[day]:
            starts,costs=decision(sample['duration'],sample['release'],sample['wait'])
            assert starts==sample['starts'],(sample,starts)
            assert all(abs(float(costs[p])-sample['cost_sums'][p])<1e-10 for p in costs)
            samples_checked+=len(starts)
        for release in range(48):
            starts,costs=decision(6,release,12)
            for policy,cost in costs.items():
                row=total[(str(day.year),policy)]
                row['cost']+=cost;row['base']+=costs['immediate'];row['oracle']+=costs['hindsight']
                row['delay']+=starts[policy]-release;row['max']=max(row['max'],starts[policy]-release)
                row['n']+=1
                row['higher' if cost>costs['immediate'] else 'lower' if cost<costs['immediate'] else 'equal']+=1
                primary_checks+=1
    fields_checked=0
    for (year,policy),value in total.items():
        got=summaries['primary'][year]['policies'][policy]
        difference=value['base']-value['cost'];gap=value['base']-value['oracle']
        expected={'jobs':value['n'],'mean_intensity_gco2_per_kwh':round(float(value['cost']/value['n']/6),6),
                  'absolute_reduction_gco2_per_kwh':round(float(difference/value['n']/6),6),
                  'percent_reduction':round(float(100*difference/value['base']),6),
                  'mean_delay_hours':round(value['delay']/value['n']/2,6),'max_delay_hours':value['max']/2,
                  'higher_jobs':value['higher'],'equal_jobs':value['equal'],'lower_jobs':value['lower'],
                  'higher_percent':round(100*value['higher']/value['n'],6),
                  'equal_percent':round(100*value['equal']/value['n'],6),
                  'lower_percent':round(100*value['lower']/value['n'],6),
                  'hindsight_gap_fraction':round(float(difference/gap),6)}
        assert got==expected,(year,policy,got,expected)
        fields_checked+=len(expected)
    no_delay_checks=0
    for year in summaries['years'].values():
        for duration in year.values():
            for policy,value in duration['w0'].items():
                assert value['percent_reduction']==0 and value['higher_jobs']==0 and value['lower_jobs']==0 and value['mean_delay_hours']==0
                no_delay_checks+=1
    return {'independent_sample_policy_checks':samples_checked,'independent_primary_job_policy_checks':primary_checks,
            'independent_primary_aggregate_fields':fields_checked,'no_delay_policy_checks':no_delay_checks}

if __name__=='__main__':
    fixture_names=fixtures()
    result={'passed':True,'synthetic_fixtures':len(fixture_names),'fixture_names':fixture_names,**reconstruct()}
    (ROOT/'results/validation.json').write_text(json.dumps(result,indent=2,sort_keys=True)+'\n')
    print(json.dumps(result,indent=2))
