import {appendFileSync,mkdirSync} from 'node:fs';
import {resolve} from 'node:path';
import {threadId} from 'node:worker_threads';
import {NodeSDK} from '@opentelemetry/sdk-node';
import {BatchSpanProcessor,ParentBasedSampler,TraceIdRatioBasedSampler} from '@opentelemetry/sdk-trace';
import {resourceFromAttributes,defaultResource} from '@opentelemetry/resources';
import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-http';
import {context,trace,SpanStatusCode} from '@opentelemetry/api';

export function serializeSpan(span){
 const sc=span.spanContext();
 return {traceId:sc.traceId,spanId:sc.spanId,parentSpanId:span.parentSpanContext?.spanId||null,traceFlags:sc.traceFlags,name:span.name,kind:span.kind,startTime:span.startTime,endTime:span.endTime,duration:span.duration,attributes:span.attributes,status:span.status,events:span.events,links:span.links,resource:span.resource.attributes,scope:span.instrumentationScope};
}
export class JsonlSpanExporter{
 constructor(file){this.file=file;this.exported=0;}
 export(spans,callback){try{appendFileSync(this.file,spans.map(s=>JSON.stringify(serializeSpan(s))).join('\n')+'\n','utf8');this.exported+=spans.length;callback({code:0});}catch(error){callback({code:1,error});}}
 async shutdown(){}
}
export function startTelemetry(service){
 const rawDir=resolve(process.env.RAW_DIR||'records/current');mkdirSync(rawDir,{recursive:true});
 const file=resolve(rawDir,`${service}-${process.pid}-${threadId}.jsonl`);
 const fileExporter=new JsonlSpanExporter(file);
 const batchOptions={maxQueueSize:512,maxExportBatchSize:64,scheduledDelayMillis:5000,exportTimeoutMillis:3000};
 const processors=[new BatchSpanProcessor({exporter:fileExporter,...batchOptions})];
 const endpoint=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
 if(endpoint)processors.push(new BatchSpanProcessor({exporter:new OTLPTraceExporter({url:endpoint,timeoutMillis:2500}),...batchOptions}));
 const ratio=Number(process.env.TRACE_RATIO??1);if(!Number.isFinite(ratio)||ratio<0||ratio>1)throw Error('TRACE_RATIO must be0..1');
 const sdk=new NodeSDK({resource:defaultResource().merge(resourceFromAttributes({'service.name':service,'service.version':'1.0.0','deployment.environment.name':'workshop'})),autoDetectResources:false,resourceDetectors:[],spanProcessors:processors,sampler:new ParentBasedSampler({root:new TraceIdRatioBasedSampler(ratio)}),logRecordProcessors:[],metricReaders:[],instrumentations:[],spanLimits:{attributeCountLimit:32,eventCountLimit:16,attributeValueLengthLimit:160}});
 sdk.start();
 const tracer=trace.getTracer('aen-otel-workshop','1.0.0');
 let closed=false;
 return{sdk,tracer,file,async shutdown(){if(closed)return;closed=true;await sdk.shutdown();return{service,exported:fileExporter.exported,file};}};
}
export async function inSpan(tracer,name,options,fn,parent=context.active()){
 return tracer.startActiveSpan(name,options,parent,async span=>{try{return await fn(span);}catch(error){span.recordException({name:error.name||'Error',message:error.message});span.setStatus({code:SpanStatusCode.ERROR,message:error.message});throw error;}finally{span.end();}});
}
export function traceLog(event,fields={}){
 const sc=trace.getSpan(context.active())?.spanContext();
 // Only fixed event names and non-sensitive business values enter these logs.
 const item={type:'application-log',event,traceId:sc?.traceId||null,spanId:sc?.spanId||null,sampled:sc?Boolean(sc.traceFlags&1):false,...fields};
 process.stdout.write(JSON.stringify(item)+'\n');return item;
}
export function allowedAttributes(input){
 const out={};if(['basic','team'].includes(input.plan))out['workshop.plan']=input.plan;if(['web','mobile'].includes(input.channel))out['workshop.channel']=input.channel;return out;
}
