#!/usr/bin/env python3
"""Run the real built t3 CLI behind a namespace-scoped proxy-only network.
Usage: python3 prove-proxy.py BASE_CHECKOUT HEAD_CHECKOUT OUTPUT_DIRECTORY
No application hooks, fetch mocks, TLS interception, or host network changes.
Requires Linux unshare user/mount/network namespaces, ip, mount, and Node.
"""
import os, sys, socket, socketserver, threading, subprocess, pathlib, time, json, select, re, struct

def relay(a,b):
    try:
        while True:
            ready,_,_=select.select([a,b],[],[],60)
            if not ready: break
            for src in ready:
                data=src.recv(262144)
                if not data: return
                (b if src is a else a).sendall(data)
    except (OSError,ValueError): pass
    finally:
        a.close(); b.close()

class Server(socketserver.ThreadingMixIn,socketserver.TCPServer):
    allow_reuse_address=True
    daemon_threads=True
class UnixServer(socketserver.ThreadingMixIn,socketserver.UnixStreamServer):
    daemon_threads=True

def inner(checkout,out,sockpath,case):
    out=pathlib.Path(out)
    subprocess.run(['ip','link','set','lo','up'],check=True)
    subprocess.run(['mount','--make-rprivate','/'],check=True)
    hosts=out/'hosts'
    hosts.write_text('127.0.0.1 localhost\n::1 localhost\n127.0.0.2 api.github.com\n127.0.0.3 github.com\n')
    subprocess.run(['mount','--bind',str(hosts),'/etc/hosts'],check=True)
    start=time.monotonic()
    events=[]
    lock=threading.Lock()
    def emit(msg):
        with lock:
            line=f'[{time.monotonic()-start:07.3f}s] {msg}'
            print(line,flush=True); events.append(line)
    class Bridge(socketserver.BaseRequestHandler):
        def handle(self):
            remote=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM); remote.connect(sockpath)
            relay(self.request,remote)
    class Reset(socketserver.BaseRequestHandler):
        def handle(self):
            emit('NETWORK: direct GitHub connection blocked (TCP reset)')
            self.request.setsockopt(socket.SOL_SOCKET,socket.SO_LINGER,struct.pack('ii',1,0))
    class Direct(socketserver.BaseRequestHandler):
        def handle(self):
            host='api.github.com' if self.server.server_address[0]=='127.0.0.2' else 'github.com'
            emit('NETWORK: NO_PROXY direct '+host+':443 connection')
            remote=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM); remote.connect(sockpath)
            remote.sendall(('CONNECT '+host+':443 HTTP/1.1\r\nHost: '+host+':443\r\nX-Evidence-Route: direct\r\n\r\n').encode())
            data=b''
            while not data.endswith(b'\r\n\r\n'): data+=remote.recv(1)
            relay(self.request,remote)
    class Mirror(socketserver.BaseRequestHandler):
        def handle(self):
            data=b''
            while b'\r\n\r\n' not in data: data+=self.request.recv(4096)
            path=data.split(b' ')[1].decode()
            emit('NETWORK: loopback mirror reached DIRECT: '+path)
            location='https://github.com/pingdotgg/t3code/releases/download'+path
            self.request.sendall(('HTTP/1.1 302 Found\r\nLocation: '+location+'\r\nContent-Length: 0\r\nConnection: close\r\n\r\n').encode())
    servers=[Server(('127.0.0.1',18080),Bridge),Server(('127.0.0.2',443),Direct if case=='bypass' else Reset),Server(('127.0.0.3',443),Direct if case=='bypass' else Reset),Server(('127.0.0.1',18444),Mirror)]
    for server in servers: threading.Thread(target=server.serve_forever,daemon=True).start()
    env={k:v for k,v in os.environ.items() if k not in ('NODE_TLS_REJECT_UNAUTHORIZED','NODE_EXTRA_CA_CERTS','NODE_OPTIONS','NODE_USE_ENV_PROXY','GH_TOKEN','GITHUB_TOKEN','T3CODE_HOME','T3CODE_RELEASE_BASE_URL','T3CODE_DEV_AUTH_TOKEN') and not k.lower().endswith('_proxy')}
    env.update(HTTPS_PROXY='http://127.0.0.1:18080',NO_PROXY='unrelated.invalid',T3CODE_DEV_SEED='0',NO_COLOR='1',TAR_OPTIONS='--no-same-owner')
    args=['node','dist/bin.mjs','update','--channel','nightly','--base-dir',str(out/'home')]
    if case=='bypass': env['NO_PROXY']='github.com'
    if case=='loopback':
        args[3:5]=['0.1.9-nightly.20261111.4501']
        env['T3CODE_RELEASE_BASE_URL']='http://127.0.0.1:18444'
        env['HTTP_PROXY']='http://127.0.0.1:18080'
    emit('ENV: Linux isolated network namespace; Node '+subprocess.check_output(['node','--version'],cwd=str(pathlib.Path(checkout)/'apps/server'),text=True).strip())
    emit('ENV: HTTPS_PROXY=http://127.0.0.1:18080; HTTP_PROXY='+env.get('HTTP_PROXY','unset')+'; NO_PROXY='+env['NO_PROXY'])
    emit('ENV: TAR_OPTIONS=--no-same-owner (user-namespace archive extraction)')
    emit('ENV: NODE_USE_ENV_PROXY unset; NODE_OPTIONS unset; no --use-env-proxy; fresh isolated T3 home')
    if case=='loopback': emit('ENV: T3CODE_RELEASE_BASE_URL=http://127.0.0.1:18444 (redirects to real GitHub assets)')
    emit('COMMAND: node dist/bin.mjs update '+('0.1.9-nightly.20261111.4501' if case=='loopback' else '--channel nightly')+' --base-dir <isolated-home>')
    proc=subprocess.Popen(args,cwd=str(pathlib.Path(checkout)/'apps/server'),env=env,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
    try:
        for line in proc.stdout:
            clean=line.rstrip().replace(str(out/'home'),'<isolated-home>').replace(checkout,'<checkout>')
            clean=re.sub(r'\x1b\[[0-9;]*[A-Za-z]','',clean)
            emit('CLI: '+clean)
        code=proc.wait(timeout=300)
    finally:
        if proc.poll() is None: proc.kill();proc.wait()
    emit('EXIT: '+str(code))
    sentinels=list((out/'home').glob('runtime/versions/*/.install-complete'))
    for sentinel in sentinels:
        emit('INSTALLED: '+sentinel.read_text().strip())
        p=subprocess.run([str(sentinel.parent/'t3'),'--version'],capture_output=True,text=True,env=env,timeout=30)
        emit('DOWNLOADED EXECUTABLE --version: '+p.stdout.strip()+f' (exit {p.returncode})')
    (out/'transcript.txt').write_text('\n'.join(events)+'\n')
    (out/'result.json').write_text(json.dumps({'exit':code,'installed':[p.read_text().strip() for p in sentinels]}))
    for server in servers: server.shutdown();server.server_close()

if __name__=='__main__':
    if sys.argv[1]=='--inner': inner(*sys.argv[2:]);sys.exit()
    base,head,destination=sys.argv[1:]
    root=pathlib.Path(destination).resolve();root.mkdir(parents=True,exist_ok=False)
    sockpath=str(root/'proxy.sock')
    current={'log':None}
    class Proxy(socketserver.BaseRequestHandler):
        def handle(self):
            data=b''
            while b'\r\n\r\n' not in data:
                chunk=self.request.recv(1)
                if not chunk:return
                data+=chunk
            method,authority,_=data.split(b'\r\n')[0].decode().split(' ')
            if method!='CONNECT':return
            host,port=authority.rsplit(':',1)
            if host not in ('api.github.com','github.com','release-assets.githubusercontent.com','objects.githubusercontent.com') or port!='443':return
            with open(current['log'],'a') as log: log.write(('DIRECT bridge (NO_PROXY) ' if b'X-Evidence-Route: direct' in data else 'PROXY CONNECT ')+authority+'\n')
            try: remote=socket.create_connection((host,int(port)),timeout=30)
            except OSError:
                self.request.sendall(b'HTTP/1.1 502 Bad Gateway\r\n\r\n');return
            self.request.sendall(b'HTTP/1.1 200 Connection established\r\n\r\n')
            relay(self.request,remote)
    server=UnixServer(sockpath,Proxy)
    threading.Thread(target=server.serve_forever,daemon=True).start()
    try:
        runs=[('before',base,'primary'),('after',head,'primary'),('after-bypass',head,'bypass'),('after-loopback',head,'loopback')]
        if os.environ.get('EVIDENCE_LOOPBACK_ONLY'):runs=runs[-1:]
        for label,checkout,case in runs:
            output=root/label;output.mkdir();current['log']=str(output/'proxy.txt');pathlib.Path(current['log']).touch()
            print('\n=== '+label+' ===',flush=True)
            proc=subprocess.run(['unshare','-Urnm',sys.executable,str(pathlib.Path(__file__).resolve()),'--inner',checkout,str(output),sockpath,case],timeout=600)
            print('DRIVER EXIT '+str(proc.returncode),flush=True)
            print(pathlib.Path(current['log']).read_text(),flush=True)
    finally: server.shutdown();server.server_close()
