import http from 'node:http';
import {runWorkerTask} from './run-worker.mjs';
import {setTimeout as delay} from 'node:timers/promises';
import {context,trace,propagation,ROOT_CONTEXT,SpanKind,SpanStatusCode} from '@opentelemetry/api';
import {startTelemetry,inSpan,traceLog,allowedAttributes} from './telemetry.mjs';

export const SCENARIOS=['basic','distributed','sequential','parallel','error','async-context','worker','sampling','privacy','shutdown'];
const service=process.env.SERVICE==='pricing'?'pricing':'checkout';
const telemetry=startTelemetry('workshop-'+service);
const port=Number(process.env.PORT??(service==='pricing'?8414:8413));
if(!Number.isInteger(port)||port<0||port>65535)throw Error('Invalid port');
const upstream=process.env.PRICING_URL||'http://127.0.0.1:8414';
const parsedUpstream=new URL(upstream);
if(!['127.0.0.1','localhost','pricing'].includes(parsedUpstream.hostname)||parsedUpstream.protocol!=='http:')throw Error('Pricing URL must point to the local teaching service');
let active=0,stopping=false;
function reply(res,code,data){res.writeHead(code,{'Content-Type':'application/json; charset=utf-8','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'});res.end(JSON.stringify(data));}
async function getPrice(mode='normal',item='alpha'){
 return inSpan(telemetry.tracer,'GET /price',{kind:SpanKind.CLIENT,attributes:{'http.request.method':'GET','server.address':parsedUpstream.hostname,'workshop.item':item}},async span=>{
  const headers={};propagation.inject(context.active(),headers);
  const response=await fetch(`${upstream}/price?mode=${mode}&item=${item}`,{headers,signal:AbortSignal.timeout(3000)});
  span.setAttribute('http.response.status_code',response.status);const data=await response.json();
  if(!response.ok)throw Error('Pricing service returned '+response.status);
  return {...data.data,traceId:data.traceId,sentTraceparent:headers.traceparent};
 });
}
async function runWorker(){
 return inSpan(telemetry.tracer,'report.dispatch-worker',{},()=>{
  const carrier={};propagation.inject(context.active(),carrier);
  return runWorkerTask(new URL('./worker.mjs',import.meta.url),{carrier,rounds:2000000});
 });
}
async function checkout(scenario,span){
 return inSpan(telemetry.tracer,'checkout.business',{attributes:{'workshop.scenario':scenario}},async business=>{
  business.addEvent('validation.completed',{'workshop.items':2});
  traceLog('checkout.started',{scenario});
  if(scenario==='basic')return await inSpan(telemetry.tracer,'quote.local',{},async()=>({total:1200}));
  if(['distributed','sampling','shutdown'].includes(scenario))return await getPrice();
  if(scenario==='sequential'){const a=await getPrice('slow','alpha');const b=await getPrice('slow','beta');return {total:a.price+b.price};}
  if(scenario==='parallel'){const values=await Promise.all([getPrice('slow','alpha'),getPrice('slow','beta')]);return {total:values.reduce((sum,x)=>sum+x.price,0)};}
  if(scenario==='error')return await getPrice('fail');
  if(scenario==='async-context')return await inSpan(telemetry.tracer,'context.after-await',{},async child=>{
   await delay(25);const afterAwait=trace.getSpan(context.active())?.spanContext();
   await new Promise(resolve=>setImmediate(resolve));const afterImmediate=trace.getSpan(context.active())?.spanContext();
   traceLog('context.checked',{sameAfterAwait:afterAwait?.spanId===child.spanContext().spanId,sameAfterImmediate:afterImmediate?.spanId===child.spanContext().spanId});
   return {afterAwait:afterAwait?.traceId,afterImmediate:afterImmediate?.traceId};
  });
  if(scenario==='worker')return await runWorker();
  if(scenario==='privacy'){
   const demoInput={plan:'team',channel:'web',email:'synthetic@example.test',token:'DEMO_TOKEN_DO_NOT_RECORD'};
   business.setAttributes(allowedAttributes(demoInput));business.addEvent('profile.filtered',{'workshop.allowed_fields':2});
   return {keptFields:['plan','channel'],omittedFields:['email','token']};
  }
 });
}
const server=http.createServer(async(req,res)=>{
 if(stopping){reply(res,503,{error:'Service stopping'});return;}
 const url=new URL(req.url,'http://local');
 if(url.pathname==='/health'){reply(res,200,{ok:true,service,active});return;}
 if(req.method!=='GET'){reply(res,405,{error:'GET only'});return;}
 if(active>=16){reply(res,503,{error:'Teaching concurrency limit'});return;}
 const route=service==='pricing'?'/price':'/demo';
 const scenario=url.searchParams.get('scenario')||'basic';
 if(url.pathname!==route||(service==='checkout'&&!SCENARIOS.includes(scenario))){reply(res,404,{error:'Unknown teaching scenario'});return;}
 const mode=url.searchParams.get('mode')||'normal',item=url.searchParams.get('item')||'alpha';
 if(service==='pricing'&&(!['normal','slow','fail'].includes(mode)||!['alpha','beta'].includes(item))){reply(res,422,{error:'Invalid teaching parameters'});return;}
 active++;
 try{
  await inSpan(telemetry.tracer,'GET '+route,{kind:SpanKind.SERVER,attributes:{'http.request.method':'GET','http.route':route,'workshop.scenario':scenario}},async span=>{
   const sc=span.spanContext();res.setHeader('X-Trace-Id',sc.traceId);
   try{
    let data;
    if(service==='pricing')data=await inSpan(telemetry.tracer,'pricing.calculate',{attributes:{'workshop.item':item}},async inner=>{
     if(mode==='slow'){inner.addEvent('teaching.delay',{'duration.ms':item==='alpha'?45:80});await delay(item==='alpha'?45:80);}
     if(mode==='fail')throw Error('Synthetic price unavailable');
     return {price:item==='alpha'?1200:800,traceId:sc.traceId,receivedTraceparent:req.headers.traceparent||null};
    });else data=await checkout(scenario,span);
    span.setAttribute('http.response.status_code',200);reply(res,200,{ok:true,service,scenario,traceId:sc.traceId,sampled:Boolean(sc.traceFlags&1),data});
   }catch(error){span.setAttribute('http.response.status_code',500);span.recordException({name:error.name,message:error.message});span.setStatus({code:SpanStatusCode.ERROR,message:error.message});traceLog('request.failed',{scenario,error:error.message});reply(res,500,{ok:false,error:error.message,traceId:sc.traceId,sampled:Boolean(sc.traceFlags&1)});}
  },propagation.extract(ROOT_CONTEXT,req.headers));
 }finally{active--;}
});
server.headersTimeout=5000;server.requestTimeout=5000;server.keepAliveTimeout=1000;
server.listen(port,process.env.HOST||'127.0.0.1',()=>{const info={type:'ready',service,port:server.address().port};process.stdout.write(JSON.stringify(info)+'\n');process.send?.(info);});
let stoppingPromise;
async function stop(){if(stoppingPromise)return stoppingPromise;stopping=true;stoppingPromise=(async()=>{const started=Date.now();await new Promise(resolve=>server.close(resolve));server.closeIdleConnections();try{const flushed=await telemetry.shutdown();const info={type:'stopped',service,active,durationMs:Date.now()-started,...flushed};process.stdout.write(JSON.stringify(info)+'\n');process.send?.(info);}finally{if(process.connected)process.disconnect();}})();return stoppingPromise;}
process.on('SIGINT',()=>stop().catch(()=>{process.exitCode=1;}));process.on('SIGTERM',()=>stop().catch(()=>{process.exitCode=1;}));process.on('message',message=>{if(message?.type==='shutdown')stop().catch(()=>{process.exitCode=1;});});
