/**
 * Utility functions for handling .env files
 */
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { pathExists } from "./node-compat/fs.js";
/**
 * Prefixes for public/frontend-exposed environment variables
 */
export const PUBLIC_VAR_PREFIXES = ['VITE_', 'AGENTUITY_PUBLIC_', 'PUBLIC_'];
/**
 * Specific AGENTUITY_ keys that are allowed to be set by users.
 * Note: There is also a whitelist in the API that must be kept in sync.
 */
export const AGENTUITY_ALLOWED_KEYS = [
    'AGENTUITY_AUTH_SECRET',
    'AGENTUITY_CLOUD_BASE_URL',
];
/**
 * Check if a key is a public variable (exposed to frontend)
 */
export function isPublicVarKey(key) {
    const upperKey = key.toUpperCase();
    return PUBLIC_VAR_PREFIXES.some((prefix) => upperKey.startsWith(prefix));
}
/**
 * Check if a key is a reserved AGENTUITY key (except AGENTUITY_PUBLIC_ and allowed keys)
 */
export function isReservedAgentuityKey(key) {
    const upperKey = key.toUpperCase();
    if (!upperKey.startsWith('AGENTUITY_')) {
        return false;
    }
    if (upperKey.startsWith('AGENTUITY_PUBLIC_')) {
        return false;
    }
    if (AGENTUITY_ALLOWED_KEYS.includes(key)) {
        return false;
    }
    return true;
}
/**
 * Get public variable keys from secrets that should not be secrets
 */
export function getPublicSecretKeys(secrets) {
    return Object.keys(secrets).filter(isPublicVarKey);
}
/**
 * Validate that no public variables are being added as secrets
 * Returns the list of invalid keys, or empty array if valid
 */
export function validateNoPublicSecrets(secrets) {
    return getPublicSecretKeys(secrets);
}
/**
 * Find an existing .env file for reading.
 */
export async function findExistingEnvFile(dir) {
    return join(dir, '.env');
}
/**
 * Find env files to load based on config profile.
 * Returns list of .env file paths in order of precedence:
 * - For production or non-local: .env.{configName}, .env, .env.production
 * - For local/development: .env.{configName}, .env.development, .env
 */
export function getEnvFilePaths(dir, options) {
    const { configName, isProduction = false } = options;
    const files = isProduction
        ? ['.env', '.env.production']
        : ['.env.development', '.env'];
    if (configName) {
        files.unshift(`.env.${configName}`);
    }
    return files.map((f) => join(dir, f));
}
/**
 * Parse a single line from an .env file
 * Handles comments, empty lines, and quoted values
 */
export function parseEnvLine(line) {
    const trimmed = line.trim();
    // Skip empty lines and comments
    if (!trimmed || trimmed.startsWith('#')) {
        return null;
    }
    const equalIndex = trimmed.indexOf('=');
    if (equalIndex === -1) {
        return null;
    }
    const key = trimmed.slice(0, equalIndex).trim();
    let value = trimmed.slice(equalIndex + 1).trim();
    // Remove surrounding quotes if present
    if ((value.startsWith('"') && value.endsWith('"')) ||
        (value.startsWith("'") && value.endsWith("'"))) {
        value = value.slice(1, -1);
    }
    return { key, value };
}
/**
 * Read and parse an .env file
 */
export async function readEnvFile(path) {
    if (!(await pathExists(path))) {
        return {};
    }
    const content = await readFile(path, 'utf-8');
    const lines = content.split('\n');
    const env = {};
    for (const line of lines) {
        const parsed = parseEnvLine(line);
        if (parsed) {
            env[parsed.key] = parsed.value;
        }
    }
    return env;
}
/**
 * Write environment variables to an .env file
 * By default, preserves existing keys that are not in the new vars.
 * Use preserveExisting: false to completely overwrite the file.
 */
export async function writeEnvFile(path, vars, options) {
    const skipKeys = options?.skipKeys || [];
    const preserveExisting = options?.preserveExisting ?? true;
    // If preserveExisting is true, read existing file and merge
    let finalVars = vars;
    if (preserveExisting) {
        const existing = await readEnvFile(path);
        // Merge: existing as base, new vars override
        finalVars = { ...existing, ...vars };
    }
    const lines = [];
    // Sort keys for consistent output
    const sortedKeys = Object.keys(finalVars).sort();
    for (const key of sortedKeys) {
        if (skipKeys.includes(key)) {
            continue;
        }
        const value = finalVars[key];
        // Add comment if provided
        if (options?.addComment) {
            const comment = options.addComment(key);
            if (comment) {
                lines.push(`# ${comment}`);
            }
        }
        // Write key=value
        lines.push(`${key}=${value}`);
    }
    const content = lines.join('\n') + '\n';
    await writeFile(path, content);
}
/**
 * Merge environment variables with special handling
 * - Later values override earlier values
 * - Can filter out keys (like AGENTUITY_* keys)
 */
export function mergeEnvVars(base, updates, options) {
    const merged = { ...base };
    const filterPrefix = options?.filterPrefix;
    for (const [key, value] of Object.entries(updates)) {
        // Skip keys with filter prefix if specified
        if (filterPrefix && key.startsWith(filterPrefix)) {
            continue;
        }
        merged[key] = value;
    }
    return merged;
}
/**
 * Filter out reserved AGENTUITY_ prefixed keys from env vars
 * This is used when pushing to the cloud to avoid sending SDK keys
 * Note: AGENTUITY_PUBLIC_* keys are allowed (they are public env vars)
 */
export function filterAgentuitySdkKeys(vars) {
    if (!vars)
        return {};
    const filtered = {};
    for (const [key, value] of Object.entries(vars)) {
        if (!isReservedAgentuityKey(key)) {
            filtered[key] = value;
        }
    }
    return filtered;
}
/**
 * Keys that should always be treated as secrets (exact match)
 */
const secretExactKeys = ['DATABASE_URL', 'AWS_ACCESS_KEY_ID', 'AWS_SESSION_TOKEN'];
/**
 * Split env vars into env and secrets based on key names
 * Convention: Keys ending with _SECRET, _KEY, _TOKEN, _PASSWORD are secrets
 * Also treats DATABASE_URL as a secret since it contains credentials
 * Note: Public vars (VITE_, AGENTUITY_PUBLIC_, PUBLIC_) always go to env, never secrets
 */
export function splitEnvAndSecrets(vars) {
    const env = {};
    const secrets = {};
    const secretSuffixes = ['_SECRET', '_KEY', '_TOKEN', '_PASSWORD', '_PRIVATE'];
    for (const [key, value] of Object.entries(vars)) {
        // Skip reserved AGENTUITY_ prefixed keys (except AGENTUITY_PUBLIC_)
        if (isReservedAgentuityKey(key)) {
            continue;
        }
        // Public vars always go to env, never secrets
        if (isPublicVarKey(key)) {
            env[key] = value;
            continue;
        }
        const isSecretByExactMatch = secretExactKeys.includes(key);
        const isSecretBySuffix = secretSuffixes.some((suffix) => key.endsWith(suffix));
        if (isSecretByExactMatch || isSecretBySuffix) {
            secrets[key] = value;
        }
        else {
            env[key] = value;
        }
    }
    return { env, secrets };
}
/**
 * Mask a secret value for display
 * - For URLs with credentials: parses and masks username/password partially
 * - For long strings: shows more context (first 6 and last 6 chars)
 * - For short strings: shows generic masking
 */
export function maskSecret(value) {
    if (!value) {
        return '';
    }
    if (value.length <= 8) {
        return '***';
    }
    // Try to parse as URL with credentials
    try {
        const url = new URL(value);
        // If URL has username or password, mask them partially
        if (url.username || url.password) {
            let maskedAuth = '';
            if (url.username) {
                // Show first 2 and last 2 chars of username
                const len = url.username.length;
                if (len <= 4) {
                    maskedAuth = '***';
                }
                else {
                    maskedAuth = `${url.username.slice(0, 2)}***${url.username.slice(-2)}`;
                }
            }
            if (url.password) {
                // Show first 3 and last 3 chars of password
                const len = url.password.length;
                if (len <= 6) {
                    maskedAuth += ':***';
                }
                else {
                    maskedAuth += `:${url.password.slice(0, 3)}***${url.password.slice(-3)}`;
                }
            }
            else if (url.username) {
                // Username but no password
                maskedAuth += ':***';
            }
            // Reconstruct URL with masked credentials
            const maskedUrl = `${url.protocol}//${maskedAuth}@${url.host}${url.pathname}${url.search}${url.hash}`;
            return maskedUrl;
        }
    }
    catch {
        // Not a valid URL, fall through to regular masking
    }
    // For long strings, show first 6 and last 6 characters for better context
    return `${value.slice(0, 6)}...${value.slice(-6)}`;
}
/**
 * Detect if a key or value looks like it should be a secret
 */
export function looksLikeSecret(key, value) {
    // Check key name for secret-like patterns
    const secretKeyPatterns = [
        /_SECRET$/i,
        /_KEY$/i,
        /_TOKEN$/i,
        /_PASSWORD$/i,
        /_PRIVATE$/i,
        /_CERT$/i,
        /_CERTIFICATE$/i,
        /^SECRET_/i,
        /^API_?KEY/i,
        /^JWT/i,
        /PASSWORD/i,
        /CREDENTIAL/i,
        /AUTH.*KEY/i,
    ];
    const keyLooksSecret = secretKeyPatterns.some((pattern) => pattern.test(key));
    if (keyLooksSecret) {
        return true;
    }
    // Check value for secret-like patterns
    if (!value || value.length < 8) {
        return false;
    }
    // JWT pattern (header.payload.signature)
    if (/^eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value)) {
        return true;
    }
    // Bearer token pattern
    if (/^Bearer\s+[A-Za-z0-9_-]{20,}$/i.test(value)) {
        return true;
    }
    // AWS/Cloud provider key patterns
    if (/^(AKIA|ASIA)[A-Z0-9]{16}$/.test(value)) {
        // AWS access key
        return true;
    }
    // GitHub token patterns
    if (/^gh[ps]_[A-Za-z0-9_]{36,}$/.test(value)) {
        return true;
    }
    // Generic long alphanumeric strings (likely API keys)
    // Exclude UUIDs (8-4-4-4-12 format) and simple alphanumeric IDs
    const isUUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value);
    if (!isUUID && /^[A-Za-z0-9_-]{32,}$/.test(value) && !/^[0-9]+$/.test(value)) {
        return true;
    }
    // PEM-encoded certificates or private keys
    if (value.includes('BEGIN CERTIFICATE') ||
        value.includes('BEGIN PRIVATE KEY') ||
        value.includes('BEGIN RSA PRIVATE KEY')) {
        return true;
    }
    return false;
}
/**
 * Add resource environment variables to the .env file.
 * Merges with existing variables, overwriting any that already exist.
 */
export async function addResourceEnvVars(dir, envVars) {
    const envPath = join(dir, '.env');
    const existing = await readEnvFile(envPath);
    const merged = { ...existing, ...envVars };
    await writeEnvFile(envPath, merged);
}
/**
 * Remove resource environment variables from the .env file.
 * Only removes the specified keys if they exist.
 */
export async function removeResourceEnvVars(dir, keys) {
    const envPath = join(dir, '.env');
    const existing = await readEnvFile(envPath);
    let changed = false;
    for (const key of keys) {
        if (key in existing) {
            delete existing[key];
            changed = true;
        }
    }
    if (changed) {
        await writeEnvFile(envPath, existing);
    }
}
/**
 * Get the value of a specific key from the .env file
 */
export async function getEnvVar(dir, key) {
    const envPath = join(dir, '.env');
    const existing = await readEnvFile(envPath);
    return existing[key];
}
//# sourceMappingURL=env-util.js.map