/**
 * VendorRegistry -- Third-party AI vendor/tool registry with risk scoring.
 *
 * Tracks all third-party AI tools, models, and services used across
 * the organization. Assigns risk scores based on data handling,
 * jurisdiction, compliance certifications, and access patterns.
 *
 * T-062: Vendor registry for third-party AI tools.
 * T-063: Risk scoring for vendors.
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import { createLogger } from '../util/log.js';
const log = createLogger('vendor-registry');
export class VendorRegistry {
    config;
    vendorsFile;
    auditFile;
    constructor(config) {
        this.config = {
            blockThreshold: 80,
            reviewThreshold: 50,
            ...config,
        };
        if (!fs.existsSync(config.registryDir)) {
            fs.mkdirSync(config.registryDir, { recursive: true });
        }
        this.vendorsFile = path.join(config.registryDir, 'vendors.json');
        this.auditFile = path.join(config.registryDir, 'vendor-audit.jsonl');
    }
    /**
     * Register a new vendor.
     */
    register(vendor) {
        const assessment = this.assessRisk(vendor);
        const newVendor = {
            id: `vendor_${crypto.randomBytes(6).toString('hex')}`,
            ...vendor,
            status: assessment.recommendation === 'BLOCK' ? 'BLOCKED'
                : assessment.recommendation === 'REVIEW' ? 'UNDER_REVIEW'
                    : 'APPROVED',
            riskScore: assessment.riskScore,
            riskFactors: assessment.factors,
            addedAt: new Date().toISOString(),
        };
        const vendors = this.loadVendors();
        vendors.push(newVendor);
        this.saveVendors(vendors);
        this.audit('VENDOR_REGISTERED', {
            vendorId: newVendor.id,
            name: newVendor.name,
            riskScore: newVendor.riskScore,
            status: newVendor.status,
        });
        log.info('Vendor registered', { id: newVendor.id, name: newVendor.name, risk: newVendor.riskScore });
        return newVendor;
    }
    /**
     * Assess risk for a vendor (without registering).
     */
    assessRisk(vendor) {
        const factors = [];
        // Factor 1: Data handling (0-20)
        const dataScore = this.scoreDataHandling(vendor.dataHandling || 'NO_DATA');
        factors.push({ factor: 'data_handling', score: dataScore, description: `Data handling: ${vendor.dataHandling}` });
        // Factor 2: Jurisdiction risk (0-20)
        const jurisdictionScore = this.scoreJurisdiction(vendor.jurisdiction || 'unknown');
        factors.push({ factor: 'jurisdiction', score: jurisdictionScore, description: `Jurisdiction: ${vendor.jurisdiction}` });
        // Factor 3: Compliance certifications (0-20, inverse -- more certs = lower risk)
        const certScore = this.scoreCertifications(vendor.certifications || []);
        factors.push({ factor: 'certifications', score: certScore, description: `Certs: ${(vendor.certifications || []).join(', ') || 'none'}` });
        // Factor 4: Vendor type risk (0-20)
        const typeScore = this.scoreVendorType(vendor.type || 'TOOL');
        factors.push({ factor: 'vendor_type', score: typeScore, description: `Type: ${vendor.type}` });
        // Factor 5: Access pattern (0-20)
        const accessScore = vendor.url ? 5 : 15; // External API is riskier than local tool
        factors.push({ factor: 'access_pattern', score: accessScore, description: vendor.url ? 'External API access' : 'Local/internal' });
        const totalScore = factors.reduce((sum, f) => sum + f.score, 0);
        let riskLevel;
        if (totalScore >= 80)
            riskLevel = 'CRITICAL';
        else if (totalScore >= 60)
            riskLevel = 'HIGH';
        else if (totalScore >= 40)
            riskLevel = 'MEDIUM';
        else
            riskLevel = 'LOW';
        let recommendation;
        if (totalScore >= this.config.blockThreshold)
            recommendation = 'BLOCK';
        else if (totalScore >= this.config.reviewThreshold)
            recommendation = 'REVIEW';
        else
            recommendation = 'APPROVE';
        return {
            vendorId: vendor.id || '',
            riskScore: totalScore,
            riskLevel,
            factors,
            recommendation,
        };
    }
    /**
     * Update vendor status after review.
     */
    updateStatus(vendorId, status, reviewedBy, notes) {
        const vendors = this.loadVendors();
        const vendor = vendors.find(v => v.id === vendorId);
        if (!vendor)
            throw new Error(`Vendor ${vendorId} not found`);
        vendor.status = status;
        vendor.reviewedAt = new Date().toISOString();
        vendor.reviewedBy = reviewedBy;
        if (notes)
            vendor.notes = notes;
        this.saveVendors(vendors);
        this.audit('STATUS_UPDATED', { vendorId, status, reviewedBy });
    }
    /**
     * Check if a vendor is allowed.
     */
    isAllowed(vendorId) {
        const vendor = this.getVendor(vendorId);
        return vendor?.status === 'APPROVED';
    }
    /**
     * Get a vendor by ID.
     */
    getVendor(vendorId) {
        return this.loadVendors().find(v => v.id === vendorId);
    }
    /**
     * Find vendor by name (case-insensitive).
     */
    findByName(name) {
        const lower = name.toLowerCase();
        return this.loadVendors().find(v => v.name.toLowerCase() === lower);
    }
    /**
     * List all vendors, optionally filtered.
     */
    list(filter) {
        let vendors = this.loadVendors();
        if (filter?.status)
            vendors = vendors.filter(v => v.status === filter.status);
        if (filter?.type)
            vendors = vendors.filter(v => v.type === filter.type);
        if (filter?.minRisk !== undefined)
            vendors = vendors.filter(v => v.riskScore >= filter.minRisk);
        return vendors;
    }
    /**
     * Get registry statistics.
     */
    getStats() {
        const vendors = this.loadVendors();
        const byStatus = {
            APPROVED: 0, UNDER_REVIEW: 0, BLOCKED: 0, PENDING: 0,
        };
        let totalRisk = 0;
        let highRisk = 0;
        for (const v of vendors) {
            byStatus[v.status]++;
            totalRisk += v.riskScore;
            if (v.riskScore >= 60)
                highRisk++;
        }
        return {
            total: vendors.length,
            byStatus,
            avgRiskScore: vendors.length > 0 ? Math.round(totalRisk / vendors.length) : 0,
            highRiskCount: highRisk,
        };
    }
    /**
     * Remove a vendor.
     */
    remove(vendorId) {
        const vendors = this.loadVendors();
        const idx = vendors.findIndex(v => v.id === vendorId);
        if (idx === -1)
            return false;
        vendors.splice(idx, 1);
        this.saveVendors(vendors);
        this.audit('VENDOR_REMOVED', { vendorId });
        return true;
    }
    // --- Private scoring ---
    scoreDataHandling(handling) {
        const scores = {
            NO_DATA: 0,
            TRANSIENT: 5,
            STORED: 12,
            TRAINED: 20,
        };
        return scores[handling] ?? 10;
    }
    scoreJurisdiction(jurisdiction) {
        const j = jurisdiction.toLowerCase();
        // Five Eyes + EU: lower risk
        if (['us', 'uk', 'eu', 'ca', 'au', 'nz', 'de', 'fr', 'nl'].includes(j))
            return 2;
        // Adequate countries: medium
        if (['jp', 'kr', 'il', 'ch', 'sg'].includes(j))
            return 8;
        // PRC, Russia: highest risk (PRC National Intelligence Law)
        if (['cn', 'prc', 'ru'].includes(j))
            return 20;
        return 12; // Unknown
    }
    scoreCertifications(certs) {
        if (certs.length === 0)
            return 20;
        if (certs.length >= 4)
            return 0;
        if (certs.length >= 2)
            return 5;
        return 10;
    }
    scoreVendorType(type) {
        const scores = {
            INFRASTRUCTURE: 15,
            MODEL_PROVIDER: 12,
            API: 10,
            PLUGIN: 8,
            TOOL: 5,
        };
        return scores[type] ?? 10;
    }
    loadVendors() {
        if (!fs.existsSync(this.vendorsFile))
            return [];
        try {
            return JSON.parse(fs.readFileSync(this.vendorsFile, 'utf-8'));
        }
        catch {
            return [];
        }
    }
    saveVendors(vendors) {
        fs.writeFileSync(this.vendorsFile, JSON.stringify(vendors, null, 2));
    }
    audit(action, data) {
        const entry = { timestamp: new Date().toISOString(), action, ...data };
        try {
            fs.appendFileSync(this.auditFile, JSON.stringify(entry) + '\n');
        }
        catch {
            log.error('Failed to write vendor audit', { action });
        }
    }
}
//# sourceMappingURL=vendor-registry.js.map