/**
 * Secure Credential Manager
 *
 * Provides secure storage and retrieval of API keys and other sensitive credentials.
 * Uses encryption when storing to disk and prioritizes environment variables.
 *
 * Security hierarchy (in order of preference):
 * 1. Environment variables (most secure - no disk storage)
 * 2. Encrypted storage with machine-specific key
 * 3. Plain-text storage (with warning)
 */
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';
import { logger } from '../utils/logger.js';
import { EventEmitter } from 'events';
// Environment variable mappings
const ENV_VAR_MAPPINGS = {
    apiKey: ['GROK_API_KEY', 'XAI_API_KEY'],
    claudeApiKey: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
    openaiApiKey: ['OPENAI_API_KEY'],
    geminiApiKey: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
};
// ============================================================================
// Encryption Utilities
// ============================================================================
/**
 * Generate a machine-specific encryption key
 * Uses hostname, username, and a static salt to create a deterministic key
 */
function getMachineKey() {
    const machineId = [
        os.hostname(),
        os.userInfo().username,
        os.platform(),
        'codebuddy-credential-salt-v1',
    ].join(':');
    return crypto.createHash('sha256').update(machineId).digest();
}
/**
 * Encrypt data using AES-256-GCM
 */
function encrypt(data) {
    const key = getMachineKey();
    const iv = crypto.randomBytes(12);
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    let encrypted = cipher.update(data, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    const authTag = cipher.getAuthTag();
    // Format: iv:authTag:encrypted
    return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
/**
 * Decrypt data using AES-256-GCM
 */
function decrypt(encryptedData) {
    const parts = encryptedData.split(':');
    if (parts.length !== 3) {
        throw new Error('Invalid encrypted data format');
    }
    const [ivHex, authTagHex, encrypted] = parts;
    // Validate hex strings (IV=12 bytes=24 hex, authTag=16 bytes=32 hex)
    if (ivHex.length !== 24 || !/^[0-9a-f]+$/i.test(ivHex)) {
        throw new Error('Invalid IV in encrypted data');
    }
    if (authTagHex.length !== 32 || !/^[0-9a-f]+$/i.test(authTagHex)) {
        throw new Error('Invalid auth tag in encrypted data');
    }
    const key = getMachineKey();
    const iv = Buffer.from(ivHex, 'hex');
    const authTag = Buffer.from(authTagHex, 'hex');
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(authTag);
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
}
// ============================================================================
// Credential Manager
// ============================================================================
export class CredentialManager extends EventEmitter {
    static instance;
    config;
    credentialsPath;
    cachedCredentials = null;
    constructor(config = {}) {
        super();
        this.config = {
            encryptionEnabled: true,
            warnPlainText: true,
            ...config,
        };
        this.credentialsPath = config.credentialsPath || path.join(os.homedir(), '.codebuddy', 'credentials.enc');
    }
    /**
     * Get singleton instance
     */
    static getInstance(config) {
        if (!CredentialManager.instance) {
            CredentialManager.instance = new CredentialManager(config);
        }
        return CredentialManager.instance;
    }
    /**
     * Reset singleton instance (for testing)
     */
    static resetInstance() {
        CredentialManager.instance = undefined;
    }
    /**
     * Get a credential by key
     * Priority: environment variable > encrypted storage > plain text storage
     */
    getCredential(key) {
        // 1. Try environment variables first (most secure)
        const envVars = ENV_VAR_MAPPINGS[key];
        if (envVars) {
            for (const envVar of envVars) {
                const value = process.env[envVar];
                if (value) {
                    logger.debug(`Credential ${key} loaded from environment variable ${envVar}`);
                    return value;
                }
            }
        }
        // 2. Try stored credentials
        const stored = this.loadCredentials();
        return stored[key];
    }
    /**
     * Set a credential
     * Stores encrypted if encryption is enabled
     */
    setCredential(key, value) {
        const credentials = this.loadCredentials();
        credentials[key] = value;
        this.saveCredentials(credentials);
        this.emit('credential:set', { key });
        logger.info(`Credential ${key} saved securely`);
    }
    /**
     * Delete a credential
     */
    deleteCredential(key) {
        const credentials = this.loadCredentials();
        delete credentials[key];
        this.saveCredentials(credentials);
        this.emit('credential:deleted', { key });
        logger.info(`Credential ${key} deleted`);
    }
    /**
     * Check if a credential exists (in env or storage)
     */
    hasCredential(key) {
        return this.getCredential(key) !== undefined;
    }
    /**
     * List all available credential keys
     */
    listCredentialKeys() {
        const stored = this.loadCredentials();
        const storedKeys = Object.keys(stored).filter(k => stored[k]);
        // Also include environment-based credentials
        const envKeys = [];
        for (const [key, envVars] of Object.entries(ENV_VAR_MAPPINGS)) {
            for (const envVar of envVars) {
                if (process.env[envVar]) {
                    envKeys.push(key);
                    break;
                }
            }
        }
        return [...new Set([...storedKeys, ...envKeys])];
    }
    /**
     * Get the API key (convenience method)
     * Checks GROK_API_KEY, XAI_API_KEY, then stored credentials
     */
    getApiKey() {
        return this.getCredential('apiKey');
    }
    /**
     * Set the API key (convenience method)
     */
    setApiKey(apiKey) {
        this.setCredential('apiKey', apiKey);
    }
    /**
     * Check if credentials are stored encrypted
     */
    isEncryptionEnabled() {
        return this.config.encryptionEnabled;
    }
    /**
     * Get security status for display
     */
    getSecurityStatus() {
        const stored = this.loadCredentials();
        const storedCount = Object.keys(stored).filter(k => stored[k]).length;
        let envCount = 0;
        for (const envVars of Object.values(ENV_VAR_MAPPINGS)) {
            for (const envVar of envVars) {
                if (process.env[envVar]) {
                    envCount++;
                    break;
                }
            }
        }
        return {
            encryptionEnabled: this.config.encryptionEnabled,
            storagePath: this.credentialsPath,
            credentialCount: storedCount,
            envCredentialCount: envCount,
        };
    }
    /**
     * Load credentials from storage
     */
    loadCredentials() {
        if (this.cachedCredentials) {
            return { ...this.cachedCredentials };
        }
        try {
            if (!fs.existsSync(this.credentialsPath)) {
                return {};
            }
            const content = fs.readFileSync(this.credentialsPath, 'utf-8');
            if (this.config.encryptionEnabled) {
                try {
                    const decrypted = decrypt(content);
                    this.cachedCredentials = JSON.parse(decrypted);
                    return { ...this.cachedCredentials };
                }
                catch {
                    // Only fall back to plaintext if content looks like valid JSON
                    // (i.e., legacy unencrypted file). Encrypted data won't start with '{'.
                    if (content.trimStart().startsWith('{')) {
                        logger.warn('Credentials file appears to be in legacy plain-text format');
                        if (this.config.warnPlainText) {
                            logger.warn('Consider re-saving credentials to enable encryption');
                        }
                        this.cachedCredentials = JSON.parse(content);
                        return { ...this.cachedCredentials };
                    }
                    // File is encrypted but decryption failed (key mismatch or corruption)
                    logger.error('Failed to decrypt credentials file. Key may have changed or file is corrupted.');
                    return {};
                }
            }
            else {
                if (this.config.warnPlainText) {
                    logger.warn('Credentials are stored in plain text. Consider enabling encryption.');
                }
                this.cachedCredentials = JSON.parse(content);
                return { ...this.cachedCredentials };
            }
        }
        catch (error) {
            logger.error('Failed to load credentials', {
                error: error instanceof Error ? error.message : String(error)
            });
            return {};
        }
    }
    /**
     * Save credentials to storage
     */
    saveCredentials(credentials) {
        try {
            // Ensure directory exists with secure permissions
            const dir = path.dirname(this.credentialsPath);
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
            }
            const content = JSON.stringify(credentials, null, 2);
            let dataToWrite;
            if (this.config.encryptionEnabled) {
                dataToWrite = encrypt(content);
            }
            else {
                if (this.config.warnPlainText) {
                    logger.warn('Saving credentials in plain text. Consider enabling encryption.');
                }
                dataToWrite = content;
            }
            // Write with secure permissions (owner read/write only)
            fs.writeFileSync(this.credentialsPath, dataToWrite, { mode: 0o600 });
            // Update cache
            this.cachedCredentials = credentials;
            // Verify file permissions
            const stats = fs.statSync(this.credentialsPath);
            const mode = stats.mode & 0o777;
            if (mode !== 0o600) {
                logger.warn(`Credentials file has insecure permissions: ${mode.toString(8)}. Should be 600.`);
                // Try to fix permissions
                try {
                    fs.chmodSync(this.credentialsPath, 0o600);
                    logger.info('Fixed credentials file permissions to 600');
                }
                catch {
                    logger.error('Failed to fix credentials file permissions');
                }
            }
        }
        catch (error) {
            logger.error('Failed to save credentials', {
                error: error instanceof Error ? error.message : String(error)
            });
            throw error;
        }
    }
    /**
     * Clear cached credentials (useful after modifications)
     */
    clearCache() {
        this.cachedCredentials = null;
    }
    /**
     * Migrate from legacy plain-text storage to encrypted
     */
    migrateToEncrypted() {
        if (!this.config.encryptionEnabled) {
            logger.warn('Encryption is disabled in config');
            return false;
        }
        try {
            // Force reload without cache
            this.cachedCredentials = null;
            // Load current credentials
            const credentials = this.loadCredentials();
            if (Object.keys(credentials).length === 0) {
                logger.info('No credentials to migrate');
                return true;
            }
            // Clear cache and re-save with encryption
            this.cachedCredentials = null;
            this.saveCredentials(credentials);
            logger.info('Successfully migrated credentials to encrypted storage');
            return true;
        }
        catch (error) {
            logger.error('Failed to migrate credentials to encrypted storage', {
                error: error instanceof Error ? error.message : String(error)
            });
            return false;
        }
    }
}
// ============================================================================
// Convenience Functions
// ============================================================================
/**
 * Get the singleton credential manager instance
 */
export function getCredentialManager(config) {
    return CredentialManager.getInstance(config);
}
/**
 * Get the API key from environment or secure storage
 */
export function getApiKey() {
    return getCredentialManager().getApiKey();
}
/**
 * Set the API key in secure storage
 */
export function setApiKey(apiKey) {
    getCredentialManager().setApiKey(apiKey);
}
//# sourceMappingURL=credential-manager.js.map