import { context, metrics, trace, } from "@opentelemetry/api";
import { BasicTracerProvider, BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
import { logger } from "../utils/logger.js";
export class TelemetryService {
    static instance;
    tracerProvider;
    enabled = false;
    initialized = false;
    usingExternalTracerProvider = false;
    meter;
    tracer;
    // Optional Metrics (only created when enabled)
    aiRequestCounter;
    aiRequestDuration;
    aiTokensUsed;
    aiProviderErrors;
    aiCostUsd;
    mcpToolCalls;
    connectionCounter;
    responseTimeHistogram;
    // Runtime metrics tracking
    activeConnectionCount = 0;
    errorCount = 0;
    requestCount = 0;
    totalResponseTime = 0;
    responseTimeCount = 0;
    constructor() {
        // Check if telemetry is enabled
        this.enabled = this.isTelemetryEnabled();
        if (this.enabled) {
            this.initializeTelemetry();
        }
        else {
            logger.debug("[Telemetry] Disabled - set NEUROLINK_TELEMETRY_ENABLED=true or configure OTEL_EXPORTER_OTLP_ENDPOINT to enable");
        }
    }
    static getInstance() {
        if (!TelemetryService.instance) {
            TelemetryService.instance = new TelemetryService();
        }
        return TelemetryService.instance;
    }
    isTelemetryEnabled() {
        return (this.hasExternalTracerProvider() ||
            process.env.NEUROLINK_TELEMETRY_ENABLED === "true" ||
            process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined);
    }
    hasExternalTracerProvider() {
        try {
            const provider = trace.getTracerProvider();
            if (!provider) {
                return false;
            }
            const providerName = provider.constructor?.name || "";
            if (providerName &&
                providerName !== "ProxyTracerProvider" &&
                providerName !== "NoopTracerProvider") {
                return true;
            }
            const delegate = typeof provider.getDelegate === "function"
                ? provider.getDelegate()
                : provider._delegate;
            const delegateName = delegate?.constructor?.name || "";
            return Boolean(delegateName && delegateName !== "NoopTracerProvider");
        }
        catch (error) {
            logger.warn("[Telemetry] Failed checking for external TracerProvider", {
                error: error instanceof Error ? error.message : String(error),
            });
            return false;
        }
    }
    adoptExternalTracerProvider(reason) {
        this.usingExternalTracerProvider = true;
        this.tracerProvider = undefined;
        this.meter = metrics.getMeter("neurolink-ai");
        this.tracer = trace.getTracer("neurolink-ai");
        this.initializeMetrics();
        logger.debug("[Telemetry] Reusing externally managed TracerProvider", {
            reason,
            endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
        });
    }
    initializeTelemetry() {
        try {
            if (this.hasExternalTracerProvider()) {
                this.adoptExternalTracerProvider("global tracer provider already registered");
                return;
            }
            const resource = resourceFromAttributes({
                [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
                [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
            });
            const exporter = new OTLPTraceExporter({
                url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
                    ? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`
                    : undefined,
            });
            this.tracerProvider = new BasicTracerProvider({
                resource,
                spanProcessors: [new BatchSpanProcessor(exporter)],
            });
            this.meter = metrics.getMeter("neurolink-ai");
            this.tracer = this.tracerProvider.getTracer("neurolink-ai");
            this.initializeMetrics();
            logger.debug("[Telemetry] Initialized local telemetry exporter", {
                endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
                globalTracerProviderOwnedBy: "observability/instrumentation",
            });
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            const isDuplicateRegistration = errorMessage.includes("duplicate registration") ||
                errorMessage.includes("already registered") ||
                errorMessage.includes("already set");
            if (isDuplicateRegistration && this.hasExternalTracerProvider()) {
                this.adoptExternalTracerProvider("duplicate global tracer registration detected");
                return;
            }
            logger.error("[Telemetry] Failed to initialize:", error);
            this.enabled = false;
        }
    }
    initializeMetrics() {
        if (!this.enabled || !this.meter) {
            return;
        }
        this.aiRequestCounter = this.meter.createCounter("ai_requests_total", {
            description: "Total number of AI requests",
        });
        this.aiRequestDuration = this.meter.createHistogram("ai_request_duration_ms", {
            description: "AI request duration in milliseconds",
        });
        this.aiTokensUsed = this.meter.createCounter("ai_tokens_used_total", {
            description: "Total number of AI tokens used",
        });
        this.aiCostUsd = this.meter.createCounter("ai_cost_usd_total", {
            description: "Total accumulated AI cost in USD",
        });
        this.aiProviderErrors = this.meter.createCounter("ai_provider_errors_total", {
            description: "Total number of AI provider errors",
        });
        this.mcpToolCalls = this.meter.createCounter("mcp_tool_calls_total", {
            description: "Total number of MCP tool calls",
        });
        this.connectionCounter = this.meter.createCounter("connections_total", {
            description: "Total number of connections",
        });
        this.responseTimeHistogram = this.meter.createHistogram("response_time_ms", {
            description: "Response time in milliseconds",
        });
    }
    async initialize() {
        if (!this.enabled) {
            return;
        }
        if (this.usingExternalTracerProvider) {
            this.initialized = true;
            logger.debug("[Telemetry] External TracerProvider already initialized by host");
            return;
        }
        if (!this.tracerProvider) {
            this.initialized = true;
            logger.debug("[Telemetry] Tracer provider already prepared during constructor");
            return;
        }
        try {
            // Register AsyncLocalStorage context manager for proper parent-child
            // span relationships across async boundaries (required for startActiveSpan)
            try {
                const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks");
                context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
            }
            catch {
                // context-async-hooks not installed — context propagation
                // will use the default (noop) manager
            }
            this.initialized = true;
            logger.debug("[Telemetry] Tracer provider started successfully");
        }
        catch (error) {
            logger.error("[Telemetry] Failed to start:", error);
            this.enabled = false;
            this.initialized = false;
        }
    }
    // AI Operation Tracing (NO-OP when disabled)
    /**
     * @deprecated Vercel AI SDK's experimental_telemetry creates ai.generateText/ai.streamText
     * spans automatically via OpenTelemetry. Using this method would create duplicate spans.
     * Kept for potential future use with non-Vercel providers (e.g., Amazon Bedrock).
     * See: TelemetryHandler.getTelemetryConfig() for the active telemetry path.
     */
    async traceAIRequest(provider, operation, operationType = "generate_text") {
        if (!this.enabled || !this.tracer) {
            return await operation();
        }
        const span = this.tracer.startSpan(`ai.${provider}.${operationType}`, {
            attributes: {
                "ai.provider": provider,
                "ai.operation": operationType,
            },
        });
        try {
            const result = await operation();
            span.setStatus({ code: 1 }); // OK
            return result;
        }
        catch (error) {
            span.setStatus({
                code: 2,
                message: error instanceof Error ? error.message : "Unknown error",
            }); // ERROR
            span.recordException(error);
            throw error;
        }
        finally {
            span.end();
        }
    }
    // Metrics Recording (NO-OP when disabled)
    recordAIRequest(provider, model, tokens, duration, cost) {
        // Track runtime metrics
        this.requestCount++;
        this.totalResponseTime += duration;
        this.responseTimeCount++;
        if (!this.enabled || !this.aiRequestCounter) {
            return;
        }
        const labels = { provider, model };
        this.aiRequestCounter.add(1, labels);
        this.aiRequestDuration?.record(duration, labels);
        this.aiTokensUsed?.add(tokens, labels);
        if (cost !== undefined && Number.isFinite(cost) && cost > 0) {
            this.aiCostUsd?.add(cost, labels);
        }
    }
    recordAIError(provider, error) {
        // Track runtime metrics
        this.errorCount++;
        if (!this.enabled || !this.aiProviderErrors) {
            return;
        }
        this.aiProviderErrors.add(1, {
            provider,
            error: error.name,
            message: error.message.substring(0, 100), // Limit message length
        });
    }
    recordMCPToolCall(toolName, duration, success) {
        if (!this.enabled || !this.mcpToolCalls) {
            return;
        }
        this.mcpToolCalls.add(1, {
            tool: toolName,
            success: success.toString(),
            duration_bucket: this.getDurationBucket(duration),
        });
    }
    recordConnection(type) {
        // Track runtime metrics
        this.activeConnectionCount++;
        if (!this.enabled || !this.connectionCounter) {
            return;
        }
        this.connectionCounter.add(1, { connection_type: type });
    }
    recordConnectionClosed(type) {
        // Track runtime metrics
        this.activeConnectionCount = Math.max(0, this.activeConnectionCount - 1);
        if (!this.enabled || !this.connectionCounter) {
            return;
        }
        // Optionally record disconnection metrics if needed
        this.connectionCounter.add(-1, {
            connection_type: type,
            event: "disconnect",
        });
    }
    recordResponseTime(endpoint, method, duration) {
        // Track runtime metrics
        this.totalResponseTime += duration;
        this.responseTimeCount++;
        if (!this.enabled || !this.responseTimeHistogram) {
            return;
        }
        this.responseTimeHistogram.record(duration, {
            endpoint,
            method,
            status_bucket: this.getStatusBucket(duration),
        });
    }
    // Custom Metrics
    recordCustomMetric(name, value, labels) {
        if (!this.enabled || !this.meter) {
            return;
        }
        const counter = this.meter.createCounter(`custom_${name}`, {
            description: `Custom metric: ${name}`,
        });
        counter.add(value, labels || {});
    }
    recordCustomHistogram(name, value, labels) {
        if (!this.enabled || !this.meter) {
            return;
        }
        const histogram = this.meter.createHistogram(`custom_${name}_histogram`, {
            description: `Custom histogram: ${name}`,
        });
        histogram.record(value, labels || {});
    }
    // Health Checks
    async getHealthMetrics() {
        const memoryUsage = process.memoryUsage();
        // Calculate error rate as percentage of errors vs total requests
        const errorRate = this.requestCount > 0 ? (this.errorCount / this.requestCount) * 100 : 0;
        // Calculate average response time
        const averageResponseTime = this.responseTimeCount > 0
            ? this.totalResponseTime / this.responseTimeCount
            : 0;
        return {
            timestamp: Date.now(),
            memoryUsage,
            uptime: process.uptime(),
            activeConnections: this.activeConnectionCount,
            errorRate: Math.round(errorRate * 100) / 100, // Round to 2 decimal places
            averageResponseTime: Math.round(averageResponseTime * 100) / 100, // Round to 2 decimal places
        };
    }
    // Telemetry Status
    isEnabled() {
        return this.enabled;
    }
    getStatus() {
        return {
            enabled: this.enabled,
            initialized: this.initialized,
            endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
            service: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
            version: process.env.OTEL_SERVICE_VERSION || "3.0.1",
        };
    }
    // Helper methods
    getDurationBucket(duration) {
        if (duration < 100) {
            return "fast";
        }
        if (duration < 500) {
            return "medium";
        }
        if (duration < 1000) {
            return "slow";
        }
        return "very_slow";
    }
    getStatusBucket(duration) {
        if (duration < 200) {
            return "excellent";
        }
        if (duration < 500) {
            return "good";
        }
        if (duration < 1000) {
            return "acceptable";
        }
        return "poor";
    }
    // Cleanup
    async shutdown() {
        if (this.enabled &&
            this.tracerProvider &&
            !this.usingExternalTracerProvider) {
            try {
                await this.tracerProvider.shutdown();
                this.initialized = false;
                logger.debug("[Telemetry] Tracer provider shutdown completed");
            }
            catch (error) {
                logger.error("[Telemetry] Error during shutdown:", error);
            }
        }
    }
}
//# sourceMappingURL=telemetryService.js.map