"""Deploy reviewed narrow Laravel source patches with verified source backups."""
import argparse
import base64
import hashlib
import importlib.util
import json
from pathlib import Path
import subprocess

ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location('deployment_client', ROOT / 'scripts/deploy.py')
client = importlib.util.module_from_spec(spec)
spec.loader.exec_module(client)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--release', required=True)
parser.add_argument('--production', action='store_true')
parser.add_argument('--bundle', choices=['photo', 'audit', 'login'], default='photo')
args = parser.parse_args()
if not args.production:
    raise ValueError('Explicit --production is required')
revision = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip()
if revision != args.release or subprocess.check_output(['git', 'status', '--porcelain'], cwd=ROOT, text=True).strip():
    raise ValueError('Requires clean reviewed release matching HEAD')
config = client.configuration(ROOT / '.deploy/config.json')
bundle = 'round2' if args.bundle == 'photo' else args.bundle
snapshot_path = '.deploy/round2-inspection.json' if args.bundle == 'photo' else '.deploy/audit/production.json'
snapshot = json.loads((ROOT / snapshot_path).read_text())
manifest = json.loads((ROOT / f'integrations/laravel/{bundle}/source-manifest.json').read_text())
patch_name = {'photo': 'profile-image.patch', 'audit': 'containment.patch', 'login': 'verified-login.patch'}[args.bundle]
import tempfile
with tempfile.TemporaryDirectory() as folder:
    for name, item in manifest.items():
        if item['baseline_sha256'] is None:
            continue
        source = snapshot['php_files'][name]
        if hashlib.sha256(source.encode()).hexdigest() != item['baseline_sha256']:
            raise ValueError('Inspected PHP baseline changed')
        target = Path(folder) / name
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(source)
    subprocess.run(['git', 'apply', '--unidiff-zero', str(ROOT / f'integrations/laravel/{bundle}/{patch_name}')], cwd=folder, check=True)
    if args.bundle == 'photo':
        target = Path(folder) / 'app/Services/ProfileImage.php'
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes((ROOT / 'integrations/laravel/app/Services/ProfileImage.php').read_bytes())
    if args.bundle == 'login':
        for name in ['app/Services/GoogleIdentity.php', 'app/Services/SocialLogin.php', 'config/social_login.php',
                     'database/migrations/2026_10_20_000001_add_google_subject_to_users.php']:
            target = Path(folder) / name
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes((ROOT / 'integrations/laravel/login' / name).read_bytes())
            manifest[name] = {'baseline_sha256': None, 'candidate_sha256': hashlib.sha256(target.read_bytes()).hexdigest()}
    files = {name: (Path(folder) / name).read_text() for name in manifest}
for name, source in files.items():
    if hashlib.sha256(source.encode()).hexdigest() != manifest[name]['candidate_sha256']:
        raise ValueError('Candidate hash mismatch')
payload = base64.b64encode(json.dumps({'release': revision, 'files': files, 'manifest': manifest,
                                     'bundle': args.bundle}).encode()).decode()
code = r'''
import base64, fcntl, hashlib, json, os, pwd, shutil, subprocess, sys
from pathlib import Path
os.umask(0o077)
payload=json.loads(base64.b64decode(sys.argv[1]))
root=Path('/www/wwwroot/app.connectlyvideochat.com')
work=Path('/www/backup/bondvia-deploy/round2')/payload['release']
if root.is_symlink() or work.exists(): raise ValueError('Unsafe target or release already attempted')
lock=open('/www/backup/bondvia-deploy/round2.lock','a')
fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
def digest(p): return hashlib.sha256(p.read_bytes()).hexdigest()
if payload.get('bundle')=='login':
 if (root/'bootstrap/cache/config.php').exists() or list((root/'bootstrap/cache').glob('routes*.php')):
  raise ValueError('Cached Laravel configuration/routes require a separate coordinated review')
 for line in (root/'.env').read_text().splitlines():
  if '=' in line and line.split('=',1)[0].strip()=='VERIFIED_LOGIN_ENABLED':
   if line.split('=',1)[1].strip().strip('"\'').lower() not in ['false','0','', '(false)']:
    raise ValueError('Verified login must remain disabled during additive deployment')
for name,item in payload['manifest'].items():
 p=root/name
 if Path(name).is_absolute() or '..' in Path(name).parts or root not in p.resolve().parents:
  raise ValueError('Unsafe source path')
 if p.is_symlink(): raise ValueError('Symlink source rejected')
 if item['baseline_sha256'] is None:
  if p.exists(): raise ValueError('Unexpected existing service')
 elif digest(p)!=item['baseline_sha256']: raise ValueError('Production source drift')
work.mkdir(parents=True,mode=0o700)
backup=work/'backup';backup.mkdir()
stage=work/'candidate';stage.mkdir()
for name,source in payload['files'].items():
 p=stage/name;p.parent.mkdir(parents=True,exist_ok=True);p.write_text(source)
 if digest(p)!=payload['manifest'][name]['candidate_sha256']: raise ValueError('Candidate integrity failed')
 subprocess.run(['/www/server/php/83/bin/php','-l',str(p)],check=True,stdout=subprocess.DEVNULL)
 old=root/name
 if old.exists():
  dest=backup/name;dest.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(old,dest)
  if digest(dest)!=payload['manifest'][name]['baseline_sha256']: raise ValueError('Backup verification failed')
(work/'manifest.json').write_text(json.dumps(payload['manifest'],indent=2))
account=pwd.getpwnam('www')
applied=[]
try:
 # Dependencies first, then the controller, with new route registration last.
 order=sorted(payload['files'],key=lambda name:(name.startswith('routes/'),name.endswith('/AuthApi.php'),name)) \
  if payload.get('bundle')=='login' else sorted(payload['files'],reverse=True)
 for name in order:
  target=root/name;temp=target.with_name(target.name+'.round2-new')
  target.parent.mkdir(parents=True,exist_ok=True,mode=0o755)
  shutil.copy2(stage/name,temp);os.chown(temp,account.pw_uid,account.pw_gid);os.chmod(temp,0o644)
  os.replace(temp,target);applied.append(name)
 for name,item in payload['manifest'].items():
  if digest(root/name)!=item['candidate_sha256']: raise ValueError('Deployed hash mismatch')
 if payload.get('bundle')=='login':
  check=r"""require 'vendor/autoload.php'; $app=require 'bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
if(config('social_login.enabled',false)) throw new RuntimeException('Verified login unexpectedly enabled');
$routes=app('router')->getRoutes();
foreach(['GET'=>'userLoginGoogle','POST'=>'v2/userLoginGoogle'] as $verb=>$uri){
 $route=$routes->match(Illuminate\Http\Request::create('/'.$uri,$verb));
 $expected=$verb==='GET'?'userLoginGoogle':'userLoginVerified';
 if(!str_ends_with($route->getActionName(),'@'.$expected)) throw new RuntimeException('Login route mismatch');
}
"""
  subprocess.run(['runuser','-u','www','--','/www/server/php/83/bin/php','-r',check],cwd=root,
                 check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
except BaseException:
 for name in reversed(applied):
  if (backup/name).exists():
   target=root/name;temp=target.with_name(target.name+'.round2-restore')
   shutil.copy2(backup/name,temp);os.chown(temp,account.pw_uid,account.pw_gid);os.replace(temp,target)
 # Leave any new, unused service in place; do not delete production source automatically.
 raise
print(json.dumps({'release':payload['release'],'backup_verified':True,'files':list(payload['files']),'database_changed':False}))
'''
result = subprocess.run(['ssh', '-p', str(config['port']), *client.ssh_options(config),
                         f"{config['user']}@{config['host']}", 'python3 - ' + payload],
                        input=code, text=True, capture_output=True)
print(result.stdout)
if result.returncode:
    print(result.stderr)
    raise SystemExit(result.returncode)
folder = ROOT / '.deploy/round2-backups' / revision
folder.mkdir(parents=True, exist_ok=True, mode=0o700)
for name, item in manifest.items():
    if item['baseline_sha256'] is None:
        continue
    target = folder / name
    target.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
    client.copy(config, f'/www/backup/bondvia-deploy/round2/{revision}/backup/{name}', target, download=True)
    target.chmod(0o600)
    if hashlib.sha256(target.read_bytes()).hexdigest() != item['baseline_sha256']:
        raise ValueError('Off-server PHP backup failed verification')
print('Off-server PHP source backups checksum verified')
