'use strict';
// Owned loopback fixture; no QScout actuator or external target.
const tls=require('tls'), fs=require('fs'), crypto=require('crypto'), cp=require('child_process'), path=require('path'), net=require('net');
const root='/workspace/artifacts/ops/qscout-client-investor-proof-20260930/lab/tls';
const key=fs.readFileSync(path.join(root,'private/continuity-key.pem'));
const cert=fs.readFileSync(path.join(root,'private/continuity-cert.pem'));
const wrongCa=fs.readFileSync(path.join(root,'private/continuity-wrong-ca.pem'));
const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
const utc=()=>new Date().toISOString(), mono=()=>Number(process.hrtime.bigint()/1000000n);
const wait=ms=>new Promise(r=>setTimeout(r,ms));
const out={started_utc:utc(),pid:process.pid,node:process.version,openssl:process.versions.openssl,script_sha256:sha(fs.readFileSync(__filename)),certificate_sha256:sha(cert),certificate_fingerprint:crypto.X509Certificate?new crypto.X509Certificate(cert).fingerprint256:null,probe_interval_ms:100,probe_timeout_ms:700,phase_target_ms:900,restart_gap_target_ms:250,phases:[],probes:[],transition_events:[],server_errors:[]};
let server=null, port=null, current='startup', sequence=0, running=true;
function startServer(name,group,listenPort){
 return new Promise((resolve,reject)=>{
  const s=tls.createServer({key,cert,minVersion:'TLSv1.3',maxVersion:'TLSv1.3',ecdhCurve:group,enableTrace:true},socket=>{
   const id=socket.once('data',buf=>{
    const m=buf.toString().match(/GET \/probe\/(\d+)/);const req=m?Number(m[1]):null;
    socket.end('HTTP/1.1 200 OK\r\nContent-Length: 12\r\nConnection: close\r\n\r\nowned-lab-ok');
    out.transition_events.push({type:'server_response',phase:name,request_id:req,utc:utc(),mono_ms:mono()});
   });
  });
  s.on('tlsClientError',e=>out.server_errors.push({phase:name,utc:utc(),code:e.code||null,message:e.message}));
  s.once('error',reject);
  s.listen(listenPort,'127.0.0.1',()=>{s.removeListener('error',reject);server=s;port=s.address().port;current=name;const event={type:'listen',phase:name,pid:process.pid,port,utc:utc(),mono_ms:mono(),group};out.transition_events.push(event);out.phases.push({name,group,listen:event});resolve();});
 });
}
function probe(kind,group,ca){
 const id=++sequence, begin=mono();
 const rec={id,kind,configured_group:group,phase_at_start:current,started_utc:utc(),started_mono_ms:begin,port,success:false,authorization:null,authorization_error:null};out.probes.push(rec);
 return new Promise(resolve=>{
  let socket, done=false, raw='';
  function finish(){if(done)return;done=true;rec.ended_utc=utc();rec.ended_mono_ms=mono();rec.duration_ms=rec.ended_mono_ms-begin;rec.response_status=raw.match(/^HTTP\/1\.1 (\d+)/)?.[1]||null;rec.response_body=raw.includes('owned-lab-ok')?'owned-lab-ok':null;rec.success=rec.authorization===true&&rec.response_status==='200'&&rec.response_body==='owned-lab-ok';resolve(rec);}
  try{socket=tls.connect({host:'127.0.0.1',port,servername:'localhost',ca,rejectUnauthorized:true,checkServerIdentity:tls.checkServerIdentity,minVersion:'TLSv1.3',maxVersion:'TLSv1.3',ecdhCurve:group,timeout:700},()=>{
   rec.authorization=socket.authorized;rec.authorization_error=socket.authorizationError||null;rec.protocol=socket.getProtocol();rec.cipher=socket.getCipher()?.name||null;rec.peer_fingerprint=socket.getPeerCertificate()?.fingerprint256||null;rec.local_port=socket.localPort;rec.remote_port=socket.remotePort;
   socket.write(`GET /probe/${id} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n`);
  });}catch(e){rec.error={code:e.code||null,message:e.message};finish();return;}
  socket.on('data',b=>raw+=b.toString());socket.on('error',e=>{rec.error={code:e.code||null,message:e.message};finish();});socket.on('timeout',()=>{rec.error={code:'TIMEOUT',message:'700ms request deadline'};socket.destroy();finish();});socket.on('close',finish);
 });
}
async function stopServer(name){const s=server;server=null;current='transition_'+name;const begin={type:'close_start',phase:name,port,utc:utc(),mono_ms:mono()};out.transition_events.push(begin);await new Promise((resolve,reject)=>s.close(e=>e?reject(e):resolve()));out.transition_events.push({type:'close_done',phase:name,port,utc:utc(),mono_ms:mono(),listening:s.listening});}
async function run(){
 await startServer('classical','X25519',0);
 const firstPort=port;
 const timer=setInterval(()=>{void probe('compatible','X25519:X25519MLKEM768',cert);},100);
 try{
  await wait(900);await stopServer('classical');await wait(250);await startServer('hybrid','X25519MLKEM768',firstPort);
  await probe('classical_only_negative','X25519',cert);
  await probe('wrong_ca_negative','X25519MLKEM768',wrongCa);
  await wait(900);await stopServer('hybrid');await wait(250);await startServer('rollback','X25519',firstPort);
  await probe('rollback_classical','X25519',cert);
  await wait(900);
 }finally{clearInterval(timer);await wait(850);if(server)await stopServer('rollback');}
 out.same_port=out.phases.every(p=>p.listen.port===firstPort);
 out.finished_utc=utc();
 out.port_released=await new Promise(resolve=>{const t=net.connect({host:'127.0.0.1',port:firstPort,timeout:500});t.once('connect',()=>{t.destroy();resolve(false)});t.once('error',e=>resolve(e.code==='ECONNREFUSED'));t.once('timeout',()=>{t.destroy();resolve(false)});});
 out.summary={compatible_started:out.probes.filter(p=>p.kind==='compatible').length,compatible_success:out.probes.filter(p=>p.kind==='compatible'&&p.success).length,compatible_failure:out.probes.filter(p=>p.kind==='compatible'&&!p.success).length,classical_only_hybrid_no_application:out.probes.find(p=>p.kind==='classical_only_negative')?.success===false,wrong_ca_no_application:out.probes.find(p=>p.kind==='wrong_ca_negative')?.success===false,rollback_classical_application:out.probes.find(p=>p.kind==='rollback_classical')?.success===true};
 fs.writeFileSync(process.env.CONTINUITY_OUT||path.join(root,'raw/continuity.json'),JSON.stringify(out,null,2)+'\n');
 console.log(JSON.stringify({port:firstPort,same_port:out.same_port,port_released:out.port_released,summary:out.summary}));
 if(!out.same_port||!out.port_released||!out.summary.classical_only_hybrid_no_application||!out.summary.wrong_ca_no_application||!out.summary.rollback_classical_application)process.exitCode=1;
}
run().catch(e=>{out.fatal={code:e.code||null,message:e.message,stack:e.stack};out.finished_utc=utc();fs.writeFileSync(process.env.CONTINUITY_OUT||path.join(root,'raw/continuity.json'),JSON.stringify(out,null,2)+'\n');console.error(e.stack);process.exitCode=1;});
