/**
 * EncryptedStorage -- Transparent encryption for governance file storage.
 *
 * DELEGATES all cryptographic operations to EncryptionLayer (AIAG-014).
 * This class provides the file-oriented convenience API (writeEncrypted,
 * readEncrypted, JSONL helpers) while EncryptionLayer owns the single
 * AES-256-GCM implementation.
 *
 * Backwards compatible: reads legacy CXENC1/CXENC2 format files and
 * re-encrypts them in the unified CONNEXUM-ENC format on next write.
 *
 * T-032: Encrypt audit logs at rest.
 * T-034: Encrypt memory files at rest.
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
import { createLogger } from '../util/log.js';
import { EncryptionLayer } from './encryption-layer.js';
const log = createLogger('encrypted-storage');
// Legacy header strings kept only for isEncrypted() sniffing and error
// messages. All cryptographic operations — including legacy CXENC1/CXENC2
// decryption — are delegated to EncryptionLayer (AIAG-014).
const LEGACY_MAGIC = 'CXENC1';
const LEGACY_MAGIC_V2 = 'CXENC2';
export class EncryptedStorage {
    passphrase;
    enabled;
    keyManager;
    encryptionLayer;
    constructor(config) {
        if (config.passphrase.length < 8) {
            throw new Error('Passphrase must be at least 8 characters');
        }
        this.passphrase = config.passphrase;
        this.enabled = config.enabled !== false;
        this.keyManager = config.keyManager;
        // Delegate all crypto to the unified EncryptionLayer (AIAG-014)
        // Pad passphrase to meet EncryptionLayer's 16-char minimum if needed
        const paddedPassphrase = config.passphrase.length < 16
            ? config.passphrase.padEnd(16, config.passphrase)
            : config.passphrase;
        this.encryptionLayer = new EncryptionLayer({
            passphrase: paddedPassphrase,
            keyManager: config.keyManager,
        });
    }
    /**
     * Write data encrypted to a file.
     * Uses the unified EncryptionLayer (CONNEXUM-ENC-V1/V2 format).
     */
    writeEncrypted(filePath, data) {
        const dir = path.dirname(filePath);
        if (!fs.existsSync(dir))
            fs.mkdirSync(dir, { recursive: true });
        if (!this.enabled) {
            fs.writeFileSync(filePath, data);
            return;
        }
        const encrypted = this.encryptionLayer.encryptString(data);
        fs.writeFileSync(filePath, encrypted);
    }
    /**
     * Read and decrypt a file.
     * Handles legacy CXENC1/CXENC2 formats AND unified CONNEXUM-ENC formats.
     */
    readEncrypted(filePath) {
        if (!fs.existsSync(filePath)) {
            throw new Error(`File not found: ${filePath}`);
        }
        const raw = fs.readFileSync(filePath);
        const headerStr = raw.subarray(0, Math.min(16, raw.length)).toString();
        // AIAG-014: delegate ALL crypto to EncryptionLayer, which now handles
        // CONNEXUM-ENC-V1/V2 + legacy CXENC1/CXENC2 in one code path.
        if (this.encryptionLayer.isEncrypted(raw)) {
            if (!this.enabled) {
                throw new Error('Encrypted file found but encryption is disabled');
            }
            const result = this.encryptionLayer.decrypt(raw);
            if (!result.success) {
                throw new Error(result.error || 'Decryption failed');
            }
            return result.content.toString('utf-8');
        }
        // Not encrypted -- return as plain text (backwards compatible)
        return raw.toString('utf-8');
    }
    /**
     * Append an encrypted JSONL entry.
     * Note: For JSONL, we re-read, append, and re-encrypt the whole file.
     * This ensures the entire file remains a single encrypted blob.
     */
    appendEncryptedJsonl(filePath, entry) {
        let existing = '';
        if (fs.existsSync(filePath)) {
            try {
                existing = this.readEncrypted(filePath);
            }
            catch {
                existing = '';
            }
        }
        const newContent = existing
            ? existing.trimEnd() + '\n' + JSON.stringify(entry) + '\n'
            : JSON.stringify(entry) + '\n';
        this.writeEncrypted(filePath, newContent);
    }
    /**
     * Read all JSONL entries from an encrypted file.
     */
    readEncryptedJsonl(filePath) {
        if (!fs.existsSync(filePath))
            return [];
        try {
            const content = this.readEncrypted(filePath);
            return content
                .trim()
                .split('\n')
                .filter(Boolean)
                .map(line => {
                try {
                    return JSON.parse(line);
                }
                catch {
                    return null;
                }
            })
                .filter((item) => item !== null);
        }
        catch {
            return [];
        }
    }
    /**
     * Write JSON object encrypted.
     */
    writeEncryptedJson(filePath, data) {
        this.writeEncrypted(filePath, JSON.stringify(data, null, 2));
    }
    /**
     * Read encrypted JSON object.
     */
    readEncryptedJson(filePath) {
        try {
            const content = this.readEncrypted(filePath);
            return JSON.parse(content);
        }
        catch {
            return null;
        }
    }
    /**
     * Check if a file is encrypted (legacy CXENC or unified CONNEXUM-ENC format).
     */
    isEncrypted(filePath) {
        if (!fs.existsSync(filePath))
            return false;
        try {
            // Check for unified format
            if (this.encryptionLayer.isFileEncrypted(filePath))
                return true;
            // Check for legacy format
            const header = Buffer.alloc(LEGACY_MAGIC_V2.length);
            const fd = fs.openSync(filePath, 'r');
            fs.readSync(fd, header, 0, LEGACY_MAGIC_V2.length, 0);
            fs.closeSync(fd);
            const str = header.toString();
            return str === LEGACY_MAGIC_V2 || str.startsWith(LEGACY_MAGIC);
        }
        catch {
            return false;
        }
    }
    /**
     * Encrypt an existing plaintext file in place.
     * Uses the unified EncryptionLayer format (migrates from legacy on write).
     */
    encryptFile(filePath) {
        if (!fs.existsSync(filePath))
            return false;
        if (this.isEncrypted(filePath))
            return true; // Already encrypted
        try {
            const content = fs.readFileSync(filePath, 'utf-8');
            this.writeEncrypted(filePath, content);
            return true;
        }
        catch (err) {
            log.error('Failed to encrypt file', { file: filePath, error: err.message });
            return false;
        }
    }
    /**
     * Decrypt an encrypted file in place (for migration/debug).
     */
    decryptFile(filePath) {
        if (!fs.existsSync(filePath))
            return false;
        if (!this.isEncrypted(filePath))
            return true; // Already plaintext
        try {
            const content = this.readEncrypted(filePath);
            fs.writeFileSync(filePath, content);
            return true;
        }
        catch (err) {
            log.error('Failed to decrypt file', { file: filePath, error: err.message });
            return false;
        }
    }
    /**
     * Get the underlying EncryptionLayer for direct buffer operations.
     * Exposed for modules that need buffer-level crypto without file I/O.
     */
    getEncryptionLayer() {
        return this.encryptionLayer;
    }
}
//# sourceMappingURL=encrypted-storage.js.map