/**
 * Scan Registry Module
 *
 * Maintains a persistent registry of all scans for analytics,
 * case study identification, and compliance auditing.
 *
 * @module telemetry/registry
 */
import { readFile, writeFile, mkdir } from "fs/promises";
import { join, dirname } from "path";
import { createHash } from "crypto";
import { logger } from "../logger.js";
// ============================================================================
// Scan Registry
// ============================================================================
/**
 * Scan Registry for persistent tracking of all scans
 */
export class ScanRegistry {
    records = [];
    storagePath;
    loaded = false;
    constructor(storagePath) {
        this.storagePath =
            storagePath ||
                join(process.env.HOME || "/tmp", ".vaspera", "scan-registry.json");
    }
    /**
     * Load registry from storage
     */
    async load() {
        if (this.loaded)
            return;
        try {
            const content = await readFile(this.storagePath, "utf-8");
            const data = JSON.parse(content);
            if (data.version !== 1) {
                logger.warn("registry.version_mismatch", {
                    expected: 1,
                    actual: data.version,
                });
            }
            this.records = data.records || [];
            this.loaded = true;
            logger.debug("registry.loaded", { recordCount: this.records.length });
        }
        catch (error) {
            if (error.code === "ENOENT") {
                this.records = [];
                this.loaded = true;
                logger.debug("registry.created_new");
            }
            else {
                logger.error("registry.load_failed", {
                    error: error instanceof Error ? error.message : String(error),
                });
                throw error;
            }
        }
    }
    /**
     * Save registry to storage
     */
    async save() {
        const data = {
            version: 1,
            lastUpdated: new Date().toISOString(),
            records: this.records,
        };
        await mkdir(dirname(this.storagePath), { recursive: true });
        await writeFile(this.storagePath, JSON.stringify(data, null, 2));
        logger.debug("registry.saved", { recordCount: this.records.length });
    }
    /**
     * Generate a unique scan ID
     */
    generateScanId() {
        const timestamp = Date.now().toString(36);
        const random = Math.random().toString(36).slice(2, 8);
        return `scan-${timestamp}-${random}`;
    }
    /**
     * Hash a project path for matching
     */
    hashProjectPath(path) {
        return createHash("sha256").update(path).digest("hex").slice(0, 16);
    }
    /**
     * Record a new scan
     */
    async recordScan(record) {
        await this.load();
        const fullRecord = {
            ...record,
            id: this.generateScanId(),
            projectHash: this.hashProjectPath(record.projectPath),
        };
        this.records.push(fullRecord);
        await this.save();
        logger.info("registry.scan_recorded", {
            scanId: fullRecord.id,
            level: fullRecord.level,
            score: fullRecord.score,
        });
        return fullRecord;
    }
    /**
     * Get a scan by ID
     */
    async getScan(scanId) {
        await this.load();
        return this.records.find((r) => r.id === scanId);
    }
    /**
     * Get scans for a project (by path or hash)
     */
    async getScansForProject(projectPathOrHash) {
        await this.load();
        const hash = projectPathOrHash.length === 16
            ? projectPathOrHash
            : this.hashProjectPath(projectPathOrHash);
        return this.records.filter((r) => r.projectHash === hash);
    }
    /**
     * Get scans for an organization
     */
    async getScansForOrg(orgName) {
        await this.load();
        return this.records.filter((r) => r.orgName === orgName);
    }
    /**
     * Get recent scans
     */
    async getRecentScans(limit = 50) {
        await this.load();
        return [...this.records]
            .sort((a, b) => new Date(b.scanDate).getTime() - new Date(a.scanDate).getTime())
            .slice(0, limit);
    }
    /**
     * Get successful scans with high scores (case study candidates)
     */
    async getCaseStudyCandidates(minScore = 80) {
        await this.load();
        return this.records
            .filter((r) => r.success &&
            r.score !== undefined &&
            r.score >= minScore &&
            (r.caseStudyConsent === true || r.repoUrl !== undefined))
            .sort((a, b) => (b.score || 0) - (a.score || 0));
    }
    /**
     * Get all unique organizations
     */
    async getOrganizations() {
        await this.load();
        const orgs = new Set();
        for (const record of this.records) {
            if (record.orgName) {
                orgs.add(record.orgName);
            }
        }
        return Array.from(orgs).sort();
    }
    /**
     * Get analytics summary
     */
    async getAnalytics() {
        await this.load();
        const byLevel = {
            CERTIFIED: 0,
            APPROVED: 0,
            REVIEW_REQUIRED: 0,
            BLOCKED: 0,
        };
        const byFramework = {};
        const byScanner = {};
        const byMonth = {};
        const findingCounts = new Map();
        let totalScore = 0;
        let scoreCount = 0;
        let totalDuration = 0;
        let successfulScans = 0;
        let failedScans = 0;
        let caseStudyCandidates = 0;
        for (const record of this.records) {
            // Count success/failure
            if (record.success) {
                successfulScans++;
            }
            else {
                failedScans++;
            }
            // Count by level
            if (record.level) {
                byLevel[record.level]++;
            }
            // Count by framework
            for (const framework of record.frameworksAssessed) {
                byFramework[framework] = (byFramework[framework] || 0) + 1;
            }
            // Count by scanner
            for (const scanner of record.scannersRun) {
                byScanner[scanner] = (byScanner[scanner] || 0) + 1;
            }
            // Count by month
            const month = record.scanDate.slice(0, 7); // YYYY-MM
            byMonth[month] = (byMonth[month] || 0) + 1;
            // Accumulate score
            if (record.score !== undefined) {
                totalScore += record.score;
                scoreCount++;
            }
            // Accumulate duration
            totalDuration += record.duration;
            // Count case study candidates
            if (record.success && record.score !== undefined && record.score >= 80) {
                if (record.caseStudyConsent || record.repoUrl) {
                    caseStudyCandidates++;
                }
            }
        }
        // Calculate top findings (placeholder - would need finding-level data)
        const topFindings = [];
        return {
            totalScans: this.records.length,
            successfulScans,
            failedScans,
            byLevel,
            byFramework,
            byScanner,
            byMonth,
            averageScore: scoreCount > 0 ? Math.round(totalScore / scoreCount) : 0,
            averageDuration: this.records.length > 0
                ? Math.round(totalDuration / this.records.length)
                : 0,
            topFindings,
            caseStudyCandidates,
        };
    }
    /**
     * Export registry to JSON
     */
    async exportToJson() {
        await this.load();
        return JSON.stringify({
            exportedAt: new Date().toISOString(),
            recordCount: this.records.length,
            records: this.records,
        }, null, 2);
    }
    /**
     * Export analytics to JSON
     */
    async exportAnalyticsToJson() {
        const analytics = await this.getAnalytics();
        return JSON.stringify({
            exportedAt: new Date().toISOString(),
            analytics,
        }, null, 2);
    }
    /**
     * Get total scan count
     */
    async getCount() {
        await this.load();
        return this.records.length;
    }
    /**
     * Clear all records (use with caution)
     */
    async clear() {
        this.records = [];
        await this.save();
        logger.warn("registry.cleared");
    }
}
// ============================================================================
// Singleton Instance
// ============================================================================
let _registry = null;
/**
 * Get the singleton registry instance
 */
export function getRegistry(storagePath) {
    if (!_registry) {
        _registry = new ScanRegistry(storagePath);
    }
    return _registry;
}
/**
 * Reset the singleton (for testing)
 */
export function resetRegistry() {
    _registry = null;
}
//# sourceMappingURL=registry.js.map