/**
 * Sentry Error Tracking Integration
 *
 * Production error tracking and monitoring:
 * - Automatic error capture
 * - Performance monitoring
 * - Release tracking
 * - User context
 */
import { EventEmitter } from 'events';
import * as os from 'os';
import { logger } from '../utils/logger';
/**
 * Sentry Integration for Code Buddy
 */
export class SentryIntegration extends EventEmitter {
    config;
    initialized = false;
    breadcrumbs = [];
    maxBreadcrumbs = 100;
    transactions = new Map();
    eventQueue = [];
    flushInterval = null;
    constructor(config) {
        super();
        this.config = {
            dsn: config.dsn,
            environment: config.environment || process.env.NODE_ENV || 'development',
            release: config.release || process.env.npm_package_version || '0.0.0',
            sampleRate: config.sampleRate ?? 1.0,
            tracesSampleRate: config.tracesSampleRate ?? 0.1,
            debug: config.debug ?? false,
            tags: config.tags || {},
            user: config.user || {},
            beforeSend: config.beforeSend || ((e) => e),
        };
    }
    /**
     * Initialize Sentry integration
     */
    async init() {
        if (this.initialized)
            return;
        if (!this.config.dsn) {
            logger.warn('Sentry DSN not configured, error tracking disabled');
            return;
        }
        // Set up global error handlers
        process.on('uncaughtException', (error) => {
            this.captureException(error, { level: 'fatal' });
        });
        process.on('unhandledRejection', (reason) => {
            const error = reason instanceof Error ? reason : new Error(String(reason));
            this.captureException(error, { level: 'error' });
        });
        // Start flush interval
        this.flushInterval = setInterval(() => {
            this.flush().catch((err) => {
                logger.debug('Failed to flush Sentry events', { error: err instanceof Error ? err.message : String(err) });
            });
        }, 10000);
        this.initialized = true;
        this.emit('initialized');
        if (this.config.debug) {
            logger.info('Sentry initialized', { environment: this.config.environment });
        }
    }
    /**
     * Capture an exception
     */
    captureException(error, options) {
        // Sample rate check
        if (Math.random() > this.config.sampleRate) {
            return '';
        }
        const eventId = this.generateEventId();
        const event = {
            event_id: eventId,
            timestamp: new Date().toISOString(),
            platform: 'node',
            level: options?.level || 'error',
            exception: {
                values: [{
                        type: error.name,
                        value: error.message,
                        stacktrace: this.parseStackTrace(error.stack),
                    }],
            },
            tags: { ...this.config.tags, ...options?.tags },
            extra: options?.extra,
            user: this.config.user,
            contexts: this.getContexts(),
            release: this.config.release,
            environment: this.config.environment,
            server_name: os.hostname(),
        };
        // Apply beforeSend hook
        const processedEvent = this.config.beforeSend(event);
        if (processedEvent) {
            this.eventQueue.push(processedEvent);
            this.emit('exception', { eventId, error });
        }
        return eventId;
    }
    /**
     * Capture a message
     */
    captureMessage(message, level = 'info', options) {
        // Sample rate check
        if (Math.random() > this.config.sampleRate) {
            return '';
        }
        const eventId = this.generateEventId();
        const event = {
            event_id: eventId,
            timestamp: new Date().toISOString(),
            platform: 'node',
            level,
            message,
            tags: { ...this.config.tags, ...options?.tags },
            extra: options?.extra,
            user: this.config.user,
            contexts: this.getContexts(),
            release: this.config.release,
            environment: this.config.environment,
            server_name: os.hostname(),
        };
        const processedEvent = this.config.beforeSend(event);
        if (processedEvent) {
            this.eventQueue.push(processedEvent);
            this.emit('message', { eventId, message, level });
        }
        return eventId;
    }
    /**
     * Add a breadcrumb
     */
    addBreadcrumb(breadcrumb) {
        this.breadcrumbs.push({
            ...breadcrumb,
            timestamp: Date.now() / 1000,
        });
        // Keep only the last N breadcrumbs
        if (this.breadcrumbs.length > this.maxBreadcrumbs) {
            this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
        }
    }
    /**
     * Set user context
     */
    setUser(user) {
        this.config.user = user || {};
    }
    /**
     * Set tag
     */
    setTag(key, value) {
        this.config.tags[key] = value;
    }
    /**
     * Set extra context
     */
    setExtra(key, value) {
        // Store in tags for simplicity
        this.config.tags[`extra_${key}`] = String(value);
    }
    /**
     * Start a transaction for performance monitoring
     */
    startTransaction(name, op) {
        // Sample rate check for traces
        if (Math.random() > this.config.tracesSampleRate) {
            return '';
        }
        const transactionId = this.generateEventId();
        const transaction = {
            name,
            op,
            startTimestamp: Date.now() / 1000,
            spans: [],
        };
        this.transactions.set(transactionId, transaction);
        this.emit('transaction:start', { transactionId, name, op });
        return transactionId;
    }
    /**
     * Start a span within a transaction
     */
    startSpan(transactionId, op, description) {
        const transaction = this.transactions.get(transactionId);
        if (!transaction)
            return '';
        const spanId = this.generateSpanId();
        const span = {
            spanId,
            op,
            description,
            startTimestamp: Date.now() / 1000,
        };
        transaction.spans.push(span);
        return spanId;
    }
    /**
     * Finish a span
     */
    finishSpan(transactionId, spanId, status = 'ok') {
        const transaction = this.transactions.get(transactionId);
        if (!transaction)
            return;
        const span = transaction.spans.find(s => s.spanId === spanId);
        if (span) {
            span.endTimestamp = Date.now() / 1000;
            span.status = status;
        }
    }
    /**
     * Finish a transaction
     */
    finishTransaction(transactionId, status = 'ok') {
        const transaction = this.transactions.get(transactionId);
        if (!transaction)
            return;
        transaction.endTimestamp = Date.now() / 1000;
        transaction.status = status;
        this.emit('transaction:finish', { transactionId, transaction });
        this.transactions.delete(transactionId);
    }
    /**
     * Flush pending events to Sentry
     */
    async flush() {
        if (this.eventQueue.length === 0)
            return;
        const events = [...this.eventQueue];
        this.eventQueue = [];
        // Send events in parallel for better performance
        const results = await Promise.allSettled(events.map(event => this.sendEvent(event)));
        // Log failures if debug mode is enabled
        if (this.config.debug) {
            for (const result of results) {
                if (result.status === 'rejected') {
                    logger.error('Failed to send event to Sentry', { error: result.reason });
                }
            }
        }
    }
    /**
     * Send event to Sentry
     */
    async sendEvent(event) {
        const { projectId, publicKey, host } = this.parseDsn(this.config.dsn);
        const url = `https://${host}/api/${projectId}/store/`;
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Sentry-Auth': `Sentry sentry_version=7, sentry_client=code-buddy/1.0.0, sentry_key=${publicKey}`,
            },
            body: JSON.stringify(event),
        });
        if (!response.ok) {
            throw new Error(`Sentry API error: ${response.status}`);
        }
        if (this.config.debug) {
            logger.debug('Event sent to Sentry', { eventId: event.event_id });
        }
    }
    /**
     * Parse Sentry DSN
     */
    parseDsn(dsn) {
        const match = dsn.match(/^https?:\/\/([^@]+)@([^/]+)\/(\d+)$/);
        if (!match) {
            throw new Error('Invalid Sentry DSN format');
        }
        return {
            publicKey: match[1],
            host: match[2],
            projectId: match[3],
        };
    }
    /**
     * Parse error stack trace
     */
    parseStackTrace(stack) {
        if (!stack)
            return undefined;
        const frames = stack
            .split('\n')
            .slice(1) // Skip error message line
            .map(line => {
            const match = line.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?/);
            if (!match)
                return null;
            return {
                function: match[1] || '<anonymous>',
                filename: match[2],
                lineno: parseInt(match[3], 10),
                colno: parseInt(match[4], 10),
                in_app: !match[2].includes('node_modules'),
            };
        })
            .filter((frame) => frame !== null)
            .reverse(); // Sentry expects oldest frame first
        return { frames };
    }
    /**
     * Get system contexts
     */
    getContexts() {
        return {
            os: {
                name: os.platform(),
                version: os.release(),
            },
            runtime: {
                name: 'node',
                version: process.version,
            },
            app: {
                app_name: 'code-buddy',
                app_version: this.config.release,
            },
        };
    }
    /**
     * Generate event ID
     */
    generateEventId() {
        return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/x/g, () => Math.floor(Math.random() * 16).toString(16));
    }
    /**
     * Generate span ID
     */
    generateSpanId() {
        return 'xxxxxxxxxxxxxxxx'.replace(/x/g, () => Math.floor(Math.random() * 16).toString(16));
    }
    /**
     * Close Sentry integration
     */
    async close() {
        if (this.flushInterval) {
            clearInterval(this.flushInterval);
            this.flushInterval = null;
        }
        await this.flush();
        this.initialized = false;
        this.emit('closed');
    }
}
// Singleton instance
let sentryInstance = null;
/**
 * Initialize Sentry
 */
export function initSentry(config) {
    if (!sentryInstance) {
        sentryInstance = new SentryIntegration(config);
        sentryInstance.init().catch((err) => {
            logger.warn('Failed to initialize Sentry', { error: err instanceof Error ? err.message : String(err) });
        });
    }
    return sentryInstance;
}
/**
 * Get Sentry instance
 */
export function getSentry() {
    return sentryInstance;
}
/**
 * Capture exception helper
 */
export function captureException(error, options) {
    return sentryInstance?.captureException(error, options) || '';
}
/**
 * Capture message helper
 */
export function captureMessage(message, level, options) {
    return sentryInstance?.captureMessage(message, level, options) || '';
}
export default SentryIntegration;
//# sourceMappingURL=sentry-integration.js.map