#!/usr/bin/env python3
"""Record the real standalone service launcher and client update RPC on an isolated home.
Requires OLD_BINARY / NEW_BINARY (locally built 0.1.8 / 0.1.9), MODULES,
DEPENDENCY_PACKAGE, NODE, SERVER_PORT, SIDE, COMMIT, ARTIFACT_ROOT.
Only release version metadata is changed for these local builds.
"""
import os, pathlib, shutil, json, subprocess, time, urllib.request, signal, sys
root = pathlib.Path(os.environ['ARTIFACT_ROOT']) / (os.environ['SIDE'] + '-' + str(time.time_ns()))
root.mkdir(parents=True)
home = root / 'home'; base = home / '.t3'; versions = base / 'runtime/versions'
for version, source in [('0.1.8', 'OLD_BINARY'), ('0.1.9', 'NEW_BINARY')]:
    directory = versions / version; directory.mkdir(parents=True)
    shutil.copy2(os.environ[source], directory / 't3')
    (directory / '.install-complete').write_text(version + '\n')
    (directory / 'node_modules').symlink_to(os.environ['MODULES'])
bin_dir = home / '.local/bin'; bin_dir.mkdir(parents=True)
cli = bin_dir / 't3'; cli.symlink_to(versions / '0.1.8/t3')
state = base / 'runtime/service-state.json'
state.write_text(json.dumps({'protocol': 3, 'activeVersion': '0.1.8'}))
port = os.environ['SERVER_PORT']
env = dict(os.environ, HOME=str(home), T3CODE_HOME=str(base), T3CODE_PORT=port,
           T3CODE_HOST='127.0.0.1', T3CODE_NO_BROWSER='true', T3CODE_DEV_SEED='0',
           PATH=str(bin_dir)+':'+os.environ['PATH'])
transcript = (root / 'transcript.txt').open('w')
def out(text=''):
    print(text, flush=True); transcript.write(text+'\n'); transcript.flush()
def command(args):
    return subprocess.check_output(args, env=env, stderr=subprocess.DEVNULL, text=True).strip()
def health():
    with urllib.request.urlopen(f'http://127.0.0.1:{port}/.well-known/t3/environment', timeout=2) as response:
        return json.load(response)
def wait_ready(version):
    deadline=time.monotonic()+180
    while time.monotonic()<deadline:
        try:
            result=health()
            if result['serverVersion']==version and (version=='0.1.8' or json.loads(state.read_text()).get('update',{}).get('status')=='committed'):
                time.sleep(1); return result
        except (OSError, ValueError): pass
        time.sleep(.25)
    raise RuntimeError('Server did not become ready')
log = (root / 'server-private.log').open('w')
launcher = None
def launch():
    return subprocess.Popen([str(versions/'0.1.8/t3'), '__service-launcher'], env=env, stdout=log, stderr=log)
def stop():
    global launcher
    if launcher is not None:
        launcher.send_signal(signal.SIGTERM)
        launcher.wait(timeout=30); launcher=None
try:
    print('\033[2J\033[H\033[?25l', end='', flush=True)
    out('T3 standalone update - ' + os.environ['SIDE'].upper())
    out('Commit: '+os.environ['COMMIT'])
    out('Linux x64 | native CLI | fresh isolated home | loopback RPC')
    out('Local release builds: 0.1.8 -> 0.1.9 (version metadata only)')
    out('Background service launcher run directly; no system service replaced.')
    out()
    launcher=launch(); wait_ready('0.1.8')
    out('$ t3 --version'); out(command([str(cli),'--version']))
    out('$ readlink ~/.local/bin/t3'); out(os.readlink(cli).replace(str(home),'~'))
    out('$ GET /.well-known/t3/environment [version and update capability]')
    h=health(); out(json.dumps({'serverVersion':h['serverVersion'],'serverSelfUpdate':h['capabilities']['serverSelfUpdate']}))
    token=command([str(cli),'auth','session','issue','--base-dir',str(base),'--scope','environment:maintain','--token-only'])
    out(); out('$ Update server -> 0.1.9 [same client RPC as the app]')
    rpc_env=dict(env, ACCESS_TOKEN=token)
    rpc=subprocess.Popen([os.environ['NODE'], str(pathlib.Path(__file__).with_name('update-rpc.mjs'))], env=rpc_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    for line in rpc.stdout: out(line.rstrip())
    if rpc.wait()!=0: raise RuntimeError('Update RPC failed')
    wait_ready('0.1.9')
    out('$ service state [after reconnection]')
    st=json.loads(state.read_text()); out(json.dumps({'activeVersion':st['activeVersion'],'updateStatus':st['update']['status']}))
    out('$ GET /.well-known/t3/environment [serverVersion]'); out(health()['serverVersion'])
    out('$ t3 --version'); updated_cli=command([str(cli),'--version']);out(updated_cli)
    out('$ readlink ~/.local/bin/t3'); updated_link=os.readlink(cli);out(updated_link.replace(str(home),'~'))
    out('CLI agrees with updated server: '+str(updated_cli=='t3 v0.1.9').upper())
    out()
    out('$ ln -sfn ~/.t3/runtime/versions/0.1.8/t3 ~/.local/bin/t3')
    cli.unlink();cli.symlink_to(versions/'0.1.8/t3')
    out('$ restart background service launcher [no update request]')
    stop();launcher=launch();wait_ready('0.1.9')
    out('$ GET /.well-known/t3/environment [serverVersion]');out(health()['serverVersion'])
    out('$ t3 --version');out(command([str(cli),'--version']))
    out('$ readlink ~/.local/bin/t3');out(os.readlink(cli).replace(str(home),'~'))
    out('Intentional pin preserved: '+str(os.readlink(cli)==str(versions/'0.1.8/t3')).upper())
    (root/'result.json').write_text(json.dumps({'cliAgrees':updated_cli=='t3 v0.1.9','pinPreserved':os.readlink(cli)==str(versions/'0.1.8/t3')}))
    out();out('Run complete. No tests or fake launcher used.')
    (pathlib.Path(os.environ['ARTIFACT_ROOT'])/(os.environ['SIDE']+'-done')).write_text(str(root))
    time.sleep(4)
finally:
    stop();log.close();transcript.close()
