// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
/**
 * Evidence Collector
 *
 * Collects audit events, policy changes, access logs, and other compliance
 * evidence for external auditor export.
 *
 * @packageDocumentation
 */
import { eq, and, gte, lte, desc, inArray, sql } from 'drizzle-orm';
import { createLogger } from '../../common/logger.js';
import { getDatabase } from '../../common/db.js';
import { auditEntries, } from '../../audit/db-store.js';
const logger = createLogger({ component: 'evidence-collector' });
// =============================================================================
// TYPES
// =============================================================================
/**
 * Evidence types that can be collected
 */
export const EVIDENCE_EVENT_TYPES = [
    'audit',
    'policy_change',
    'access_decision',
    'trust_score_change',
    'escalation_decision',
    'data_retention',
];
// =============================================================================
// EVIDENCE COLLECTOR
// =============================================================================
/**
 * Collector for compliance evidence
 *
 * Aggregates audit events, policy changes, access decisions, and other
 * compliance-relevant data for export to external auditors.
 */
export class EvidenceCollector {
    db = null;
    initialized = false;
    collectorVersion = '1.0.0';
    constructor() { }
    /**
     * Initialize the collector
     */
    async initialize() {
        if (this.initialized)
            return;
        this.db = getDatabase();
        this.initialized = true;
        logger.info('Evidence collector initialized');
    }
    /**
     * Ensure collector is initialized
     */
    async ensureInitialized() {
        if (!this.initialized || !this.db) {
            await this.initialize();
        }
        return this.db;
    }
    /**
     * Collect all evidence based on filters
     */
    async collectEvidence(filter) {
        const db = await this.ensureInitialized();
        const collectionId = crypto.randomUUID();
        const eventTypes = filter.eventTypes ?? [...EVIDENCE_EVENT_TYPES];
        logger.info({
            collectionId,
            tenantId: filter.tenantId,
            startDate: filter.startDate.toISOString(),
            endDate: filter.endDate.toISOString(),
            eventTypes,
        }, 'Starting evidence collection');
        const startTime = Date.now();
        // Collect evidence for each requested type
        const [auditEvents, policyChanges, accessDecisions, trustScoreChanges, escalationDecisions, dataRetentionLogs,] = await Promise.all([
            eventTypes.includes('audit')
                ? this.collectAuditEvents(db, filter)
                : Promise.resolve([]),
            eventTypes.includes('policy_change')
                ? this.collectPolicyChanges(db, filter)
                : Promise.resolve([]),
            eventTypes.includes('access_decision')
                ? this.collectAccessDecisions(db, filter)
                : Promise.resolve([]),
            eventTypes.includes('trust_score_change')
                ? this.collectTrustScoreChanges(db, filter)
                : Promise.resolve([]),
            eventTypes.includes('escalation_decision')
                ? this.collectEscalationDecisions(db, filter)
                : Promise.resolve([]),
            eventTypes.includes('data_retention')
                ? this.collectDataRetentionLogs(db, filter)
                : Promise.resolve([]),
        ]);
        // Calculate summary statistics
        const summary = this.calculateSummary(auditEvents, policyChanges, accessDecisions, trustScoreChanges, escalationDecisions, dataRetentionLogs);
        const collection = {
            metadata: {
                collectionId,
                tenantId: filter.tenantId,
                collectedAt: new Date().toISOString(),
                periodStart: filter.startDate.toISOString(),
                periodEnd: filter.endDate.toISOString(),
                collectorVersion: this.collectorVersion,
                eventTypesIncluded: eventTypes,
                includePayloads: filter.includePayloads ?? false,
            },
            auditEvents,
            policyChanges,
            accessDecisions,
            trustScoreChanges,
            escalationDecisions,
            dataRetentionLogs,
            summary,
        };
        const duration = Date.now() - startTime;
        logger.info({
            collectionId,
            totalRecords: summary.totalRecords,
            durationMs: duration,
        }, 'Evidence collection completed');
        return collection;
    }
    /**
     * Collect audit events from the audit log
     */
    async collectAuditEvents(db, filter) {
        const limit = filter.limit ?? 10000;
        const results = await db
            .select()
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, filter.tenantId), gte(auditEntries.timestamp, filter.startDate), lte(auditEntries.timestamp, filter.endDate)))
            .orderBy(desc(auditEntries.timestamp))
            .limit(limit);
        return results.map((record) => ({
            id: record.id,
            timestamp: record.timestamp.toISOString(),
            eventType: record.eventType,
            category: record.category,
            severity: record.severity ?? 'info',
            actorId: record.actorId,
            actorType: record.actorType,
            actorName: record.actorName ?? undefined,
            action: record.action,
            resourceType: record.resourceType,
            resourceId: record.resourceId,
            resourceName: record.resourceName ?? undefined,
            outcome: record.outcome ?? 'success',
            reason: record.reason ?? undefined,
            tenantId: record.tenantId,
            requestId: record.requestId ?? undefined,
            traceId: record.traceId ?? undefined,
            metadata: filter.includePayloads ? (record.metadata ?? undefined) : undefined,
        }));
    }
    /**
     * Collect policy change events
     */
    async collectPolicyChanges(db, filter) {
        const limit = filter.limit ?? 10000;
        // Query audit entries for policy events
        const policyEventTypes = [
            'policy.created',
            'policy.updated',
            'policy.published',
            'policy.deprecated',
            'policy.archived',
        ];
        const results = await db
            .select()
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, filter.tenantId), gte(auditEntries.timestamp, filter.startDate), lte(auditEntries.timestamp, filter.endDate), inArray(auditEntries.eventType, policyEventTypes)))
            .orderBy(desc(auditEntries.timestamp))
            .limit(limit);
        return results.map((record) => {
            const metadata = record.metadata;
            const changeType = this.mapPolicyEventToChangeType(record.eventType);
            return {
                id: record.id,
                timestamp: record.timestamp.toISOString(),
                policyId: record.resourceId,
                policyName: record.resourceName ?? 'Unknown Policy',
                version: metadata?.['version'] ?? '1.0',
                previousVersion: metadata?.['previousVersion'],
                changeType,
                changedBy: record.actorId,
                changedByType: record.actorType,
                changeDescription: record.reason ?? undefined,
                diff: filter.includePayloads
                    ? {
                        before: metadata?.['before'],
                        after: metadata?.['after'],
                    }
                    : undefined,
            };
        });
    }
    /**
     * Map policy event type to change type
     */
    mapPolicyEventToChangeType(eventType) {
        switch (eventType) {
            case 'policy.created':
                return 'created';
            case 'policy.updated':
                return 'updated';
            case 'policy.published':
                return 'published';
            case 'policy.deprecated':
                return 'deprecated';
            case 'policy.archived':
                return 'archived';
            default:
                return 'updated';
        }
    }
    /**
     * Collect access control decisions
     */
    async collectAccessDecisions(db, filter) {
        const limit = filter.limit ?? 10000;
        // Query audit entries for access decisions
        const accessEventTypes = ['authz.granted', 'authz.denied', 'authz.elevated'];
        const results = await db
            .select()
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, filter.tenantId), gte(auditEntries.timestamp, filter.startDate), lte(auditEntries.timestamp, filter.endDate), inArray(auditEntries.eventType, accessEventTypes)))
            .orderBy(desc(auditEntries.timestamp))
            .limit(limit);
        return results.map((record) => {
            const metadata = record.metadata;
            return {
                id: record.id,
                timestamp: record.timestamp.toISOString(),
                intentId: metadata?.['intentId'] ?? record.resourceId,
                entityId: record.actorId,
                entityType: record.actorType,
                action: record.action,
                resource: `${record.resourceType}:${record.resourceId}`,
                decision: record.eventType === 'authz.denied' ? 'denied' : 'granted',
                reason: record.reason ?? undefined,
                trustScore: metadata?.['trustScore'],
                trustLevel: metadata?.['trustLevel'],
                policyEvaluated: metadata?.['policiesEvaluated'],
                constraintsApplied: metadata?.['constraintsApplied'],
            };
        });
    }
    /**
     * Collect trust score change events
     */
    async collectTrustScoreChanges(db, filter) {
        const limit = filter.limit ?? 10000;
        // Query audit entries for trust score changes
        const results = await db
            .select()
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, filter.tenantId), gte(auditEntries.timestamp, filter.startDate), lte(auditEntries.timestamp, filter.endDate), eq(auditEntries.category, 'system'), sql `${auditEntries.eventType} LIKE 'trust.%'`))
            .orderBy(desc(auditEntries.timestamp))
            .limit(limit);
        return results.map((record) => {
            const metadata = record.metadata;
            return {
                id: record.id,
                timestamp: record.timestamp.toISOString(),
                entityId: record.resourceId,
                entityType: record.resourceType,
                previousScore: metadata?.['previousScore'] ?? 0,
                newScore: metadata?.['newScore'] ?? 0,
                previousLevel: metadata?.['previousLevel'] ?? 0,
                newLevel: metadata?.['newLevel'] ?? 0,
                changeReason: record.reason ?? 'Score update',
                signalType: metadata?.['signalType'],
                signalSource: metadata?.['signalSource'],
            };
        });
    }
    /**
     * Collect escalation decision events
     */
    async collectEscalationDecisions(db, filter) {
        const limit = filter.limit ?? 10000;
        // Query audit entries for escalation events
        const escalationEventTypes = [
            'escalation.created',
            'escalation.acknowledged',
            'escalation.approved',
            'escalation.rejected',
            'escalation.timeout',
            'escalation.cancelled',
        ];
        const results = await db
            .select()
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, filter.tenantId), gte(auditEntries.timestamp, filter.startDate), lte(auditEntries.timestamp, filter.endDate), inArray(auditEntries.eventType, escalationEventTypes)))
            .orderBy(desc(auditEntries.timestamp))
            .limit(limit);
        return results.map((record) => {
            const metadata = record.metadata;
            const status = this.mapEscalationEventToStatus(record.eventType);
            return {
                id: record.id,
                timestamp: record.timestamp.toISOString(),
                escalationId: record.resourceId,
                intentId: metadata?.['intentId'] ?? '',
                reason: record.reason ?? 'Escalation required',
                escalatedTo: metadata?.['escalatedTo'] ?? '',
                status,
                resolvedBy: metadata?.['resolvedBy'],
                resolvedAt: metadata?.['resolvedAt'],
                resolutionNotes: metadata?.['resolutionNotes'],
                timeToResolution: metadata?.['timeToResolution'],
            };
        });
    }
    /**
     * Map escalation event type to status
     */
    mapEscalationEventToStatus(eventType) {
        switch (eventType) {
            case 'escalation.created':
            case 'escalation.acknowledged':
                return 'pending';
            case 'escalation.approved':
                return 'approved';
            case 'escalation.rejected':
            case 'escalation.cancelled':
                return 'rejected';
            case 'escalation.timeout':
                return 'timeout';
            default:
                return 'pending';
        }
    }
    /**
     * Collect data retention enforcement logs
     */
    async collectDataRetentionLogs(db, filter) {
        const limit = filter.limit ?? 10000;
        // Query audit entries for data retention events
        const dataRetentionEventTypes = [
            'data.deleted',
            'data.exported',
            'system.config.changed',
        ];
        const results = await db
            .select()
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, filter.tenantId), gte(auditEntries.timestamp, filter.startDate), lte(auditEntries.timestamp, filter.endDate), inArray(auditEntries.eventType, dataRetentionEventTypes)))
            .orderBy(desc(auditEntries.timestamp))
            .limit(limit);
        return results.map((record) => {
            const metadata = record.metadata;
            return {
                id: record.id,
                timestamp: record.timestamp.toISOString(),
                operation: this.mapDataEventToOperation(record.eventType),
                dataType: record.resourceType,
                recordCount: metadata?.['recordCount'] ?? 1,
                retentionPolicy: metadata?.['retentionPolicy'] ?? 'default',
                triggeredBy: metadata?.['triggeredBy'] ?? 'manual',
                requestId: record.requestId ?? undefined,
                success: record.outcome === 'success',
                errorMessage: record.outcome !== 'success' ? record.reason ?? undefined : undefined,
            };
        });
    }
    /**
     * Map data event type to operation
     */
    mapDataEventToOperation(eventType) {
        switch (eventType) {
            case 'data.deleted':
                return 'delete';
            case 'data.exported':
                return 'archive';
            default:
                return 'purge';
        }
    }
    /**
     * Calculate summary statistics for the collection
     */
    calculateSummary(auditEvents, policyChanges, accessDecisions, trustScoreChanges, escalationDecisions, dataRetentionLogs) {
        const byEventType = {
            audit: auditEvents.length,
            policy_change: policyChanges.length,
            access_decision: accessDecisions.length,
            trust_score_change: trustScoreChanges.length,
            escalation_decision: escalationDecisions.length,
            data_retention: dataRetentionLogs.length,
        };
        const byOutcome = {};
        const bySeverity = {};
        // Count outcomes from audit events
        for (const event of auditEvents) {
            byOutcome[event.outcome] = (byOutcome[event.outcome] ?? 0) + 1;
            bySeverity[event.severity] = (bySeverity[event.severity] ?? 0) + 1;
        }
        // Count access decisions
        for (const decision of accessDecisions) {
            byOutcome[decision.decision] = (byOutcome[decision.decision] ?? 0) + 1;
        }
        // Count escalation outcomes
        for (const escalation of escalationDecisions) {
            byOutcome[`escalation_${escalation.status}`] =
                (byOutcome[`escalation_${escalation.status}`] ?? 0) + 1;
        }
        // Count data retention outcomes
        for (const log of dataRetentionLogs) {
            byOutcome[log.success ? 'success' : 'failure'] =
                (byOutcome[log.success ? 'success' : 'failure'] ?? 0) + 1;
        }
        const totalRecords = auditEvents.length +
            policyChanges.length +
            accessDecisions.length +
            trustScoreChanges.length +
            escalationDecisions.length +
            dataRetentionLogs.length;
        return {
            totalRecords,
            byEventType,
            byOutcome,
            bySeverity,
        };
    }
    /**
     * Get event counts by category for a time period
     */
    async getEventCounts(tenantId, startDate, endDate) {
        const db = await this.ensureInitialized();
        const results = await db
            .select({
            category: auditEntries.category,
            count: sql `count(*)::int`,
        })
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, tenantId), gte(auditEntries.timestamp, startDate), lte(auditEntries.timestamp, endDate)))
            .groupBy(auditEntries.category);
        return Object.fromEntries(results.map((r) => [r.category, r.count]));
    }
    /**
     * Get event counts by time period (hourly, daily, weekly)
     */
    async getEventCountsByPeriod(tenantId, startDate, endDate, period) {
        const db = await this.ensureInitialized();
        const truncExpr = period === 'hour'
            ? sql `date_trunc('hour', ${auditEntries.timestamp})`
            : period === 'day'
                ? sql `date_trunc('day', ${auditEntries.timestamp})`
                : sql `date_trunc('week', ${auditEntries.timestamp})`;
        const results = await db
            .select({
            period: truncExpr,
            count: sql `count(*)::int`,
        })
            .from(auditEntries)
            .where(and(eq(auditEntries.tenantId, tenantId), gte(auditEntries.timestamp, startDate), lte(auditEntries.timestamp, endDate)))
            .groupBy(truncExpr)
            .orderBy(truncExpr);
        return results.map((r) => ({
            period: r.period.toISOString(),
            count: r.count,
        }));
    }
}
// =============================================================================
// SINGLETON
// =============================================================================
let evidenceCollectorInstance = null;
/**
 * Get the singleton evidence collector instance
 */
export function getEvidenceCollector() {
    if (!evidenceCollectorInstance) {
        evidenceCollectorInstance = new EvidenceCollector();
    }
    return evidenceCollectorInstance;
}
/**
 * Reset the singleton (for testing)
 */
export function resetEvidenceCollector() {
    evidenceCollectorInstance = null;
}
//# sourceMappingURL=evidence-collector.js.map