import datetime
import hashlib
import json
import os
from pathlib import Path
import resource
import shutil
import subprocess
import sys
import time

ROOT = Path(__file__).resolve().parent.parent
EXPORT = ROOT.parent/'2026-11-03_contextra-offline-export_r01'
REPO = Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
DELIVERY = ROOT/'Contextra_Offline_USB_20261103_r01'
OUTPUT = DELIVERY/'package'
COMMIT = '31ed63dab239b3f8a08a745ade78b929a0a9d5d7'
GIB = 1024**3
sys.path[:0] = [str(REPO), str(EXPORT/'helpers')]
from deployment_kit import bundle
from deployment_kit.core import KitError, require, write_json
from deployment_kit.offline import package
from deployment_kit.offline.host import Host
from prepare_evidence import snapshot


def git(*args):
    return subprocess.check_output(['git','-C',str(REPO),*args],text=True,timeout=30).strip()


def main():
    os.environ['DOCKER_HOST'] = 'unix:///var/run/docker.sock'
    os.environ.pop('DOCKER_CONTEXT',None)
    os.sched_setaffinity(0,sorted(os.sched_getaffinity(0))[:2])
    resource.setrlimit(resource.RLIMIT_AS,(8*GIB,8*GIB))
    os.nice(10)
    if shutil.which('ionice'):
        subprocess.run(['ionice','-c','2','-n','7','-p',str(os.getpid())],check=True,timeout=10)
    require(git('rev-parse','HEAD')==COMMIT and not git('status','--porcelain'),
            'SOURCE_CHANGED','승인 후 source 상태가 달라져 재확인이 필요합니다.')
    require(not OUTPUT.exists(),'OUTPUT_EXISTS','기존 package는 덮어쓰지 않습니다.')
    inputs=json.loads((EXPORT/'reports/package-inputs.json').read_bytes())
    hashes=json.loads((EXPORT/'reports/source-lock.json').read_bytes())
    tracked=bundle.committed_files(REPO,COMMIT)
    for name in inputs['source_paths']:
        require(package.sha256(REPO/name)==hashes[name],'SOURCE_CHANGED','검증 source와 다릅니다.')
        bundle.checked_source(REPO,name,tracked[name])
    total=sum(Path(v['source']).stat().st_size for v in inputs['assets'].values())
    require(shutil.disk_usage(ROOT).free>=total+15*GIB,'OUTPUT_SPACE','봉인 출력과15GiB 여유가 필요합니다.')
    baseline=snapshot()
    host=Host(); host_snapshot=host.snapshot()
    spec=dict(inputs['spec'],source_commit=COMMIT)
    require(all(host_snapshot[k]==spec['target'][k] for k in ('server','backend','definition_sha256'))
            and host_snapshot['images']==spec['before_images'],'TARGET_DRIFT','현재 설치 기준이 다릅니다.')
    write_json(ROOT/'reports/before.json',baseline,new=True)
    write_json(ROOT/'reports/source-gate.json',dict(commit=COMMIT,source_files=len(hashes),archives=6,
               source_lock=True,installed_tools=True,target_match=True,base_images_match=True),new=True)
    started=time.monotonic(); checks={}; stages=[]; min_root=[baseline['free']['/']]
    original_sha, original_copy = package.sha256, shutil.copyfile

    def guard():
        free=shutil.disk_usage('/').free;min_root[0]=min(min_root[0],free)
        require(free>=12*GIB,'ROOT_RESERVE','root 여유가 선제 중단선 아래입니다.')
        require(shutil.disk_usage(ROOT).free>=15*GIB,'OUTPUT_SPACE','출력 여유15GiB 미만입니다.')

    def stage(name,path,done=False):
        guard()
        item=dict(stage=name,file=Path(path).name,done=done,elapsed=round(time.monotonic()-started,2),
                  root_free=shutil.disk_usage('/').free,data_free=shutil.disk_usage(ROOT).free)
        stages.append(item)
        write_json(ROOT/'reports/seal-progress.json',dict(status='RUNNING',current=item,history=stages))
        print(json.dumps(item),flush=True)

    def observed_sha(path):
        path=Path(path); large=path.stat().st_size>GIB
        if large:
            checks[str(path)]=checks.get(str(path),0)+1
            phase='HASH_PACKAGE' if path.is_relative_to(OUTPUT) else 'HASH_SOURCE_'+str(checks[str(path)])
            stage(phase,path)
        value=original_sha(path)
        if large:stage(phase,path,True)
        return value

    def observed_copy(src,dst,*args,**kwargs):
        large=Path(src).stat().st_size>GIB
        if large:
            require(shutil.disk_usage(ROOT).free>=Path(src).stat().st_size+15*GIB,'OUTPUT_SPACE','복사 공간이 부족합니다.')
            stage('COPY_PACKAGE',src)
        result=original_copy(src,dst,*args,**kwargs)
        if large:stage('COPY_PACKAGE',src,True)
        return result

    try:
        package.sha256=observed_sha
        shutil.copyfile=observed_copy
        digest=package.create(REPO,OUTPUT,spec,inputs['source_paths'],inputs['assets'])
    except BaseException as exc:
        write_json(ROOT/'reports/seal-progress.json',dict(status='BLOCKED',code=getattr(exc,'code',type(exc).__name__),history=stages))
        raise
    finally:
        package.sha256=original_sha
        shutil.copyfile=original_copy
    raw=(OUTPUT/'package.json').read_bytes()
    require(hashlib.sha256(raw).hexdigest()==digest,'MANIFEST_DRIFT','봉인 manifest가 바뀌었습니다.')
    m=package.validate(json.loads(raw))
    require(all(m['files'][n]['sha256']==h for n,h in hashes.items()),'SOURCE_DRIFT','봉인 source 지문 불일치입니다.')
    require(all(m['files'][n]['sha256']==v['sha256'] for n,v in inputs['assets'].items()),'ASSET_DRIFT','봉인 archive 지문 불일치입니다.')
    after=snapshot()
    require(after['containers']==baseline['containers'] and after['files']==baseline['files'],
            'OPERATIONAL_CHANGE','운영 상태/설정 변화가 있어 전달을 중단합니다.')
    require(git('rev-parse','HEAD')==COMMIT,'SOURCE_CHANGED','봉인 중 Git 기준이 바뀌었습니다.')
    result=dict(timestamp=datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).isoformat(),
                status='SEALED_PACKAGE_VERIFIED',package=str(OUTPUT),sha256=digest,source_commit=COMMIT,
                files=len(m['files']),archives=len(m['archives']),bytes=sum(v['size'] for v in m['files'].values()),
                full_package_readback=True,source_lock_match=True,asset_hash_match=True,operating_state_unchanged=True,
                min_root_free=min_root[0],root_free=after['free']['/'],data_free=after['free']['/data'],
                elapsed_seconds=round(time.monotonic()-started,2),runtime_applied=False,windows_copy=False,usb_copy=False)
    write_json(ROOT/'reports/after.json',after,new=True)
    write_json(ROOT/'reports/sealed-package.json',result,new=True)
    write_json(ROOT/'reports/seal-progress.json',dict(status='COMPLETE',history=stages,result=result))
    print(json.dumps(result),flush=True)


if __name__=='__main__':
    try:
        main()
    except KitError as exc:
        print(json.dumps(dict(status='BLOCKED',code=exc.code,message=exc.message),ensure_ascii=False),flush=True)
        raise SystemExit(2)
