import copy
import csv
import hashlib
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
REVIEWED = '2026-09-18'
DATA = ROOT / 'data/website-eth-psi-2026-09-18'
RESEARCH = ROOT / 'data/manera-2026-09-18'
REVIEW_ID = 'eth-psi-study-review'
SOURCE_DETAILS = {
    'eth-whitepaper-full-2026': ('eth-psi-report-2026', 'primary', 'ETH–PSI: Nuclear Power and the Future Swiss Energy System',
        'Printed pp2, 10, 15, 21–22 and 27–28 (physical PDF page = printed page + 4).',
        'Conditional system modelling, coauthored by Annalisa Manera and eighteen others. The 2.6–4.9 GW result uses CHF2024 5,000/kW, 5% real WACC and a shared generation target. Security-of-supply stress analysis and existing-plant lifetime extensions are outside its scope. Selected passages reviewed; models not independently reproduced.'),
    'eth-whitepaper-models-2026': ('eth-psi-models-2026', 'primary', 'ETH–PSI: nuclear white paper modelling documentation',
        'Table 3, printed pp5–7; sections 2.2.2–2.2.5, pp8–9; sections 2.3.1–2.3.3, pp10–12; Figures 21 and 25, pp23 and 25 (physical PDF page = printed page + 6).',
        'Companion appendix to the report, rather than an independent study. Includes the market-driven cases without a domestic-generation target. Chart results are read qualitatively, without estimating exact capacities from pixels. The 5 TWh winter net-import goal is excluded. Documentation ambiguities remain recorded in the accompanying review.'),
    'tamedia-interview-2025': ('manera-interview-2025', 'commentary', 'Annalisa Manera: July 2025 interview on nuclear policy and support',
        'Interview published 13 July 2025; questions on the SCNAT report’s timetable, profitability and wind-support comparison.',
        'An interview records Manera’s position. Read alongside SCNAT’s response. Its tariff comparison is a policy argument; gross tariff revenue is distinct from net subsidy and money available after operating and financing costs.'),
    'scnat-response-2025': ('scnat-response-2025', 'commentary', 'SCNAT response to the July 2025 Manera interview',
        'Official institutional post, opening paragraph and numbered responses 1 and 2; third-party comments excluded.',
        'SCNAT attributes its timing estimate to sequential project processes and describes profitability as uncertain. This records its response without adjudicating the underlying report. Exact post date remains unverified; the context is the interview of 13 July 2025.'),
}


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()
    row = dict(path=str(path.relative_to(ROOT)), sha256=hashlib.sha256(content).hexdigest(), bytes=len(content))
    if download:
        row['download'] = download
        target = ROOT / 'website/public' / download.lstrip('/')
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(content)
    return row


def main():
    prior = load(ROOT / 'data/website-historical-nuclear-2026-09-18/catalog.json')
    catalog = copy.deepcopy(prior)
    originals = {row['source_id']: row for row in load(RESEARCH / 'source-catalog.json')['sources']}
    review = load(RESEARCH / 'whitepaper-review.json')
    for research_id, (public_id, kind, title, locator, note) in SOURCE_DETAILS.items():
        original = originals[research_id]
        raw = artifact(ROOT / original['path'])
        assert (raw['sha256'], raw['bytes']) == (original['sha256'], original['bytes'])
        received = original.get('received_at_utc') or original['retrieved_at_utc']
        supplied = original['status'] == 'archived_user_provided'
        if supplied:
            note += ' Archived from a user-supplied download; title, authors, publication date and DOI match the publication. No independent remote byte comparison is claimed.'
        catalog['sources'].append(dict(
            id=public_id, title=title, publisher=original['publisher'], kind=kind, url=original['url'],
            publicationDate=original['publication_date'], edition=original['publication_date'] or 'Response to the July 2025 interview; exact date unverified',
            retrievedAt=received, reviewedAt=REVIEWED, locator=locator, note=note,
            acquisition='Supplied PDF received and archived unchanged' if supplied else 'Original publisher response archived',
            artifacts=[raw, artifact(ROOT / original['text_path'])],
        ))

    claims = []

    def add(claim_id, label, value, unit, status, definition, limitation, source, locator):
        row = dict(id=claim_id, label=label, value=value, unit=unit, status=status,
                   period='2050 scenario in the ETH–PSI study published 29 June 2026', definition=definition,
                   limitation=limitation, sources=[source, REVIEW_ID], reviewedAt=REVIEWED,
                   formula=f'Direct transcription from {locator}; no model rerun or Swiss supplier quotation.')
        claims.append(row)
        catalog['claims'].append(row)

    for label, cost in [('low', 5000), ('medium', 8000), ('high', 12000)]:
        add(f'eth-psi-occ-{label}', f'ETH–PSI: {label} nuclear construction-cost assumption', cost, 'CHF2024/kW', 'assumption',
            'Overnight construction cost per unit of electrical capacity, in 2024 Swiss francs. This excludes construction financing.',
            'A model input, distinct from a current executable Swiss project offer. Model-specific construction and operating treatment must also be considered.',
            'eth-psi-models-2026', 'Table 3, p6; section 2.3.3, p12')
    for label, rate in [('derisked', 5), ('private', 8)]:
        add(f'eth-psi-wacc-{label}', f'ETH–PSI: {label} nuclear financing assumption', rate, '% real WACC', 'assumption',
            'Weighted average cost of capital: the combined return required by debt and equity financing, above inflation.',
            'The study associates 5% with investment de-risking such as public guarantees and 8% with private nuclear finance. Neither rate is a bankable financing offer.',
            'eth-psi-models-2026', 'Table 3, p7; section 2.2.2, p8')
    add('eth-psi-shared-target', 'ETH–PSI: shared generation target', 45, 'TWh/year', 'assumption',
        'Hypothetical domestic generation requirement met jointly by non-hydro renewables and new nuclear in the shared-target policy family.',
        'This is a study policy variant. The market-driven family removes the domestic-generation requirement; net-zero constraints and electricity trade remain.',
        'eth-psi-models-2026', 'section 2.3.2, p11')
    report_text = (ROOT / originals['eth-whitepaper-full-2026']['text_path']).read_text()
    assert 'models invest between 2.6 and 4.9 GW' in report_text
    for bound, value in [('lower', 2.6), ('upper', 4.9)]:
        add(f'eth-psi-nuclear-{bound}', f'ETH–PSI: {bound} end of the low-cost nuclear result', value, 'GW', 'scenario',
            'Endpoint of the four-model new-nuclear capacity range with CHF2024 5,000/kW overnight cost, 5% real WACC and the shared 45 TWh/year generation target.',
            'Only the low-cost case. Not the capacity range for the CHF2024 8,000/kW case, a statistical confidence interval or a selected reactor fleet. New operation in 2050 is assumed.',
            'eth-psi-report-2026', 'pp21–22, Figure 6; conclusion p27')
    add('eth-psi-nuclear-start', 'ETH–PSI: assumed new-nuclear start year', 2050, 'year', 'assumption',
        'New nuclear enters the study in 2050 by assumption, with existing-plant lifetime extensions excluded.',
        'A scenario boundary rather than a demonstrated licensing or construction schedule. It does not replace WattInfo’s conditional delivery cases.',
        'eth-psi-report-2026', 'section 2.2.1, p10')
    add('eth-psi-minimum-unit', 'ETH–PSI: minimum modelled nuclear investment size', 300, 'MW', 'assumption',
        'Minimum size within continuous capacity optimisation using generic light-water reactor assumptions.',
        'Continuous model sizes are an approximation of discrete available plants. This input establishes neither SMR prices nor approved Swiss sites.',
        'eth-psi-models-2026', 'section 2.3.3, p12')
    add('eth-psi-excluded-winter-goal', 'Winter net-import goal excluded from ETH–PSI modelling', 5, 'TWh/winter', 'target',
        'The winter net-import goal identified by the authors and explicitly excluded from all four models.',
        'Not a model result or enforced import limit. The authors discuss absent implementation rules and effects on trading and investment decisions.',
        'eth-psi-models-2026', 'Table 3, p7; section 2.3.2, p11')

    inputs = copy.deepcopy(review['assumptions'])
    inputs['source_id'] = 'eth-psi-models-2026'
    demand = []
    for parameter, label in [('Space_heat_service', 'Space heating'), ('Warm_water_service', 'Hot water'), ('Industrial_heat_service', 'Industrial heat'), ('Total_electricity', 'Nexus-e electricity input')]:
        original = next(row for row in inputs['rows'] if row['parameter'] == parameter)
        is_electricity = parameter == 'Total_electricity'
        model = 'Nexus-e' if is_electricity else 'SES'
        value = original['values'][model]
        if not is_electricity:
            assert all(original['values'][name] == value for name in ['SES', 'SES-ETH', 'STEM'])
        claim_id = 'eth-psi-demand-' + parameter.lower().replace('_', '-')
        definition = 'Externally fixed electricity-demand input for Nexus-e, citing BFE 2020.' if is_electricity else 'Heat-service demand shared by SES, SES-ETH and STEM. The electricity used to supply this heat depends on the selected equipment and its efficiency.'
        add(claim_id, 'ETH–PSI assumption: ' + label, value, 'TWh/year', 'assumption', definition,
            'Different model scope and demand treatment from WattInfo. Heat services must not be added directly to final electricity use. These inputs have not replaced our demand scenarios.',
            'eth-psi-models-2026', 'Table 3, p5, including footnotes 2 and 3')
        demand.append(dict(parameter=parameter, label=label, value=value, claim_id=claim_id, kind='electricity input' if is_electricity else 'heat service'))

    market = []
    for model in ['STEM', 'Nexus-e', 'SES', 'SES-ETH']:
        for wacc in [8, 5]:
            for cost in [5000, 8000, 12000]:
                selected = model == 'STEM' or (model == 'Nexus-e' and wacc == 5 and cost == 5000)
                market.append(dict(model=model, wacc_percent=wacc, overnight_chf2024_kw=cost, nuclear_selected=selected,
                                   capacity_gw=None, source_id='eth-psi-models-2026', figure=21 if wacc == 8 else 25,
                                   printed_page=23 if wacc == 8 else 25, evidence_status='qualitative_reading_of_published_chart'))
    source_map = {old: details[0] for old, details in SOURCE_DETAILS.items()}
    findings = copy.deepcopy(review['findings'])
    for finding in findings:
        finding['source_id'] = source_map[finding['source_id']]
    dataset = dict(reviewedAt=REVIEWED, model_reproduced=False,
                   headline=dict(cost_claim='eth-psi-occ-low', financing_claim='eth-psi-wacc-derisked', target_claim='eth-psi-shared-target', capacity_claims=['eth-psi-nuclear-lower', 'eth-psi-nuclear-upper']),
                   findings=findings, model_inputs=inputs, demand_comparison=demand, market_results=market, claims=claims,
                   remaining_work=review['remaining_work'],
                   interpretation='Conditional central-planner system economics. Swiss bids, owner profitability, stress adequacy, site selection and delivery schedules need separate evidence. Market-driven cases retain net-zero constraints; 5% financing remains de-risked.',
                   media_review=dict(sources=['manera-interview-2025', 'scnat-response-2025'], scope='Attributed policy positions, separate from the joint study. Timing and profitability responses reviewed; no adjudication of the underlying SCNAT report.', accounting='A support tariff times generation gives gross revenue. Establish market revenue, support rules and all owner costs before estimating net subsidy, profit or funds available for construction. Annual generation equality does not establish equal winter service.'),
                   inputs=[artifact(RESEARCH / name) for name in ['whitepaper-review.json', 'source-catalog.json', 'claim-checks.json']])
    write(DATA / 'evidence.json', dataset)
    files = [artifact(DATA / 'evidence.json', '/downloads/eth-psi/evidence.json'), artifact(Path(__file__).resolve(), '/downloads/eth-psi/build_website_eth_psi.py')]
    fields = ['model', 'wacc_percent', 'overnight_chf2024_kw', 'nuclear_selected', 'capacity_gw', 'source_id', 'figure', 'printed_page', 'evidence_status']
    with (DATA / 'market-results.csv').open('w', newline='') as stream:
        writer = csv.DictWriter(stream, fieldnames=fields, lineterminator='\n')
        writer.writeheader()
        writer.writerows(market)
    files.append(artifact(DATA / 'market-results.csv', '/downloads/eth-psi/market-results.csv'))
    catalog['sources'].append(dict(id=REVIEW_ID, title='ETH–PSI study: conditions, model inputs and reading notes', publisher='WattInfo', kind='project-calculation',
        url='/downloads/eth-psi/evidence.json', publicationDate=REVIEWED, edition='Study review, 18 September 2026', retrievedAt=REVIEWED, reviewedAt=REVIEWED,
        locator='headline; market_results; demand_comparison; model_inputs; findings; media_review', artifacts=files,
        note='Curated transcription and qualitative figure review, with separate attribution of public commentary. Models were not independently reproduced. No exact market-case capacities are inferred from plotted points. Earlier WattInfo demand and delivery calculations remain unchanged.'))
    for collection in ['sources', 'claims']:
        assert catalog[collection][:len(prior[collection])] == prior[collection]
    for path in [DATA / 'catalog.json', ROOT / 'website/src/data/catalog.json', ROOT / 'website/public/downloads/catalog.json']:
        write(path, catalog)
    print(f'Added two study documents, two commentary records, one review and {len(claims)} claims.')


if __name__ == '__main__':
    main()
