"""Recheck retained public upstream archives and write the provenance manifest.

No network access or archive extraction. Published registry integrity hashes are
checked; npm Sigstore/DSSE attestations are decoded, not cryptographically verified.
"""
from pathlib import Path
import base64
import hashlib
import json
import shutil
import tarfile

HERE = Path(__file__).resolve().parent
ROOT = HERE.parent


def sha256(data):
    return hashlib.sha256(data).hexdigest()


def member_bytes(archive, suffix):
    with tarfile.open(archive) as tar:
        matches = [m for m in tar.getmembers() if m.isfile() and m.name.endswith(suffix)]
        if len(matches) != 1:
            raise AssertionError(f'{archive.name}: expected one member ending {suffix}, found {len(matches)}')
        return tar.extractfile(matches[0]).read()


packages = []
for registry_path in sorted(HERE.glob('*-registry.json')):
    metadata = json.loads(registry_path.read_text(encoding='utf-8'))
    basename = metadata['name'].replace('@mlightcad/', '') + '-' + metadata['version']
    archive = HERE / f'{basename}-npm.tgz'
    raw = archive.read_bytes()
    algorithm, expected = metadata['dist']['integrity'].split('-', 1)
    actual = base64.b64encode(hashlib.new(algorithm, raw).digest()).decode()
    assert actual == expected, f'Published npm integrity mismatch: {archive.name}'
    record = {
        'name': metadata['name'], 'version': metadata['version'],
        'declared_license': metadata.get('license'),
        'npm_archive': archive.name, 'npm_tarball_url': metadata['dist']['tarball'],
        'npm_integrity': metadata['dist']['integrity'], 'npm_integrity_matches': True,
        'npm_gitHead': metadata.get('gitHead'), 'npm_gitHead_present': 'gitHead' in metadata,
        'registry_metadata': registry_path.name,
        'attestation_signature_verified': False,
    }
    attestation_path = HERE / f'{basename}-attestations.json'
    if attestation_path.exists():
        record['attestations_file'] = attestation_path.name
        record['attestation_claimed_source_dependencies'] = []
        for att in json.loads(attestation_path.read_text(encoding='utf-8')).get('attestations', []):
            envelope = att.get('bundle', {}).get('dsseEnvelope', {})
            if not envelope.get('payload'):
                continue
            statement = json.loads(base64.b64decode(envelope['payload']))
            predicate = statement.get('predicate', {})
            record['attestation_claimed_source_dependencies'].extend(
                predicate.get('buildDefinition', {}).get('resolvedDependencies', []))
    packages.append(record)

converter_wasm = member_bytes(HERE/'libredwg-converter-3.15.1-npm.tgz', '/dist/libredwg-web.wasm')
legacy_archive = HERE/'libredwg-web-1dd682f46339f37b67c5ff1085d10d04a8c16d7e-source.tar.gz'
legacy_wasm = member_bytes(legacy_archive, '/bindings/javascript/wasm/libredwg-web.wasm')
assert converter_wasm == legacy_wasm, 'Deployed converter binary must match retained v0.7.14 source-tree binary'
wasm_comparisons = []
for version in ['0.7.14', '0.7.15', '0.7.16', '0.7.17']:
    wasm = member_bytes(HERE/f'libredwg-web-{version}-npm.tgz', '/wasm/libredwg-web.wasm')
    wasm_comparisons.append({'libredwg_web_npm_version':version,'size':len(wasm),'sha256':sha256(wasm),'matches_converter_wasm':wasm == converter_wasm})
runtime_wasm = ROOT/'assets/libredwg-web.wasm'
if runtime_wasm.exists():
    assert runtime_wasm.read_bytes() == converter_wasm, 'Runtime WASM differs from audited converter binary'
runtime_worker = ROOT/'assets/libredwg-parser-worker.js'
converter_worker = member_bytes(HERE/'libredwg-converter-3.15.1-npm.tgz', '/dist/libredwg-parser-worker.js')
if runtime_worker.exists():
    assert runtime_worker.read_bytes() == converter_worker, 'Runtime worker differs from audited converter worker'

sources = [
    ('mlightcad/cad-viewer','v1.7.4','17cf21a4195ce77a41cc983bc9a22c9e20c14b06'),
    ('mlightcad/realdwg-web','v1.15.1','9f0b3382a9c201c4826a09dd76c2df3ce7067b6d'),
    ('mlightcad/libredwg-web','v0.7.14','1dd682f46339f37b67c5ff1085d10d04a8c16d7e'),
    ('mlightcad/libredwg-web','v0.7.17','2654a724bce3d5f588a0d16b6426a23e3f0fef46'),
    ('zserge/jsmn',None,'85695f3d5903b1cd5b4030efe50db3b4f5f3c928'),
]
source_records = []
for repo, tag, commit in sources:
    archive = HERE/f'{repo.split("/")[1]}-{commit}-source.tar.gz'
    assert archive.is_file(), archive
    source_records.append({'repository':f'https://github.com/{repo}','tag':tag,'commit':commit,'archive':archive.name,'archive_url':f'https://codeload.github.com/{repo}/tar.gz/{commit}'})

for version in ['0.7.14','0.7.17']:
    tree = json.loads((HERE/f'libredwg-web-{version}-tree.json').read_text(encoding='utf-8'))
    gitlinks = [obj for obj in tree['tree'] if obj.get('mode') == '160000']
    assert len(gitlinks) == 1 and gitlinks[0]['path'] == 'jsmn' and gitlinks[0]['sha'] == sources[-1][2]

licenses = ROOT/'licenses'
licenses.mkdir(exist_ok=True)
shutil.copyfile(HERE/'GPL-3.0.txt', licenses/'GPL-3.0.txt')
with tarfile.open(HERE/'realdwg-web-9f0b3382a9c201c4826a09dd76c2df3ce7067b6d-source.tar.gz') as tar:
    for suffix, output in [('/LICENSE','realdwg-web-1.15.1-ROOT-LICENSE')]:
        matches = [m for m in tar.getmembers() if m.isfile() and m.name.count('/') == 1 and m.name.endswith(suffix)]
        if matches:
            (licenses/output).write_bytes(tar.extractfile(matches[0]).read())

files = []
for p in sorted(HERE.iterdir()):
    if p.is_file() and p.name not in ['MANIFEST.json','SHA256SUMS.txt']:
        files.append({'file':p.name,'size':p.stat().st_size,'sha256':sha256(p.read_bytes())})
manifest = {
    'audit_date':'2026-10-10',
    'scope':'Exact retained upstream source/packages for GPL DWG path and principal MIT viewer dependencies; not a legal-compliance certification or a byte-for-byte rebuild claim.',
    'npm_packages':packages,
    'source_archives':source_records,
    'converter_worker_sha256':sha256(converter_worker),
    'converter_wasm_sha256':sha256(converter_wasm),
    'converter_wasm_bytes':len(converter_wasm),
    'converter_wasm_matches_retained_v0_7_14_tree':True,
    'wasm_comparisons':wasm_comparisons,
    'wasm_recompiled_from_C_source':False,
    'upstream_wasm_compiler_version_pinned_in_release_build':False,
    'archive_files':files,
}
(HERE/'MANIFEST.json').write_text(json.dumps(manifest,indent=2)+'\n',encoding='utf-8')
(HERE/'SHA256SUMS.txt').write_text(''.join(f'{r["sha256"]}  {r["file"]}\n' for r in files),encoding='utf-8')
print(f'PASS: {len(packages)} npm integrities, {len(source_records)} source archives, pinned jsmn, runtime worker/WASM comparison. Manifest written.')
