"""Install only verified repair-source files, backing up replaced code; never touch ledgers/secrets/contracts."""
import argparse,hashlib,json,os,shutil,sys,tempfile,uuid
from pathlib import Path
from infinbot_common import private_dir,Rejected,atomic_json,timestamp


def install(target):
    root=Path(__file__).resolve().parent;target=Path(target).absolute()
    if sys.platform!='darwin':raise Rejected('Run installation on the intended Mac')
    if not target.is_dir() or target.is_symlink():raise Rejected('Existing non-symlink project directory required')
    manifest=json.loads((root/'repair-manifest.json').read_text())
    for row in manifest['files']:
        rel=Path(row['path'])
        if rel.is_absolute() or '..' in rel.parts or rel.suffix not in ('.py','.sql','.txt','.html','.json'):
            raise Rejected('Invalid source-only manifest')
        source=root/rel
        if source.is_symlink() or hashlib.sha256(source.read_bytes()).hexdigest()!=row['sha256']:raise Rejected('Package integrity mismatch')
        destination=target/rel
        if any(p.is_symlink() for p in (destination,*destination.parents) if p!=Path('/')):raise Rejected('Symlink target rejected')
    vault=private_dir(target/'private');backup=private_dir(vault/'code-backups'/uuid.uuid4().hex)
    installed=[]
    try:
        for row in manifest['files']:
            rel=Path(row['path']);source=root/rel;destination=target/rel
            destination.parent.mkdir(parents=True,exist_ok=True)
            if destination.exists():
                copy=backup/rel;copy.parent.mkdir(parents=True,exist_ok=True,mode=0o700)
                shutil.copy2(destination,copy);copy.chmod(0o600)
            fd,name=tempfile.mkstemp(prefix='.infinbot-code-',dir=destination.parent)
            try:
                with os.fdopen(fd,'wb') as f:f.write(source.read_bytes());f.flush();os.fsync(f.fileno())
                os.replace(name,destination);installed.append(str(rel))
            finally:
                if os.path.exists(name):os.unlink(name)
        receipt={'status':'REPAIR_SOURCE_INSTALLED','product':'InFinBot','installed_at':timestamp(),
                 'code_backup':str(backup),'installed_files':installed,'ledgers_modified':False,
                 'broker_credentials_modified':False,'lgc_contract_modified':False,'schedules_installed':False}
        atomic_json(backup/'install-receipt.json',receipt);return receipt
    except Exception:
        atomic_json(backup/'partial-install-receipt.json',{'status':'PARTIAL_INSTALL_REVIEW_REQUIRED','installed_files':installed,'original_code_backup':str(backup)})
        raise


if __name__=='__main__':
    p=argparse.ArgumentParser(description=__doc__);p.add_argument('--target',default=str(Path.home()/'ForesixaQuant'));a=p.parse_args()
    try:print(json.dumps(install(a.target),indent=2))
    except Exception:print(json.dumps({'status':'INSTALL_BLOCKED_OR_PARTIAL','reason':'Inspect package integrity, paths and code-backup receipts; no ledger/credential/contract overwritten'}));raise SystemExit(1)
