"""Deterministic sensitivity audit, using pinned source snapshots and audited IDs."""
import csv
import hashlib
import json
import math
from pathlib import Path
from statistics import fmean
import openpyxl

ROOT=Path(__file__).resolve().parents[1]
DATA=ROOT/'data'
OUT=ROOT/'results'
OUT.mkdir(exist_ok=True)

def write_json(name,obj):
    (OUT/name).write_text(json.dumps(obj,indent=2,sort_keys=True,ensure_ascii=False,allow_nan=False)+'\n')

def write_csv(name,rows):
    with (OUT/name).open('w',newline='') as f:
        writer=csv.DictWriter(f,fieldnames=list(rows[0]));writer.writeheader();writer.writerows(rows)

def positive(v):
    return isinstance(v,(int,float)) and not isinstance(v,bool) and math.isfinite(v) and v>0

sources=json.loads((DATA/'sources.json').read_text())
for name,entry in sources['files'].items():
    raw=(DATA/name).read_bytes()
    assert len(raw)==entry['bytes'], name
    assert hashlib.sha256(raw).hexdigest()==entry['sha256'], name
config=json.loads((DATA/'design.json').read_text())
years=list(range(config['year_start'],config['year_end']+1))
wb=json.loads((DATA/'worldbank-countries.json').read_text())
assert wb[0]['pages']==1 and wb[0]['total']==len(wb[1])
countries={r['id']:r for r in wb[1] if r['region']['id']!='NA'}
byname={r['name'].casefold():r['id'] for r in countries.values()}
assert len(byname)==len(countries)
wb_gdp=json.loads((DATA/'worldbank-gdp.json').read_text())
assert wb_gdp[0]['pages']==1 and wb_gdp[0]['total']==len(wb_gdp[1])
gdp={}
for r in wb_gdp[1]:
    assert r['indicator']['id']==config['gdp_indicator']
    key=(r['countryiso3code'],int(r['date']))
    if key[0] not in countries:
        continue
    assert key not in gdp
    gdp[key]=r['value']
book=openpyxl.load_workbook(DATA/'national-fossil-2025.xlsx',read_only=True,data_only=True)

def load_sheet(name,header_row):
    # Pinned workbook layout. Bound dimensions rather than trusting Excel's inflated used range.
    sheet=book[name]
    rows=list(sheet.iter_rows(min_row=1,max_row=300,max_col=251,values_only=True))
    assert rows[1][1].startswith('All values in million tonnes of carbon per year.')
    header=rows[header_row-1]
    names=[x for x in header[1:] if x is not None]
    assert len(names)==len(set(names))
    result={}
    for row in rows[header_row:]:
        if isinstance(row[0],(int,float)) and row[0] in years:
            assert int(row[0]) not in result
            result[int(row[0])]={header[i]:row[i] for i in range(1,len(row)) if header[i] is not None}
    assert set(result)==set(years)
    return result

territorial=load_sheet('Territorial Emissions',12)
consumption=load_sheet('Consumption Emissions',9)
assert set(territorial[2023])==set(consumption[2023])
aliases=config['alias_iso3']
for name,iso in aliases.items():
    assert iso in countries and name in consumption[2023]
# World Bank IDs distinguish countries/economies from regional aggregates. No fuzzy match.
crosswalk=[];excluded=[];panel=[]
for name in sorted(consumption[2023]):
    iso=aliases.get(name,byname.get(name.casefold()))
    if iso is None:
        excluded.append({'gcb_name':name,'iso3':'','reason':'No exact World Bank economy mapping (regional aggregate, international transport, statistical difference, or unmatched economy)'})
        continue
    missing=[]
    for year in years:
        for variable,values in [('gdp',gdp.get((iso,year))),('territorial',territorial[year].get(name)),('consumption',consumption[year].get(name))]:
            if not positive(values):missing.append(f'{variable}:{year}')
    if missing:
        excluded.append({'gcb_name':name,'iso3':iso,'reason':';'.join(missing)})
        continue
    crosswalk.append({'gcb_name':name,'iso3':iso,'worldbank_name':countries[iso]['name'],'join':'explicit_alias' if name in aliases else 'exact_casefold'})
    for year in years:
        panel.append({'iso3':iso,'name':name,'year':year,'gdp_constant_2015_usd':gdp[(iso,year)],'territorial_mtc':territorial[year][name],'consumption_mtc':consumption[year][name]})
bykey={(r['iso3'],r['year']):r for r in panel}
assert len(bykey)==len(panel)
isos=sorted(r['iso3'] for r in crosswalk)
assert len(isos)==len(set(isos))
name_for={r['iso3']:r['gcb_name'] for r in crosswalk}
allrows=[];summary=[];sets={}
for start,end in config['endpoint_windows']:
    tag=f'{start}_{end}'
    sets[tag]={}
    for iso in isos:
        a=bykey[iso,start];b=bykey[iso,end]
        gd=b['gdp_constant_2015_usd']/a['gdp_constant_2015_usd']-1
        td=b['territorial_mtc']/a['territorial_mtc']-1
        cd=b['consumption_mtc']/a['consumption_mtc']-1
        t=gd>0 and td<0;c=gd>0 and cd<0
        allrows.append({'iso3':iso,'name':name_for[iso],'start':start,'end':end,'gdp_change_pct':100*gd,'territorial_change_pct':100*td,'consumption_change_pct':100*cd,'territorial_absolute':t,'consumption_absolute':c,'both_absolute':t and c})
        sets[tag][iso]=(t,c)
    values=list(sets[tag].values())
    summary.append({'window':tag,'n':len(isos),'territorial_absolute':sum(t for t,c in values),'consumption_absolute':sum(c for t,c in values),'both_absolute':sum(t and c for t,c in values),'territorial_only':sum(t and not c for t,c in values),'consumption_only':sum(c and not t for t,c in values)})

smooth=[];smooth_sets={}
for iso in isos:
    a={k:fmean(bykey[iso,y][k] for y in config['smoothed_windows'][0]) for k in ['gdp_constant_2015_usd','territorial_mtc','consumption_mtc']}
    b={k:fmean(bykey[iso,y][k] for y in config['smoothed_windows'][1]) for k in a}
    gd=b['gdp_constant_2015_usd']/a['gdp_constant_2015_usd']-1
    td=b['territorial_mtc']/a['territorial_mtc']-1;cd=b['consumption_mtc']/a['consumption_mtc']-1
    t=gd>0 and td<0;c=gd>0 and cd<0;smooth_sets[iso]=(t,c)
    smooth.append({'iso3':iso,'name':name_for[iso],'gdp_change_pct':100*gd,'territorial_change_pct':100*td,'consumption_change_pct':100*cd,'territorial_absolute':t,'consumption_absolute':c,'both_absolute':t and c})
primary=sets['2015_2023']
primary_rows=[r for r in allrows if r['start']==2015 and r['end']==2023]
consistent=[iso for iso in isos if all(t and c for tag in sets for t,c in [sets[tag][iso]]) and all(smooth_sets[iso])]
error_rows=[]
for delta in config['carbon_relative_endpoint_error_scenarios']:
    row={'carbon_endpoint_relative_error':delta,'n':len(isos)}
    # Independent multiplicative endpoint perturbations e_start,e_end in [-delta,+delta].
    # Compute exact best/worst signs within this deterministic scenario, GDP held fixed.
    for key in ['territorial','consumption']:
        definitely=[];possibly=[]
        for iso in isos:
            a=bykey[iso,2015];b=bykey[iso,2023]
            growing=b['gdp_constant_2015_usd']>a['gdp_constant_2015_usd']
            ratio=b[key+'_mtc']/a[key+'_mtc']
            if growing and ratio*(1+delta)/(1-delta)<1:definitely.append(iso)
            if growing and ratio*(1-delta)/(1+delta)<1:possibly.append(iso)
        row[key+'_definite_n']=len(definitely);row[key+'_possible_n']=len(possibly)
    # Same underlying economy but independently perturb both inventories for a joint scenario.
    both_def=[];both_pos=[]
    for iso in isos:
        a=bykey[iso,2015];b=bykey[iso,2023];ratios=[b[k]/a[k] for k in ['territorial_mtc','consumption_mtc']]
        growing=b['gdp_constant_2015_usd']>a['gdp_constant_2015_usd']
        if growing and all(r*(1+delta)/(1-delta)<1 for r in ratios):both_def.append(iso)
        if growing and all(r*(1-delta)/(1+delta)<1 for r in ratios):both_pos.append(iso)
    row['both_definite_n']=len(both_def);row['both_possible_n']=len(both_pos)
    error_rows.append(row)
# Model-free endpoint changes describe this snapshot, not sampling confidence intervals.
R1={'n_economies':len(isos),'n_panel_rows':len(panel),'start_year':2015,'end_year':2023,**{k:v for k,v in next(r for r in summary if r['window']=='2015_2023').items() if k not in ['window','n']},
    'territorial_only_names':', '.join(name_for[i] for i in isos if primary[i][0] and not primary[i][1]),
    'consumption_only_names':', '.join(name_for[i] for i in isos if primary[i][1] and not primary[i][0]),
    'consistent_both_all_windows_n':len(consistent),
    'consistent_both_all_windows_names':', '.join(name_for[i] for i in consistent),
    'smooth_both_n':sum(t and c for t,c in smooth_sets.values()),
    'smooth_both_classification_flips_n':sum((t and c)!=all(primary[iso]) for iso,(t,c) in smooth_sets.items()),
    'long_window_both_n':sum(t and c for t,c in sets['2005_2023'].values()),
    'precovid_both_n':sum(t and c for t,c in sets['2015_2019'].values()),
    'primary_vs_precovid_both_flips_n':sum(all(primary[i])!=all(sets['2015_2019'][i]) for i in isos),
    'primary_vs_long_both_flips_n':sum(all(primary[i])!=all(sets['2005_2023'][i]) for i in isos),
    'both_at_05pct_definite_n':next(r['both_definite_n'] for r in error_rows if r['carbon_endpoint_relative_error']==0.05),
    'both_at_05pct_possible_n':next(r['both_possible_n'] for r in error_rows if r['carbon_endpoint_relative_error']==0.05)}
write_json('R1.json',R1);write_json('window-summary.json',summary);write_json('endpoint-error-scenarios.json',error_rows)
write_csv('panel.csv',panel);write_csv('crosswalk.csv',crosswalk);write_csv('exclusions.csv',excluded);write_csv('endpoint-classifications.csv',allrows);write_csv('smoothed-classifications.csv',smooth)
print(json.dumps(R1,indent=2,ensure_ascii=False));print(json.dumps(summary,indent=2))
