import { Env } from '../config/env.js';
import { Logger } from '../config/logger.js';
import { RequestContext } from '../http/RequestContext.js';
import * as crypto from '../node-singletons/crypto.js';
/**
 * Trace logger for structured logging
 */
export const TraceLogger = Object.freeze({
    /**
     * Create a new trace logger instance
     */
    create(traceId, serviceName) {
        return {
            info(message, data) {
                Logger.info(`[${traceId}] ${serviceName} INFO: ${message}`, data ?? '');
            },
            warn(message, data) {
                Logger.warn(`[${traceId}] ${serviceName} WARN: ${message}`, data ?? '');
            },
            error(message, data) {
                Logger.error(`[${traceId}] ${serviceName} ERROR: ${message}`, data ?? '');
            },
            debug(message, data) {
                if (Env.get('DEBUG') !== undefined && Env.get('DEBUG') !== '') {
                    Logger.debug(`[${traceId}] ${serviceName} DEBUG: ${message}`, data ?? '');
                }
            },
        };
    },
});
/**
 * Generate unique trace ID
 */
function generateTraceId() {
    return `${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;
}
function shouldSampleRequest(samplingRate) {
    if (samplingRate >= 1)
        return true;
    if (samplingRate <= 0)
        return false;
    const scale = 1_000_000;
    const value = crypto.randomInt(0, scale) / scale;
    return value <= samplingRate;
}
function createTracingMiddleware(serviceName, enabled, samplingRate) {
    return async (req, res, next) => {
        if (enabled === false || shouldSampleRequest(samplingRate) === false) {
            return next();
        }
        // Check for existing trace ID (from parent service)
        const traceIdHeader = req.getHeader('x-trace-id');
        const traceId = (typeof traceIdHeader === 'string' ? traceIdHeader : '') || generateTraceId();
        const parentServiceIdHeader = req.getHeader('x-parent-service-id');
        const parentServiceId = typeof parentServiceIdHeader === 'string' ? parentServiceIdHeader : undefined;
        const depthHeader = req.getHeader('x-trace-depth');
        const depth = Number.parseInt((typeof depthHeader === 'string' ? depthHeader : '') || '0');
        // Store trace context in request
        const traceContext = {
            traceId,
            parentServiceId,
            depth,
            startTime: Date.now(),
            serviceName,
        };
        // Keep the generic RequestContext traceId in sync for shared logging/observability.
        RequestContext.setTraceId(req, traceId);
        req.context = Object.keys(req.context).length > 0 ? req.context : {};
        req.context['trace'] = traceContext;
        req.context['traceLogger'] = TraceLogger.create(traceId, serviceName);
        // Attach trace headers to response
        res.setHeader('x-trace-id', traceId);
        res.setHeader('x-trace-service', serviceName);
        res.setHeader('x-trace-depth', depth.toString());
        // Log request start
        const method = req.getMethod();
        const path = req.getPath();
        Logger.info(`[TRACE ${traceId}] ${serviceName} ${method} ${path} (depth: ${depth}) ` +
            (parentServiceId === undefined ? '' : `(from: ${parentServiceId})`));
        // Track response timing
        const startTime = Date.now();
        const originalJson = res.json.bind(res);
        res.json = function (data) {
            const duration = Date.now() - startTime;
            Logger.info(`[TRACE ${traceId}] ${serviceName} ${method} ${path} ${res.getStatus()} (${duration}ms)`);
            return originalJson(data);
        };
        await next();
    };
}
function createInjectHeaders(serviceName) {
    return (headers = {}, traceId) => {
        const depthHeader = headers['x-trace-depth'];
        const newDepth = Number.parseInt(depthHeader ?? '0') + 1;
        return {
            ...headers,
            'x-trace-id': traceId ?? crypto.randomBytes(8).toString('hex'),
            'x-parent-service-id': serviceName,
            'x-trace-depth': newDepth.toString(),
        };
    };
}
/**
 * Request Tracing Middleware
 * Enables request tracking across microservices for debugging and observability
 */
export function RequestTracingMiddlewareFactory() {
    return {
        /**
         * Middleware to add request tracing
         */
        middleware(serviceName, enabled = true, samplingRate = 1) {
            return createTracingMiddleware(serviceName, enabled, samplingRate);
        },
        /**
         * Middleware to inject trace headers into outgoing service calls
         */
        injectHeaders(serviceName, _targetServiceName) {
            return createInjectHeaders(serviceName);
        },
    };
}
export const RequestTracingMiddleware = RequestTracingMiddlewareFactory();
export const middleware = (serviceName, enabled = true, samplingRate = 1) => RequestTracingMiddleware.middleware(serviceName, enabled, samplingRate);
export default RequestTracingMiddleware;
