/**
 * Usage Telemetry Module
 *
 * Tracks scan events and usage patterns for analytics and case studies.
 * All telemetry is opt-in and respects user privacy preferences.
 *
 * @module telemetry/usage
 */
import { createHash } from "crypto";
import { logger } from "../logger.js";
/**
 * Check if telemetry is enabled via environment variable
 */
function getEnvTelemetryEnabled() {
    const envValue = process.env.VASPERA_TELEMETRY_ENABLED;
    return envValue === "true" || envValue === "1";
}
/**
 * Default telemetry configuration (reads from environment)
 */
export const DEFAULT_TELEMETRY_CONFIG = {
    enabled: getEnvTelemetryEnabled(),
    endpoint: process.env.VASPERA_TELEMETRY_ENDPOINT || "https://api.vaspera.dev/telemetry",
    apiKey: process.env.VASPERA_TELEMETRY_API_KEY,
    includeRepoUrl: process.env.VASPERA_TELEMETRY_INCLUDE_REPO === "true",
    includeOrgName: process.env.VASPERA_TELEMETRY_INCLUDE_ORG === "true",
    includeUserEmail: process.env.VASPERA_TELEMETRY_INCLUDE_EMAIL === "true",
};
// ============================================================================
// Telemetry Client
// ============================================================================
/**
 * In-memory buffer for batching events
 */
const eventBuffer = [];
const MAX_BUFFER_SIZE = 100;
const FLUSH_INTERVAL_MS = 30000;
let flushTimer = null;
let currentConfig = { ...DEFAULT_TELEMETRY_CONFIG };
/**
 * Initialize telemetry with configuration
 */
export function initTelemetry(config) {
    currentConfig = { ...DEFAULT_TELEMETRY_CONFIG, ...config };
    if (currentConfig.enabled && !flushTimer) {
        flushTimer = setInterval(flushEvents, FLUSH_INTERVAL_MS);
    }
    logger.debug("telemetry.initialized", {
        enabled: currentConfig.enabled,
        endpoint: currentConfig.endpoint,
    });
}
/**
 * Check if telemetry is enabled
 */
export function isTelemetryEnabled() {
    return currentConfig.enabled;
}
/**
 * Hash a value for anonymization
 */
export function hashValue(value) {
    return createHash("sha256").update(value).digest("hex").slice(0, 16);
}
/**
 * Create base event fields
 */
function createBaseEvent(eventType, projectPath) {
    return {
        eventType,
        timestamp: new Date().toISOString(),
        projectHash: hashValue(projectPath),
        vasperaVersion: process.env.npm_package_version || "unknown",
        platform: process.platform,
        nodeVersion: process.version,
    };
}
/**
 * Track a telemetry event
 */
export async function trackEvent(event) {
    if (!currentConfig.enabled) {
        return;
    }
    const { projectPath, ...rest } = event;
    const baseEvent = createBaseEvent(rest.eventType, projectPath);
    const fullEvent = { ...baseEvent, ...rest };
    // Strip fields that weren't opted into
    if (!currentConfig.includeRepoUrl && "repoUrl" in fullEvent) {
        delete fullEvent.repoUrl;
    }
    if (!currentConfig.includeOrgName && "orgName" in fullEvent) {
        delete fullEvent.orgName;
    }
    if (!currentConfig.includeUserEmail && "userEmail" in fullEvent) {
        delete fullEvent.userEmail;
    }
    eventBuffer.push(fullEvent);
    logger.debug("telemetry.event_tracked", {
        eventType: event.eventType,
        bufferSize: eventBuffer.length,
    });
    // Flush if buffer is full
    if (eventBuffer.length >= MAX_BUFFER_SIZE) {
        await flushEvents();
    }
}
/**
 * Flush buffered events to the telemetry endpoint
 */
export async function flushEvents() {
    if (!currentConfig.enabled || eventBuffer.length === 0) {
        return;
    }
    const events = [...eventBuffer];
    eventBuffer.length = 0;
    try {
        const endpoint = currentConfig.endpoint || DEFAULT_TELEMETRY_CONFIG.endpoint;
        const response = await fetch(endpoint, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                ...(currentConfig.apiKey && {
                    Authorization: `Bearer ${currentConfig.apiKey}`,
                }),
            },
            body: JSON.stringify({ events }),
        });
        if (!response.ok) {
            logger.warn("telemetry.flush_failed", {
                status: response.status,
                eventCount: events.length,
            });
            // Re-add events to buffer on failure (with limit)
            eventBuffer.push(...events.slice(0, MAX_BUFFER_SIZE - eventBuffer.length));
        }
        else {
            logger.debug("telemetry.flush_success", { eventCount: events.length });
        }
    }
    catch (error) {
        logger.warn("telemetry.flush_error", {
            error: error instanceof Error ? error.message : String(error),
            eventCount: events.length,
        });
        // Re-add events to buffer on error (with limit)
        eventBuffer.push(...events.slice(0, MAX_BUFFER_SIZE - eventBuffer.length));
    }
}
/**
 * Shutdown telemetry (flush remaining events)
 */
export async function shutdownTelemetry() {
    if (flushTimer) {
        clearInterval(flushTimer);
        flushTimer = null;
    }
    await flushEvents();
    logger.debug("telemetry.shutdown");
}
// ============================================================================
// Convenience Functions
// ============================================================================
/**
 * Track certification start
 */
export async function trackCertificationStarted(projectPath, scanners, frameworks, corpusSize) {
    await trackEvent({
        eventType: "certification_started",
        projectPath,
        scanners,
        frameworks,
        corpusSize,
    });
}
/**
 * Track certification completion
 */
export async function trackCertificationCompleted(projectPath, certificationId, level, score, duration, severityCounts, totalFindings, frameworks, optionalInfo) {
    await trackEvent({
        eventType: "certification_completed",
        projectPath,
        certificationId,
        level,
        score,
        duration,
        severityCounts,
        totalFindings,
        frameworks,
        ...optionalInfo,
    });
}
/**
 * Track certification failure
 */
export async function trackCertificationFailed(projectPath, errorMessage, errorCategory, duration) {
    // Sanitize error message to avoid leaking sensitive data
    const sanitizedError = errorMessage
        .replace(/\/[^\s]+/g, "[PATH]")
        .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[EMAIL]")
        .replace(/\b[A-Za-z0-9]{32,}\b/g, "[TOKEN]")
        .slice(0, 200);
    await trackEvent({
        eventType: "certification_failed",
        projectPath,
        errorMessage: sanitizedError,
        errorCategory,
        duration,
    });
}
/**
 * Track a finding discovery
 */
export async function trackFindingDiscovered(projectPath, category, severity, scanner, ruleId) {
    await trackEvent({
        eventType: "finding_discovered",
        projectPath,
        category,
        severity,
        scanner,
        ruleId,
    });
}
/**
 * Track a scanner run
 */
export async function trackScannerRun(projectPath, scanner, duration, findingsCount, success) {
    await trackEvent({
        eventType: "scanner_run",
        projectPath,
        scanner,
        duration,
        findingsCount,
        success,
    });
}
// ============================================================================
// Exports
// ============================================================================
export const telemetry = {
    init: initTelemetry,
    isEnabled: isTelemetryEnabled,
    track: trackEvent,
    flush: flushEvents,
    shutdown: shutdownTelemetry,
    trackCertificationStarted,
    trackCertificationCompleted,
    trackCertificationFailed,
    trackFindingDiscovered,
    trackScannerRun,
};
//# sourceMappingURL=usage.js.map