"""Fetch only named immutable origins, verify SHA256, extract named members locally."""
from pathlib import Path
import argparse, hashlib, json, tarfile, urllib.request

def fetch(manifest, output):
    output.mkdir(parents=True, exist_ok=True)
    receipts=[]
    for source in manifest['sources']:
        archive=output/source['archive']
        cached=archive.exists()
        if not cached:
            request=urllib.request.Request(source['url'],headers={'User-Agent':'SandboxPhysics-AC27-independent-science/1'})
            with urllib.request.urlopen(request,timeout=60) as response:
                if int(response.headers.get('Content-Length','0'))>source['bytes']:raise ValueError('Origin exceeds named byte size')
                body=response.read(source['bytes']+1)
            if len(body)!=source['bytes'] or hashlib.sha256(body).hexdigest()!=source['sha256']:raise ValueError('Origin size/hash mismatch; nothing extracted')
            archive.write_bytes(body)
        body=archive.read_bytes()
        if len(body)!=source['bytes'] or hashlib.sha256(body).hexdigest()!=source['sha256']:raise ValueError('Cached source corrupt; refusing to replace silently')
        destination=output/source['directory'];destination.mkdir(exist_ok=True)
        with tarfile.open(archive) as container:
            for member in source['members']:
                metadata=container.getmember(member['name'])
                if not metadata.isfile() or metadata.size!=member['bytes'] or Path(metadata.name).name!=metadata.name:raise ValueError('Unexpected source member')
                content=container.extractfile(metadata).read()
                if hashlib.sha256(content).hexdigest()!=member['sha256']:raise ValueError('Source member hash mismatch')
                (destination/member['name']).write_bytes(content)
        receipts.append({'url':source['url'],'bytes':source['bytes'],'sha256':source['sha256'],'cache':cached,'response_body_bytes':0 if cached else source['bytes']})
    return receipts
if __name__=='__main__':
    parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--output',type=Path,default=Path('inputs'));args=parser.parse_args()
    receipts=fetch(json.loads(Path('source-manifest.json').read_text()),args.output)
    (args.output/'download-receipt.json').write_text(json.dumps(receipts,indent=2)+'\n')
    print(json.dumps(receipts,indent=2))
