import hashlib,json,os,pathlib,shutil,signal,subprocess,sys,tarfile,time
from gate import g,R
os.umask(0o077)
HEAD='4bdf50cf2013f1a2f6e9fe85cbb1ec0634b4efa4'
BASE_R=R
if len(sys.argv)>2:
 assert sys.argv[2]=='build-r02'
 R=R/sys.argv[2]
EDITED=['.dockerignore','docker/Dockerfile','docker/requirements-tracking.txt','app/metadata_samples.py','app/det_result_path.py','tests/test_metadata_samples.py']
def fingerprint():return {p:g.sha(g.REPO/p) for p in EDITED}
def source_cas():
 assert g.sh(['git','-C',str(g.REPO),'rev-parse','HEAD']).strip()==HEAD
 assert fingerprint()==json.loads((R/'source-manifest.json').read_text())['edited']
def prepare():
 if R!=BASE_R:
  R.mkdir(mode=0o700)
  for name in ['gate.json','source-verify.json','deps-report.json','verify.py']:
   shutil.copyfile(BASE_R/name,R/name)
 assert not (R/'source-manifest.json').exists()
 assert g.sh(['git','-C',str(g.REPO),'rev-parse','HEAD']).strip()==HEAD
 assert sorted(l[3:] for l in g.sh(['git','-C',str(g.REPO),'status','--porcelain']).splitlines())==sorted(EDITED)
 assert not g.sh(['git','-C',str(g.REPO),'diff','--cached','--name-only']).strip()
 assert json.loads((R/'source-verify.json').read_text())['status']=='PASS'
 changed=fingerprint();digest=hashlib.sha256(json.dumps(changed,sort_keys=True).encode()).hexdigest()
 tag='contextra-analytics:metadata-4bdf50c-'+digest[:8]
 assert subprocess.run(['docker','image','inspect',tag],capture_output=True).returncode!=0
 snap=R/'snapshot';snap.mkdir(mode=0o700)
 p=subprocess.Popen(['git','-C',str(g.REPO),'archive',HEAD],stdout=subprocess.PIPE)
 with tarfile.open(fileobj=p.stdout,mode='r|') as archive:archive.extractall(snap,filter='data')
 assert p.wait()==0
 for path in EDITED:shutil.copyfile(g.REPO/path,snap/path)
 assert fingerprint()==changed
 (R/'source-manifest.json').write_text(json.dumps(dict(base=HEAD,edited=changed,digest=digest,tag=tag),indent=2))
 (R/'source-hashes.json').write_text(json.dumps({str(p.relative_to(snap)):g.sha(p) for p in (snap/'app').rglob('*.py')},indent=2))
 print(json.dumps(dict(tag=tag,digest=digest)))
def build():
 source_cas();assert not (R/'build.json').exists()
 gate=json.loads((R/'gate.json').read_text());meta=json.loads((R/'source-manifest.json').read_text())
 assert shutil.disk_usage(gate['docker_root']).free>=15*1024**3
 assert subprocess.run(['pgrep','-f',r'(^|/)docker (build|buildx build|pull)'],capture_output=True).returncode==1
 start=time.monotonic();reason=None
 with (R/'build.log').open('w') as log:
  p=subprocess.Popen(['docker','build','-f','docker/Dockerfile','--target','app','-t',meta['tag'],'.'],cwd=R/'snapshot',
    stdout=log,stderr=log,start_new_session=True,env=dict(os.environ,DOCKER_BUILDKIT='1'))
  while p.poll() is None:
   if time.monotonic()-start>1200 or shutil.disk_usage(gate['docker_root']).free<15*1024**3:
    reason='TIME_OR_DISK_CAP';os.killpg(p.pid,signal.SIGTERM)
    try:p.wait(15)
    except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL)
    break
   time.sleep(2)
  code=p.wait()
 out=dict(returncode=code,abort_reason=reason,elapsed_s=time.monotonic()-start,tag=meta['tag'])
 if code==0 and reason is None:out['image_id']=json.loads(g.sh(['docker','image','inspect',meta['tag']]))[0]['Id']
 (R/'build.json').write_text(json.dumps(out,indent=2));print(json.dumps(out));assert code==0 and reason is None
if __name__=='__main__':{'prepare':prepare,'build':build}[sys.argv[1]]()
