/**
 * SIEM Event Enrichment
 *
 * Provides event enrichment capabilities:
 * - Geo-location data from IP addresses
 * - Threat intelligence context
 * - User context (role, tenant)
 * - Field name normalization
 *
 * @packageDocumentation
 * @module security/siem/enrichment
 */
import { createLogger } from '../../common/logger.js';
const logger = createLogger({ component: 'siem-enrichment' });
/**
 * Default geo enrichment provider using external service
 */
export class DefaultGeoProvider {
    serviceUrl;
    cache = new Map();
    cacheTtlMs;
    constructor(config) {
        this.serviceUrl = config.serviceUrl;
        this.cacheTtlMs = (config.cacheTtlSeconds ?? 3600) * 1000;
    }
    async lookup(ip) {
        // Check cache
        const cached = this.cache.get(ip);
        if (cached && cached.expiresAt > Date.now()) {
            return cached.value;
        }
        // Skip private IPs
        if (this.isPrivateIp(ip)) {
            return null;
        }
        try {
            if (!this.serviceUrl) {
                return null;
            }
            const response = await fetch(`${this.serviceUrl}/${ip}`);
            if (!response.ok) {
                return null;
            }
            const data = await response.json();
            const geo = {
                country: data.country,
                countryCode: data.country_code,
                city: data.city,
                region: data.region,
                latitude: data.latitude,
                longitude: data.longitude,
                timezone: data.timezone,
            };
            // Cache result
            this.cache.set(ip, {
                value: geo,
                expiresAt: Date.now() + this.cacheTtlMs,
            });
            return geo;
        }
        catch (error) {
            logger.warn({ ip, error: error.message }, 'Geo lookup failed');
            return null;
        }
    }
    isPrivateIp(ip) {
        // Check for private IP ranges
        const parts = ip.split('.');
        if (parts.length !== 4) {
            return false;
        }
        const first = parseInt(parts[0], 10);
        const second = parseInt(parts[1], 10);
        // 10.0.0.0/8
        if (first === 10) {
            return true;
        }
        // 172.16.0.0/12
        if (first === 172 && second >= 16 && second <= 31) {
            return true;
        }
        // 192.168.0.0/16
        if (first === 192 && second === 168) {
            return true;
        }
        // 127.0.0.0/8 (localhost)
        if (first === 127) {
            return true;
        }
        return false;
    }
}
/**
 * Default threat intelligence provider
 */
export class DefaultThreatProvider {
    serviceUrl;
    apiKey;
    cache = new Map();
    cacheTtlMs;
    constructor(config) {
        this.serviceUrl = config.serviceUrl;
        this.apiKey = config.apiKey;
        this.cacheTtlMs = (config.cacheTtlSeconds ?? 3600) * 1000;
    }
    async lookup(indicator, type) {
        const cacheKey = `${type}:${indicator}`;
        // Check cache
        const cached = this.cache.get(cacheKey);
        if (cached && cached.expiresAt > Date.now()) {
            return cached.value;
        }
        try {
            if (!this.serviceUrl) {
                return null;
            }
            const headers = {
                'Content-Type': 'application/json',
            };
            if (this.apiKey) {
                headers['Authorization'] = `Bearer ${this.apiKey}`;
            }
            const response = await fetch(`${this.serviceUrl}/lookup?type=${type}&value=${encodeURIComponent(indicator)}`, { headers });
            if (!response.ok) {
                return null;
            }
            const data = await response.json();
            if (!data.found) {
                // Cache negative result
                this.cache.set(cacheKey, {
                    value: null,
                    expiresAt: Date.now() + this.cacheTtlMs,
                });
                return null;
            }
            const threat = {
                indicator,
                threatType: data.threat_type,
                confidence: data.confidence,
                source: data.source,
                firstSeen: data.first_seen ? new Date(data.first_seen) : undefined,
                lastSeen: data.last_seen ? new Date(data.last_seen) : undefined,
                malwareFamilies: data.malware_families,
                threatActors: data.threat_actors,
                mitreAttack: data.mitre_attack,
            };
            // Cache result
            this.cache.set(cacheKey, {
                value: threat,
                expiresAt: Date.now() + this.cacheTtlMs,
            });
            return threat;
        }
        catch (error) {
            logger.warn({ indicator, type, error: error.message }, 'Threat lookup failed');
            return null;
        }
    }
}
/**
 * Default user enrichment provider
 */
export class DefaultUserProvider {
    serviceUrl;
    includeRoles;
    includeGroups;
    includeTenant;
    cache = new Map();
    cacheTtlMs;
    constructor(config) {
        this.serviceUrl = config.serviceUrl;
        this.includeRoles = config.includeRoles ?? true;
        this.includeGroups = config.includeGroups ?? true;
        this.includeTenant = config.includeTenant ?? true;
        this.cacheTtlMs = (config.cacheTtlSeconds ?? 300) * 1000;
    }
    async lookup(userId) {
        // Check cache
        const cached = this.cache.get(userId);
        if (cached && cached.expiresAt > Date.now()) {
            return cached.value;
        }
        try {
            if (!this.serviceUrl) {
                return null;
            }
            const params = new URLSearchParams();
            if (this.includeRoles)
                params.set('includeRoles', 'true');
            if (this.includeGroups)
                params.set('includeGroups', 'true');
            if (this.includeTenant)
                params.set('includeTenant', 'true');
            const response = await fetch(`${this.serviceUrl}/users/${userId}?${params.toString()}`);
            if (!response.ok) {
                return null;
            }
            const data = await response.json();
            const user = {
                userId: data.id,
                username: data.username,
                email: data.email,
                roles: data.roles,
                groups: data.groups,
                tenantId: data.tenant_id,
                tenantName: data.tenant_name,
                department: data.department,
                privileged: data.privileged,
            };
            // Cache result
            this.cache.set(userId, {
                value: user,
                expiresAt: Date.now() + this.cacheTtlMs,
            });
            return user;
        }
        catch (error) {
            logger.warn({ userId, error: error.message }, 'User lookup failed');
            return null;
        }
    }
}
// =============================================================================
// Event Enricher
// =============================================================================
/**
 * Event enricher that combines multiple enrichment sources
 */
export class EventEnricher {
    config;
    geoProvider;
    threatProvider;
    userProvider;
    fieldNormalization;
    constructor(config, options) {
        this.config = config;
        this.fieldNormalization = config.fieldNormalization ?? {};
        // Initialize providers
        if (config.geo?.enabled) {
            this.geoProvider = options?.geoProvider ?? new DefaultGeoProvider(config.geo);
        }
        if (config.threat?.enabled) {
            this.threatProvider =
                options?.threatProvider ?? new DefaultThreatProvider(config.threat);
        }
        if (config.user?.enabled) {
            this.userProvider = options?.userProvider ?? new DefaultUserProvider(config.user);
        }
    }
    /**
     * Enrich a security event
     */
    async enrich(event) {
        const startTime = Date.now();
        const enrichedFields = [];
        const errors = [];
        let enrichedEvent = { ...event };
        // Geo enrichment
        if (this.geoProvider && !event.geo) {
            const ipFields = this.config.geo?.ipFields ?? ['sourceIp', 'destinationIp'];
            for (const field of ipFields) {
                const ip = this.getFieldValue(event, field);
                if (ip && typeof ip === 'string') {
                    try {
                        const geo = await this.geoProvider.lookup(ip);
                        if (geo) {
                            enrichedEvent = { ...enrichedEvent, geo };
                            enrichedFields.push('geo');
                            break; // Use first successful lookup
                        }
                    }
                    catch (error) {
                        errors.push({
                            field: 'geo',
                            error: error.message,
                        });
                    }
                }
            }
        }
        // Threat enrichment
        if (this.threatProvider && !event.threat) {
            const indicatorFields = this.config.threat?.indicatorFields ?? [
                'sourceIp',
                'destinationIp',
                'fileHash',
            ];
            for (const field of indicatorFields) {
                const indicator = this.getFieldValue(event, field);
                if (indicator && typeof indicator === 'string') {
                    try {
                        const indicatorType = this.getIndicatorType(field);
                        const threat = await this.threatProvider.lookup(indicator, indicatorType);
                        if (threat) {
                            enrichedEvent = { ...enrichedEvent, threat };
                            enrichedFields.push('threat');
                            break; // Use first successful lookup
                        }
                    }
                    catch (error) {
                        errors.push({
                            field: 'threat',
                            error: error.message,
                        });
                    }
                }
            }
        }
        // User enrichment
        if (this.userProvider && event.user?.userId) {
            try {
                const userContext = await this.userProvider.lookup(event.user.userId);
                if (userContext) {
                    enrichedEvent = {
                        ...enrichedEvent,
                        user: {
                            ...enrichedEvent.user,
                            ...userContext,
                        },
                    };
                    enrichedFields.push('user');
                }
            }
            catch (error) {
                errors.push({
                    field: 'user',
                    error: error.message,
                });
            }
        }
        // Field normalization
        if (Object.keys(this.fieldNormalization).length > 0) {
            enrichedEvent = this.normalizeFields(enrichedEvent);
            if (enrichedEvent !== event) {
                enrichedFields.push('fields');
            }
        }
        return {
            event: enrichedEvent,
            enrichedFields,
            errors,
            durationMs: Date.now() - startTime,
        };
    }
    /**
     * Enrich multiple events
     */
    async enrichBatch(events) {
        // Process in parallel with concurrency limit
        const results = [];
        const concurrency = 10;
        for (let i = 0; i < events.length; i += concurrency) {
            const batch = events.slice(i, i + concurrency);
            const batchResults = await Promise.all(batch.map((event) => this.enrich(event)));
            results.push(...batchResults);
        }
        return results;
    }
    /**
     * Get a field value from an event
     */
    getFieldValue(event, field) {
        const parts = field.split('.');
        let value = event;
        for (const part of parts) {
            if (value === null || value === undefined) {
                return undefined;
            }
            value = value[part];
        }
        return value;
    }
    /**
     * Get indicator type based on field name
     */
    getIndicatorType(field) {
        if (field.toLowerCase().includes('ip')) {
            return 'ip';
        }
        if (field.toLowerCase().includes('hash')) {
            return 'hash';
        }
        if (field.toLowerCase().includes('domain') || field.toLowerCase().includes('host')) {
            return 'domain';
        }
        if (field.toLowerCase().includes('url')) {
            return 'url';
        }
        if (field.toLowerCase().includes('email')) {
            return 'email';
        }
        return 'unknown';
    }
    /**
     * Normalize field names
     */
    normalizeFields(event) {
        if (!event.customFields) {
            return event;
        }
        const normalizedCustomFields = {};
        let changed = false;
        for (const [key, value] of Object.entries(event.customFields)) {
            const normalizedKey = this.fieldNormalization[key] ?? key;
            if (normalizedKey !== key) {
                changed = true;
            }
            normalizedCustomFields[normalizedKey] = value;
        }
        if (!changed) {
            return event;
        }
        return {
            ...event,
            customFields: normalizedCustomFields,
        };
    }
}
// =============================================================================
// Factory Functions
// =============================================================================
/**
 * Create an event enricher
 */
export function createEventEnricher(config, options) {
    return new EventEnricher(config, options);
}
/**
 * Create a no-op enricher (passthrough)
 */
export function createNoOpEnricher() {
    return new EventEnricher({});
}
//# sourceMappingURL=enrichment.js.map