"""Download a selected source package locally, never through the catalog server.
python3 scripts/download_manifest.py MANIFEST_FILE_OR_HTTPS_URL --output ./assets --max-mb 100
No archive extraction or downloaded code execution. Retain upstream attribution files.
"""
import argparse
import hashlib
import json
import math
import os
from pathlib import Path
import re
import tempfile
import urllib.parse
import urllib.request


def safe_relative(value):
 if not isinstance(value,str) or not value or '\\' in value or ':' in value or '\x00' in value:
  raise ValueError('Unsafe relative path')
 if any(x in ('','.','..') for x in value.split('/')) or any(ord(c)<32 for c in value):raise ValueError('Unsafe relative path')
 return value

def no_symlinks(path):
 p=Path(os.path.abspath(path))
 for part in [p,*p.parents]:
  # Canonical macOS system aliases /tmp and /var only.
  if part.is_symlink() and str(part) not in ('/tmp','/var','/etc'):raise ValueError('Symlink target rejected')
 return p

def download(manifest,output,max_bytes):
 asset_id=manifest['id'];revision=manifest['revision'];repo=manifest['repo']
 if not re.fullmatch(r'[A-Za-z0-9_.-]+',asset_id) or asset_id in ('.','..'):raise ValueError('Invalid asset ID')
 if repo!='nvidia/simready-assets' or not re.fullmatch(r'[0-9a-fA-F]{40}',revision):raise ValueError('Unexpected source repository/revision')
 files=manifest['files'];seen=set();total=0
 if not isinstance(files,list) or not files or len(files)>5000:raise ValueError('Invalid file enumeration')
 for f in files:
  rel=safe_relative(f['path'])
  if rel in seen:raise ValueError('Duplicate manifest file')
  seen.add(rel)
  if isinstance(f['size'],bool) or not isinstance(f['size'],int) or f['size']<0:raise ValueError('Invalid size')
  expected='https://huggingface.co/datasets/'+repo+'/resolve/'+revision+'/'+urllib.parse.quote(asset_id+'/'+rel,safe='/')
  if f['url']!=expected:raise ValueError('URL is not the pinned upstream file')
  if f.get('sha256') is not None and not re.fullmatch('[0-9a-fA-F]{64}',f['sha256']):raise ValueError('Invalid SHA256')
  total+=f['size']
 if manifest['total_files']!=len(files) or manifest['total_bytes']!=total:raise ValueError('Manifest totals disagree')
 if total>max_bytes:raise ValueError('Package exceeds explicit download budget')
 for r in manifest.get('root_usds',[]):
  if safe_relative(r) not in seen:raise ValueError('Entry USD absent from manifest')
 base=no_symlinks(Path(output)/asset_id);base.mkdir(parents=True,exist_ok=True)
 verified=0;reused=0
 for f in files:
  target=no_symlinks(base/f['path']);target.parent.mkdir(parents=True,exist_ok=True)
  good=False
  if target.exists() and target.stat().st_size==f['size'] and f.get('sha256'):
   digest=hashlib.sha256()
   with target.open('rb') as cached:
    for chunk in iter(lambda:cached.read(1024*1024),b''):digest.update(chunk)
   good=digest.hexdigest()==f['sha256'].lower()
  if good:reused+=1
  else:
   fd,temp=tempfile.mkstemp(prefix='.download-',suffix='.part',dir=target.parent);digest=hashlib.sha256();count=0
   try:
    with os.fdopen(fd,'wb') as out, urllib.request.urlopen(f['url'],timeout=60) as response:
     while True:
      chunk=response.read(1024*1024)
      if not chunk:break
      count+=len(chunk)
      if count>f['size']:raise ValueError('File exceeds manifest size')
      digest.update(chunk);out.write(chunk)
    if count!=f['size']:raise ValueError('Truncated source file')
    if f.get('sha256') and digest.hexdigest()!=f['sha256'].lower():raise ValueError('Source hash mismatch')
    no_symlinks(target);os.replace(temp,target)
   finally:
    if os.path.exists(temp):os.unlink(temp)
  verified+=bool(f.get('sha256'))
 result={'id':asset_id,'directory':str(base),'files':len(files),'bytes':total,'verified_hashes':verified,
         'missing_hashes':len(files)-verified,'reused_files':reused,'root_usds':manifest.get('root_usds',[]),
         'license':manifest.get('license','unknown'),'dcc_validation':'unverified'}
 return result

def main():
 p=argparse.ArgumentParser(description=__doc__);p.add_argument('manifest');p.add_argument('--output',default='./assets');p.add_argument('--max-mb',type=float,default=100)
 a=p.parse_args()
 if not math.isfinite(a.max_mb) or a.max_mb<=0:raise ValueError('Positive finite download budget required')
 if a.manifest.startswith('https://'):
  with urllib.request.urlopen(a.manifest,timeout=60) as response:raw=response.read(8*1024*1024+1)
  if len(raw)>8*1024*1024:raise ValueError('Manifest too large')
 else:raw=Path(a.manifest).read_bytes()
 print(json.dumps(download(json.loads(raw),a.output,int(a.max_mb*1024*1024)),ensure_ascii=False,indent=2))

if __name__=='__main__':main()
