import csv
import hashlib
import json
from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from model.demand import calculate, parameters

EDITION = 'website-demand-comparison-2026-09-17'
DATA = ROOT / 'data' / EDITION
REVIEWED = '2026-09-17'
SOURCE = 'demand-scenario-comparison'


def load(path):
    return json.loads(path.read_text())


def write(path, value):
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(value, indent=2, ensure_ascii=False, allow_nan=False) + '\n')


def artifact(path, download=None):
    content = path.read_bytes()
    record = dict(path=str(path.relative_to(ROOT)), sha256=hashlib.sha256(content).hexdigest(), bytes=len(content))
    if download:
        record['download'] = download
        target = ROOT / 'website/public' / download.lstrip('/')
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(content)
    return record


def main():
    DATA.mkdir(parents=True, exist_ok=True)
    config = load(ROOT / 'model/parameters.json')
    baseline = load(ROOT / 'data/demand-model-v1/baseline.json')
    archived = {row['scenario']: row for row in load(ROOT / 'data/demand-model-v1/scenario-results.json') if row['year'] == 2050}
    catalog = load(ROOT / 'data/website-costs-2026-09-17/catalog.json')
    catalog['reviewedAt'] = REVIEWED
    cases = []
    definitions = [
        ('lower_demand', 'Lower demand', 'Less growth, stronger efficiency gains', 'lower-demand-2050'),
        ('central', 'Central case', 'Working reference for our proposed strategy', 'central-demand-2050'),
        ('higher_demand', 'Higher demand', 'More growth, weaker efficiency gains', 'higher-demand-2050'),
    ]
    for key, label, description, claim_id in definitions:
        result = calculate(baseline, config, 2050, key)
        assert result == archived[key], f'{key}: model differs from frozen research'
        claim = next(row for row in catalog['claims'] if row['id'] == claim_id)
        assert result['electricity_demand_twh'] == claim['value']
        cases.append(dict(key=key, label=label, description=description, claim_id=claim_id, total_twh=result['electricity_demand_twh'], inputs=parameters(config, key)))
    row_specs = [
        ('population', ['population_million'], 'Residents', 'People living in Switzerland.', 1, 0, 'million residents', 'number'),
        ('services', ['services_activity'], 'Service-sector activity', 'Floor area and activity in services, including agriculture. This is not a GDP forecast.', 100, -100, '% versus 2024', 'change'),
        ('industry', ['industry_activity'], 'Industrial activity', 'How much industrial activity the model assumes. This is not a GDP forecast.', 100, -100, '% versus 2024', 'change'),
        ('travel', ['road_activity'], 'Road travel', 'Total distance driven by the covered road vehicles.', 100, -100, '% versus 2024', 'change'),
        ('existing-electricity', ['services_electric_intensity', 'industry_electric_intensity'], 'Electricity per unit of business activity', 'Existing service and industrial uses, excluding separately modelled building heat and cooling. New electric uses are added separately.', 100, -100, '% versus 2024', 'change'),
        ('heat', ['space_heat_intensity'], 'Space heat needed per unit of activity', 'Heat per resident in homes, or per unit of business activity. Insulation and heating controls can reduce it. Weather stays unchanged.', 100, -100, '% versus 2024', 'change'),
        ('heat-pump', ['space_heat_spf'], 'New heat pumps for space heating', 'Heat delivered per unit of electricity over a season. A larger number means less electricity for the same heat.', 1, 0, 'kWh heat / kWh electricity', 'number'),
        ('car', ['cars_battery_kwh_per_km'], 'Electric-car consumption', 'Energy at the battery per 100 km. Charging losses are added separately.', 100, 0, 'kWh / 100 km', 'number'),
    ]
    rows = []
    for key, keys, label, description, scale, offset, unit, display in row_specs:
        values = []
        for case in cases:
            raw = [case['inputs'][parameter] for parameter in keys]
            assert len(set(raw)) == 1
            value = round(raw[0] * scale + offset, 10)
            claim_id = f'demand-input-{key}-{case["key"].replace("_", "-")}'
            formula = f'parameters(config, {case["key"]!r})[{keys[0]!r}] × {scale} + ({offset}).'
            if len(keys) > 1:
                formula += f' Same input value verified for {", ".join(keys)}.'
            catalog['claims'].append(dict(id=claim_id, label=f'{label}: {case["label"].lower()}', value=value, unit=unit, status='assumption', period='2050 model input', definition=description, limitation='Analyst-selected scenario assumption. Not an official forecast, measured future value or guaranteed efficiency gain. Multiple inputs change together in the complete case.', formula=formula, sources=['demand-model-v1', SOURCE], reviewedAt=REVIEWED))
            values.append(dict(scenario=case['key'], raw_values=raw, value=value, claim_id=claim_id))
        rows.append(dict(key=key, parameters=keys, label=label, description=description, scale=scale, offset=offset, unit=unit, display=display, values=values))
    changed = []
    common = []
    for key, metadata in config['parameters'].items():
        values = {case['key']: case['inputs'][key] for case in cases}
        entry = dict(parameter=key, description=metadata['description'], unit=metadata['unit'], values=values, affects_final_demand=key != 'effective_loss_fraction')
        (common if len(set(values.values())) == 1 else changed).append(entry)
    assert all(case['inputs']['fossil_heat_conversion'] == 1 and case['inputs']['resistance_conversion'] == 1 and case['inputs']['process_fossil_conversion'] == .9 and case['inputs']['space_heat_weather_multiplier'] == 1 for case in cases)
    assert all(archived[case['key']]['technology_replacement_progress'] == 1 for case in cases)
    assert all(case['inputs'][key] == 0 for case in cases for key in ['data_centres_increment_twh', 'hydrogen_and_efuels_increment_twh', 'district_heat_increment_twh'])
    catalogue_shared = [('building-heat-conversion', 100, '%', 'Identified household resistance heating and the modelled oil/gas/coal building-heat pool are replaced.', 'fossil_heat_conversion = resistance_conversion = 1; × 100.'), ('process-conversion', 90, '%', 'The modelled oil/gas/coal process-heat pool is converted to electricity.', 'process_fossil_conversion = 0.9; × 100.')]
    for key, value, unit, definition, formula in catalogue_shared:
        catalog['claims'].append(dict(id='demand-shared-' + key, label='Shared electrification target: ' + key.replace('-', ' '), value=value, unit=unit, status='assumption', period='2050, all three demand cases', definition=definition, limitation='An assumed replacement target, not proven adoption or complete economy-wide fossil elimination. Covered road modes also reach full electric replacement; aviation and non-road conversion remain outside the model.', formula=formula, sources=['demand-model-v1', SOURCE], reviewedAt=REVIEWED))
    dataset = dict(reviewedAt=REVIEWED, status='explanation_of_existing_frozen_scenarios', year=2050, boundary='Annual final electricity, excluding network losses, storage charging and net exports', cases=cases, displayed_inputs=rows, all_different_inputs=changed, common_inputs=common, shared_adoption_path=config['adoption_path'], methods=dict(results='Recalculate all three complete 2050 results and require exact equality to frozen scenario-results.json. No model inputs were changed.', display='Activity and intensity factors are shown as percentage changes from 2024: (factor − 1) × 100. Vehicle consumption is converted from kWh/km to kWh/100 km. Raw inputs remain in this file.', interpretation='The cases are bundles of assumptions, not confidence bounds or a causal decomposition. Changes interact; individual effects cannot simply be summed. The different network-loss fractions affect only the separate supply-accounting output, not these final-demand totals.', provenance='The baseline draws on BFE/BFS sources indexed in demand-model-v1. Future scenario endpoints are analyst choices, not official forecasts or guaranteed technical improvements.'), inputs=[artifact(ROOT / name) for name in ['model/parameters.json', 'model/demand.py', 'data/demand-model-v1/baseline.json', 'data/demand-model-v1/scenario-results.json']])
    write(DATA / 'comparison.json', dataset)
    with (DATA / 'inputs.csv').open('w', newline='') as handle:
        writer = csv.DictWriter(handle, fieldnames=['parameter', 'unit', 'description', 'affects_final_demand', *[case['key'] for case in cases]])
        writer.writeheader()
        for row in [*changed, *common]:
            writer.writerow({key: value for key, value in row.items() if key != 'values'} | row['values'])
    artifacts = [artifact(DATA / name, '/downloads/demand-comparison/' + name) for name in ['comparison.json', 'inputs.csv']]
    artifacts.append(artifact(Path(__file__).resolve(), '/downloads/demand-comparison/build_website_demand_comparison.py'))
    catalog['sources'].append(dict(id=SOURCE, title='What changes between the three demand cases', publisher='WattInfo', kind='project-calculation', url='/downloads/demand-comparison/comparison.json', publicationDate=REVIEWED, edition='Explanation of frozen demand-model-v1 scenarios; 17 September 2026', retrievedAt=REVIEWED, reviewedAt=REVIEWED, locator='cases; displayed_inputs; all_different_inputs; common_inputs; methods; model/parameters.json scenario overrides', note='All three 2050 results reproduce the archived model exactly. Future parameters are analyst assumptions. The selected inputs explain the differences; the download lists every input, including charging allocation, industrial heating, cooling and the accounting residual. No new official forecast or technology-learning claim is introduced.', artifacts=artifacts))
    for path in [DATA / 'catalog.json', ROOT / 'website/src/data/catalog.json', ROOT / 'website/public/downloads/catalog.json']:
        write(path, catalog)
    print(f'{len(cases)} archived results reproduced; {len(rows)} displayed assumption rows; {len(changed)} differing inputs; {len(catalog["sources"])} sources, {len(catalog["claims"])} claims.')


if __name__ == '__main__':
    main()
