from pathlib import Path
from datetime import datetime,timezone
from fractions import Fraction
from collections import defaultdict
from functools import lru_cache
import csv,json,hashlib,math,statistics,sys
import replay_math as own
own.raw_year=lru_cache(maxsize=None)(own.raw_year)
base=Path('work/storage-test');result=base/'results';out=Path('work/storage-independent-review')
plan=json.loads((base/'plan-v2.json').read_text());approval=json.loads((out/'execution-approval-v2.json').read_text());selected=json.loads((out/'prespecified-replay-checks-v2.json').read_text())['selected_full_slot_replays']
issues=[];comparisons=0

def require(ok,label,detail=None):
    global comparisons
    comparisons+=1
    if not ok:issues.append({'check':label,'detail':detail})

def eqnum(value,expected,label):
    actual=float(value);expect=float(expected)
    require(math.isfinite(actual) and math.isclose(actual,expect,rel_tol=1e-12,abs_tol=1e-10),label,{'actual':actual,'expected':expect})

def h(p):return hashlib.sha256(p.read_bytes()).hexdigest()
metadata=json.loads((result/'run-metadata.json').read_text());lock=json.loads((result/'EXECUTION-LOCK.json').read_text())
for k,filename in [('plan_sha256','plan-v2.json'),('runner_sha256','run_storage_test.py')]:
 require(metadata[k]==lock[k]==approval[k]==h(base/filename),'hash binding '+k)
require(metadata['approval_sha256']==h(out/'execution-approval-v2.json'),'approval hash')
require(not metadata['plan_modified_during_run'],'plan unchanged during run')
require(metadata['physical_timeline_claim_allowed'] is False,'physical scope flag')
require(datetime.fromisoformat(metadata['started_utc'])>=datetime.fromisoformat(approval['reviewed_utc'])>=datetime.fromisoformat(plan['committed_before_data_outcomes_utc']),'revision/review/execution temporal order')
for r,a in zip(metadata['raw_inputs'],approval['raw_inputs']):
 p=base/'raw'/f"eco2mix-{r['year']}-selected.csv"
 require(r==a and r['sha256']==h(p) and r['bytes']==p.stat().st_size,'raw input unchanged '+str(r['year']))
require(h(base/'raw/dataset-metadata.json')==approval['metadata_sha256'],'metadata unchanged')
conditions=json.loads((result/'year-scenario-results.json').read_text());across=json.loads((result/'across-year-summaries.json').read_text());perms=list(csv.DictReader((result/'permutation-results.csv').open(newline='')))
require(len(conditions)==50 and metadata['scenario_year_conditions']==50,'50 conditions')
require(len(perms)==49950 and metadata['permutations']==49950,'49950 permutations')
lookup={(int(x['year']),x['scenario']):x for x in conditions};require(len(lookup)==50,'unique condition keys')
groups=defaultdict(list)
for p in perms:groups[(int(p['year']),p['scenario'])].append(p)
require(set(groups)==set(lookup),'permutation condition keys')
mathdata={};originals=[];replays=[];calc={}
for scenario in plan['scenarios']:
 for year in plan['data']['years']:
  sid=scenario['id'];key=(year,sid);r=lookup[key];d=own.build(year,scenario);mathdata[key]=d;exact=own.direct_excursion(d);D=int(r['normalization_denominator']);R=int(r['original_range_numerator']);C=exact['capacity_exact'];mean=Fraction(d['annual_sample_sums'][0],len(d['rows']));nslots=len(d['rows'])
  require(C==Fraction(R,2*D),'original exact rational '+str(key))
  require(r['exact_cycle_residual_numerator']=='0' and d['N_sum']==0,'exact cycle zero '+str(key))
  require(r['original_date_order']==d['dates'],'original nominal date order '+str(key))
  require(r['nominal_slots']==nslots and r['nominal_days']==len(d['days']),'retained sample/day counts '+str(key))
  for k,v in zip(('sum_consumption_MW_samples','sum_wind_MW_samples','sum_solar_MW_samples'),d['annual_sample_sums']):require(r[k]==v,'source sums '+k+str(key))
  vals=[N for day in d['dates'] for N in d['days'][day]];dd=d['denominator']
  eqnum(r['mean_demand_MW'],mean,'mean demand '+str(key))
  eqnum(r['residual_population_variance_MW2'],Fraction(sum(v*v for v in vals),nslots*dd*dd),'residual variance '+str(key))
  eqnum(r['residual_positive_peak_MW'],Fraction(max(vals),dd),'residual positive peak '+str(key))
  eqnum(r['residual_absolute_peak_MW'],Fraction(max(map(abs,vals)),dd),'residual absolute peak '+str(key))
  require(all(Fraction(sum(N for day in d['dates'] if int(day[5:7])==month for N in d['days'][day]),dd)==Fraction(int(r['month_net_numerators'][str(month)]),D) for month in range(1,13)),'exact monthly sums '+str(key))
  pg=groups[key];require(len(pg)==999 and sorted(int(p['replicate']) for p in pg)==list(range(999)),'complete unique replicate IDs '+str(key))
  capacities=[]
  for p in pg:
   rep=int(p['replicate']);text=f'incoprea-storage-v2|year={year}|scenario={sid}|replicate={rep}';seed=int.from_bytes(hashlib.sha256(text.encode()).digest()[:8],'big');require(int(p['seed'])==seed,'seed '+str(key)+(str(rep)))
   cap=Fraction(int(p['range_numerator']),2*D);capacities.append(cap)
   eqnum(p['capacity_nominal_MWh_equivalent'],cap,'permutation MWh '+str(key)+str(rep));eqnum(p['capacity_nominal_GWh_equivalent'],cap/1000,'permutation GWh '+str(key)+str(rep));eqnum(p['equivalent_mean_demand_nominal_hours'],cap/mean,'permutation hours '+str(key)+str(rep))
  med=own.linear_quantile(capacities,Fraction(1,2));lo=own.linear_quantile(capacities,Fraction(25,1000));hi=own.linear_quantile(capacities,Fraction(975,1000));upper=Fraction(1+sum(x>=C for x in capacities),1000);lower=Fraction(1+sum(x<=C for x in capacities),1000);two=min(Fraction(1),2*min(upper,lower));ratio=C/med
  expected={'original_nominal_GWh_equivalent':C/1000,'original_equivalent_mean_demand_nominal_hours':C/mean,'permutation_median_nominal_GWh_equivalent':med/1000,'permutation_q025_nominal_GWh_equivalent':lo/1000,'permutation_q975_nominal_GWh_equivalent':hi/1000,'permutation_min_nominal_GWh_equivalent':min(capacities)/1000,'permutation_max_nominal_GWh_equivalent':max(capacities)/1000,'original_to_permutation_median_ratio':ratio,'difference_original_minus_median_nominal_GWh_equivalent':(C-med)/1000,'difference_original_minus_median_nominal_hours':(C-med)/mean,'upper_reference_rank':upper,'lower_reference_rank':lower,'two_sided_reference_rank':two}
  for k,v in expected.items():eqnum(r[k],v,k+str(key))
  for k,v in {'original_above_permutation_median':C>med,'original_below_permutation_median':C<med,'original_outside_central95_reference':C<lo or C>hi,'permutations':999}.items():require(r[k]==v,k+str(key))
  calc[key]={'ratio':float(ratio),'two_sided':two,'above':C>med,'below':C<med,'outside':C<lo or C>hi}
  originals.append({'year':year,'scenario':sid,'independent_exact_capacity_nominal_MWh_equivalent':str(C),'author_exact_capacity_matches':C==Fraction(R,2*D),'independent_residual_denominator':str(dd),'exact_cycle_zero':True,'nominal_slots':nslots})
for item in selected:
 key=(item['year'],item['scenario']);d=mathdata[key];seed,dates=own.permutation_dates(d,item['replicate']);x=own.direct_excursion(d,dates);r=lookup[key];p=next(p for p in groups[key] if int(p['replicate'])==item['replicate']);D=int(r['normalization_denominator']);authorcap=Fraction(int(p['range_numerator']),2*D);orderhash=hashlib.sha256(','.join(dates).encode()).hexdigest()
 require(x['capacity_exact']==authorcap,'selected exact direct-slot permutation '+str(item));require(seed==int(p['seed']) and orderhash==p['order_sha256'],'selected permutation order/seed '+str(item));require(sorted(dates)==d['dates'],'selected day multiset '+str(item));require(all(date[:7]==orig[:7] for date,orig in zip(dates,d['dates'])),'selected month membership '+str(item))
 replays.append({**item,'seed':seed,'capacity_exact_nominal_MWh_equivalent':str(x['capacity_exact']),'author_exact_capacity_matches':x['capacity_exact']==authorcap,'order_sha256':orderhash,'order_matches':orderhash==p['order_sha256'],'direct_slots':len(d['rows']),'exact_cycle_zero':x['final_prefix']==0})
primary=sorted([(k,v) for k,v in calc.items() if k[1]=='primary_70_30'],key=lambda kv:kv[1]['two_sided']);prev=Fraction(0)
for i,(key,v) in enumerate(primary):
 prev=max(prev,min(Fraction(1),(10-i)*v['two_sided']));eqnum(lookup[key]['two_sided_reference_rank_holm_descriptive'],prev,'Holm descriptive '+str(key))
for a in across:
 group=[v for k,v in calc.items() if k[1]==a['scenario']];ratios=[x['ratio'] for x in group];logs=[math.log(x) for x in ratios]
 vals={'years':len(group),'median_original_to_reference_ratio':statistics.median(ratios),'minimum_ratio':min(ratios),'maximum_ratio':max(ratios),'years_above_reference_median':sum(x['above'] for x in group),'years_below_reference_median':sum(x['below'] for x in group),'years_outside_central95_reference':sum(x['outside'] for x in group),'mean_year_log_ratio':statistics.mean(logs),'sd_year_log_ratio':statistics.stdev(logs)}
 for k,v in vals.items():eqnum(a[k],v,'across '+a['scenario']+k)
control=json.loads((result/'deterministic-control.json').read_text());require([x['capacity_MWh'] for x in control]==[1,.5],'analytic control capacities');require([x['residual_MW'] for x in control]==[[1,1,-1,-1],[1,-1,1,-1]],'analytic residual sequences')
for r in control:
 seq=r['residual_MW'];n=len(seq);mu=Fraction(sum(seq),n);var=sum((Fraction(v)-mu)**2 for v in seq)/n;prefix=[Fraction(0)]
 for v in seq:prefix.append(prefix[-1]+Fraction(v,2))
 require(mu==0 and var==1 and max(seq)==1 and min(seq)==-1,'analytic moments '+r['label']);require(max(prefix)-min(prefix)==Fraction(str(r['capacity_MWh'])),'analytic independent prefix range '+r['label']);require([float(x) for x in prefix]==r['cumulative_nominal_MWh'],'analytic trajectory '+r['label'])
output={'review_type':'internal Codex independent numerical audit, not external peer review','checked_utc':datetime.now(timezone.utc).isoformat(),'verdict':'PASS' if not issues else 'FAIL','scope':'sealed v2 nominal-calendar chronology-sensitivity diagnostic only; no physical timeline/actual French sizing claim','plan_sha256':h(base/'plan-v2.json'),'runner_sha256':h(base/'run_storage_test.py'),'independent_math_script_sha256':h(out/'replay_math.py'),'audit_script_sha256':h(out/'audit_results.py'),'comparison_count':comparisons,'issues':issues,'conditions':len(conditions),'permutations':len(perms),'independently_recomputed_original_conditions':originals,'preselected_full_slot_replays':replays,'across_year_summaries_verified':across,'limitations':['Seven selected full-slot permutation capacities were independently regenerated, not all49950; every retained row seed, unit conversion and every output summary was checked.','Source physical UTC timeline remains unresolved; v2 uses declared nominal slot weights.','This checks internal numerical consistency and declared model implementation, not independent validation of source measurements or external peer review.']}
(out/'independent-numerical-audit-v2.json').write_text(json.dumps(output,indent=2)+'\n')
print(json.dumps({k:v for k,v in output.items() if k not in ('independently_recomputed_original_conditions','preselected_full_slot_replays','across_year_summaries_verified')},indent=2));print(json.dumps(across,indent=2))
if issues:sys.exit(1)
