#!/usr/bin/env python3
"""Build the verified Phantasy Star Nova complete-English Vita3K CPK.

Inputs supplied by the user:
  1) clean base NOVA_FileList_Vita.cpk from PCSG00351
  2) the public Arks-Layer English partial patch ZIP or its NOVA_FileList_Vita_patch.cpk

This repository supplies translation data and build tooling, not copyrighted game assets.
"""
from pathlib import Path
import argparse, hashlib, os, shutil, subprocess, sys, tempfile, zipfile

ROOT=Path(__file__).resolve().parent.parent
EXPECTED_APP_SHA='ad85c330c3258b0f18f57dcf77736f9fbea89c40b66dfef3fbb5c4daeff77e7a'
EXPECTED_APP_SIZE=2834095024
EXPECTED_PUBLIC_PATCH_SHA='50c733ec4ab5ccc57c6d7e1e0f30d6b8dca2f0ad8104a00f31ca33294bd0466a'
EXPECTED_COMPLETE_OVERLAY_SHA='887a32c15927c227e989366f28f1624c95d7dbef9d8b781c4a6266040a160d17'
EXPECTED_FINAL_SHA='ebf2cea4c8158f32557d52c58b111101c1818157d862cd7b1c570b096730e4ba'
EXPECTED_FINAL_SIZE=3252228483

def sha256_file(p,bs=16<<20):
    h=hashlib.sha256()
    with open(p,'rb') as f:
        while True:
            b=f.read(bs)
            if not b: break
            h.update(b)
    return h.hexdigest()

def run(cmd,env=None):
    print('>', ' '.join(map(str,cmd)), flush=True)
    subprocess.run(list(map(str,cmd)),check=True,env=env)

def resolve_public_patch(src:Path,work:Path)->Path:
    if src.suffix.lower()!='.zip':
        return src.resolve()
    out=work/'NOVA_FileList_Vita_patch_PUBLIC.cpk'
    with zipfile.ZipFile(src) as z:
        candidates=[n for n in z.namelist() if Path(n).name.lower()=='nova_filelist_vita_patch.cpk' and '__macosx' not in n.lower()]
        if len(candidates)!=1:
            raise SystemExit(f'Could not uniquely find NOVA_FileList_Vita_patch.cpk in {src}. Candidates={candidates[:10]}')
        with z.open(candidates[0]) as f, out.open('wb') as g:
            shutil.copyfileobj(f,g,8<<20)
    return out

def make_repo_zip(out:Path):
    with zipfile.ZipFile(out,'w',compression=zipfile.ZIP_DEFLATED,compresslevel=6) as z:
        for p in sorted((ROOT/'corpus/rmd').glob('*.json')):
            z.write(p,arcname='repo/rmd/'+p.name)

def main():
    ap=argparse.ArgumentParser(description='End-to-end PS Nova complete-English Vita3K builder')
    ap.add_argument('--app',required=True,help='Clean base ux0/app/PCSG00351/NOVA_FileList_Vita.cpk')
    ap.add_argument('--public-patch',required=True,help='Public Arks-Layer partial English patch ZIP or its CPK (NOT the official v1.05 game update)')
    ap.add_argument('--output',default='NOVA_FileList_Vita_FULL_ENGLISH.cpk')
    ap.add_argument('--work',default='_psnova_build_work')
    ap.add_argument('--keep-work',action='store_true')
    a=ap.parse_args()
    app=Path(a.app).resolve(); public=Path(a.public_patch).resolve(); out=Path(a.output).resolve(); work=Path(a.work).resolve()
    if not app.is_file(): raise SystemExit('APP CPK not found: '+str(app))
    if not public.is_file(): raise SystemExit('Public patch input not found: '+str(public))
    work.mkdir(parents=True,exist_ok=True)
    success=False
    try:
        print('[1/8] Verifying clean APP CPK...')
        ash=sha256_file(app)
        if app.stat().st_size!=EXPECTED_APP_SIZE or ash!=EXPECTED_APP_SHA:
            raise SystemExit(f'Wrong APP CPK. Expected {EXPECTED_APP_SIZE} bytes / {EXPECTED_APP_SHA}; got {app.stat().st_size} / {ash}')

        print('[2/8] Resolving and verifying public Arks-Layer partial patch...')
        patch=resolve_public_patch(public,work)
        psha=sha256_file(patch)
        if psha!=EXPECTED_PUBLIC_PATCH_SHA:
            raise SystemExit('Wrong public partial-patch CPK. Expected SHA-256 '+EXPECTED_PUBLIC_PATCH_SHA+'; got '+psha+'\nDo not use the official game v1.05 update CPK here.')

        print('[3/8] Preparing translation repository index...')
        repo_zip=work/'translation_repository.zip'; make_repo_zip(repo_zip)

        print('[4/8] Building canonical RMD templates (public patch first, base APP second)...')
        canonical=work/'canonical'
        run([sys.executable,ROOT/'tools/build_canonical_sources.py','--app-cpk',app,'--patch-cpk',patch,'--repo',repo_zip,'--out',canonical,'--force'])

        print('[5/8] Compiling 24,850 English RMDs...')
        compiled=work/'compiled'
        env=os.environ.copy(); env['PSNOVA_SCRIPTS']=str(canonical/'scripts')
        run([sys.executable,ROOT/'tools/psnova_textinserter_py.py','--output',str(compiled)],env=env)

        print('[6/8] Building complete English overlay...')
        overlay=work/'NOVA_FileList_Vita_patch_COMPLETE.cpk'
        env=os.environ.copy(); env['PSNOVA_OLD_PATCH_CPK']=str(patch); env['PSNOVA_COMPILED_DIR']=str(compiled); env['PSNOVA_RUNTIME_CPK_OUT']=str(overlay)
        run([sys.executable,ROOT/'tools/build_runtime_cpk.py'],env=env)
        osha=sha256_file(overlay)
        if osha!=EXPECTED_COMPLETE_OVERLAY_SHA:
            raise SystemExit('Complete overlay hash mismatch. Expected '+EXPECTED_COMPLETE_OVERLAY_SHA+'; got '+osha)

        print('[7/8] Building full Vita3K replacement CPK...')
        run([sys.executable,ROOT/'tools/build_vita3k_full_merge.py','--app',app,'--overlay',overlay,'--manifest',ROOT/'manifests/compiled_manifest.csv','--output',out])

        print('[8/8] Final verification...')
        fsha=sha256_file(out); fsize=out.stat().st_size
        if fsha!=EXPECTED_FINAL_SHA or fsize!=EXPECTED_FINAL_SIZE:
            raise SystemExit(f'Final output mismatch. Expected {EXPECTED_FINAL_SIZE} / {EXPECTED_FINAL_SHA}; got {fsize} / {fsha}')
        print('\nPASS')
        print('Output:',out)
        print('Size:',fsize)
        print('SHA-256:',fsha)
        print('\nVita3K: back up ux0/app/PCSG00351/NOVA_FileList_Vita.cpk, rename this output to NOVA_FileList_Vita.cpk, and replace ONLY that file.')
        print('If using game update v1.05, keep it separately under ux0/patch/PCSG00351.')
        success=True
    finally:
        if success and not a.keep_work and work.exists():
            shutil.rmtree(work,ignore_errors=True)

if __name__=='__main__': main()
