#!/usr/bin/env python3
"""Package accepted binaries/seed without rebuilding them; normalize all tar metadata."""
import argparse
import gzip
import hashlib
import json
from pathlib import Path
import shutil
import tarfile


def sha(path):
    return hashlib.file_digest(Path(path).open('rb'), 'sha256').hexdigest()


def package(source, web, output):
    source, web, output = map(Path, (source, web, output))
    if output.exists():
        raise ValueError('release output must be fresh')
    manifest = json.loads((source/'release.json').read_text())
    if manifest['product'] != 'kaiplus':
        raise ValueError('not a KaiPlus release')
    web_hash = hashlib.sha256()
    web_files = sorted(p for p in web.rglob('*') if p.is_file())
    for path in web_files:
        if path.is_symlink():
            raise ValueError('web assets contain an alias')
        web_hash.update(path.relative_to(web).as_posix().encode()+b'\0'+path.read_bytes()+b'\0')
    if manifest['webAssetsDigest'] != 'sha256:'+web_hash.hexdigest():
        raise ValueError('frontend differs from the accepted embedded frontend')
    for artifact in manifest['artifacts']:
        path = source/artifact['file']
        if path.is_symlink() or sha(path) != artifact['sha256'] or path.stat().st_size != artifact['size']:
            raise ValueError('accepted binary identity mismatch')
    seed_index = json.loads((source/'skills-v2/index.json').read_text())
    if sha(source/'skills-v2'/seed_index['archive']) != seed_index['sha256']:
        raise ValueError('accepted complete snapshot archive mismatch')
    if sha(source/'skills-v2/snapshot.json') != seed_index['manifestSHA256']:
        raise ValueError('accepted complete snapshot manifest mismatch')
    output.mkdir(parents=True)
    for artifact in manifest['artifacts']:
        shutil.copyfile(source/artifact['file'], output/artifact['file'])
        (output/artifact['file']).chmod(0o755)
    shutil.copytree(source/'skills-v2', output/'skills-v2')
    shutil.copyfile(Path(__file__), output/'release-packager.py')
    root = 'kaiplus-binary-'+manifest['version']
    archive = output/(root+'.tar.gz')
    files = {}
    for artifact in manifest['artifacts']:
        arch = {'amd64':'x86_64','arm64':'aarch64'}[artifact['arch']]
        files['kaiplus.'+arch] = (source/artifact['file'],0o755)
        # Helpers use the exact accepted CLI bytes; their native helper entry
        # point is already compiled into this executable, with identical version.
        files['helpers/kaiplus_workspace_tool.'+arch] = (source/artifact['file'],0o755)
    for path in web_files:
        files['www/'+path.relative_to(web).as_posix()] = (path,0o644)
    with archive.open('wb') as raw, gzip.GzipFile(filename='', fileobj=raw, mode='wb', mtime=0) as compressed:
        with tarfile.open(fileobj=compressed, mode='w', format=tarfile.GNU_FORMAT) as tar:
            for name,(path,mode) in sorted(files.items()):
                info=tarfile.TarInfo(root+'/'+name)
                info.size=path.stat().st_size;info.mode=mode;info.mtime=info.uid=info.gid=0
                info.uname=info.gname=''
                with path.open('rb') as body:tar.addfile(info,body)
    manifest['packages']=[{'file':archive.name,'sha256':sha(archive),'size':archive.stat().st_size}]
    manifest['packaging']={'tool':'release-packager.py','sha256':sha(output/'release-packager.py'),
                           'format':'normalized-gnu-tar-gzip','epoch':0,
                           'frontendIncluded':True,'workspaceHelpersUseAcceptedCLI':True}
    (output/'release.json').write_text(json.dumps(manifest,indent=2)+'\n')
    paths=sorted(p for p in output.rglob('*') if p.is_file())
    (output/'SHA256SUMS').write_text(''.join(sha(p)+'  '+p.relative_to(output).as_posix()+'\n' for p in paths))
    return manifest


def main():
    p=argparse.ArgumentParser(description=__doc__)
    p.add_argument('--source',type=Path,required=True);p.add_argument('--web',type=Path,required=True);p.add_argument('--output',type=Path,required=True)
    a=p.parse_args();manifest=package(a.source,a.web,a.output)
    print(json.dumps({'product':manifest['product'],'version':manifest['version'],'packages':manifest['packages'],'skills':manifest['skills']['version']}))


if __name__=='__main__':main()
