from __future__ import annotations
from pathlib import Path
import hashlib
import platform
import numpy as np
import pandas as pd
from scipy.stats import wilcoxon
import scipy

OUT=Path(__file__).resolve().parents[1] / 'outputs'
METRICS=['mean_tardiness','p95_tardiness','on_time_rate','empty_ratio','horizon']

def seed_for(*parts):
    h=hashlib.sha256('|'.join(map(str,parts)).encode()).digest()
    return int.from_bytes(h[:8],'little')

def holm(pvals):
    p=np.asarray(pvals,float); n=len(p)
    order=np.argsort(p); adj=np.empty(n,float); running=0.0
    for rank,idx in enumerate(order):
        val=(n-rank)*p[idx]
        running=max(running,val)
        adj[idx]=min(1.0,running)
    return adj

def paired_values(raw, proposed, baseline, metric, profile=None):
    d=raw.copy()
    if profile is not None: d=d[d.profile==profile]
    keys=['seed','profile']
    if 'capacity_config' in d.columns: keys=['seed','profile','capacity_config']
    p=d[d.method==proposed].set_index(keys).sort_index()
    b=d[d.method==baseline].set_index(keys).sort_index()
    if metric=='on_time_rate': vals=(p[metric]-b[metric]).dropna().to_numpy()
    else: vals=(b[metric]-p[metric]).dropna().to_numpy()
    return vals

def boot_ci(vals, label, nboot=20000):
    rng=np.random.default_rng(seed_for('bootstrap',label,20260731))
    n=len(vals); chunks=[]; remaining=nboot
    while remaining:
        k=min(2000,remaining)
        chunks.append(rng.choice(vals,size=(k,n),replace=True).mean(axis=1))
        remaining-=k
    b=np.concatenate(chunks)
    return np.quantile(b,[.025,.975])

def infer_main():
    raw=pd.read_csv(OUT/'main_by_run.csv')
    rows=[]
    for profile in [None,'nominal','demand_surge','aisle_disruption','combined_stress']:
        tmp=[]
        for metric in METRICS:
            vals=paired_values(raw,'Proposed RCRD','Matched energy-core',metric,profile)
            lo,hi=boot_ci(vals,f'main|{profile}|{metric}')
            p=wilcoxon(vals,alternative='two-sided').pvalue
            tmp.append(dict(profile='all' if profile is None else profile,metric=metric,n=len(vals),
                            mean_improvement=vals.mean(),ci_low=lo,ci_high=hi,p=p,
                            dz=vals.mean()/vals.std(ddof=1) if vals.std(ddof=1)>0 else np.nan))
        adj=holm([x['p'] for x in tmp])
        for x,a in zip(tmp,adj): x['p_holm']=a
        rows.extend(tmp)
    pd.DataFrame(rows).to_csv(OUT/'paired_inference.csv',index=False)

    # Descriptive comparisons against standard rules.
    prop=raw[raw.method=='Proposed RCRD'].groupby(['seed','profile']).first()
    crows=[]
    for baseline in ['FIFO-nearest','EDD-nearest']:
        base=raw[raw.method==baseline].groupby(['seed','profile']).first()
        for metric in METRICS:
            vals=(prop[metric]-base[metric]).to_numpy() if metric=='on_time_rate' else (base[metric]-prop[metric]).to_numpy()
            lo,hi=boot_ci(vals,f'conv|{baseline}|{metric}')
            crows.append(dict(baseline=baseline,metric=metric,n=len(vals),mean_improvement=vals.mean(),ci_low=lo,ci_high=hi))
    pd.DataFrame(crows).to_csv(OUT/'conventional_comparisons.csv',index=False)

def infer_robustness():
    raw=pd.read_csv(OUT/'robustness_by_run.csv')
    rows=[]
    for (cfg,profile),d in raw.groupby(['capacity_config','profile']):
        for metric in METRICS:
            vals=paired_values(d,'Proposed RCRD','Matched energy-core',metric)
            lo,hi=boot_ci(vals,f'rob|{cfg}|{profile}|{metric}')
            rows.append(dict(capacity_config=cfg,profile=profile,metric=metric,n=len(vals),
                             mean_improvement=vals.mean(),ci_low=lo,ci_high=hi))
    pd.DataFrame(rows).to_csv(OUT/'robustness_inference.csv',index=False)

def sensitivity_comparison():
    s=pd.read_csv(OUT/'sensitivity_by_run.csv')
    c=pd.read_csv(OUT/'sensitivity_core_by_run.csv')
    rows=[]
    for cfg,d in s.groupby('config'):
        m=d.merge(c[['seed','profile']+METRICS],on=['seed','profile'],suffixes=('_r','_c'))
        meta=d.iloc[0]
        row=dict(config=cfg,weight_name=meta.weight_name,weight_factor=meta.weight_factor,n=len(m))
        for metric in METRICS:
            vals=(m[f'{metric}_r']-m[f'{metric}_c']).to_numpy() if metric=='on_time_rate' else (m[f'{metric}_c']-m[f'{metric}_r']).to_numpy()
            row[f'{metric}_improvement']=vals.mean()
        rows.append(row)
    pd.DataFrame(rows).to_csv(OUT/'sensitivity_vs_core.csv',index=False)

def environment():
    txt='\n'.join([
        f'python={platform.python_version()}',f'platform={platform.platform()}',
        f'processor={platform.processor()}',f'numpy={np.__version__}',
        f'pandas={pd.__version__}',f'scipy={scipy.__version__}',
        'candidate_limit=18','disruption_bucket_length=15.0','jobs_per_run=450',
        'main_seeds=1300-1359','robustness_seeds=1400-1439','sensitivity_seeds=1500-1519',
    ])+'\n'
    (OUT/'environment.txt').write_text(txt)

if __name__=='__main__':
    infer_main(); infer_robustness(); sensitivity_comparison(); environment()
    print('analysis complete')
