// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
import { createLogger } from '../../common/logger.js';
// Lazy import to avoid circular dependencies
let DbApiKeyStore = null;
async function getDbStoreClass() {
    if (!DbApiKeyStore) {
        const module = await import('./db-store.js');
        DbApiKeyStore = module.DbApiKeyStore;
    }
    return DbApiKeyStore;
}
const logger = createLogger({ component: 'api-key-store' });
// =============================================================================
// IN-MEMORY STORE IMPLEMENTATION
// =============================================================================
/**
 * In-memory implementation of API key store.
 *
 * NOTE: This is suitable for development and testing only.
 * Production deployments should use Redis or a database.
 */
export class InMemoryApiKeyStore {
    /** API keys indexed by ID */
    keysById = new Map();
    /** API keys indexed by prefix for fast lookup */
    keysByPrefix = new Map();
    /** Rate limit states indexed by key ID */
    rateLimitStates = new Map();
    /** Cleanup interval handle */
    cleanupInterval = null;
    constructor() {
        this.startCleanup();
    }
    /**
     * Create a new API key
     */
    async create(apiKey) {
        // Check for duplicate prefix
        if (this.keysByPrefix.has(apiKey.prefix)) {
            throw new Error(`API key with prefix ${apiKey.prefix} already exists`);
        }
        this.keysById.set(apiKey.id, apiKey);
        this.keysByPrefix.set(apiKey.prefix, apiKey);
        logger.info({ keyId: apiKey.id, prefix: apiKey.prefix, tenantId: apiKey.tenantId }, 'API key created in store');
        return apiKey;
    }
    /**
     * Get an API key by ID
     */
    async getById(id) {
        return this.keysById.get(id) ?? null;
    }
    /**
     * Get an API key by prefix (first 8 chars)
     * This is the primary lookup method during validation
     */
    async getByPrefix(prefix) {
        return this.keysByPrefix.get(prefix) ?? null;
    }
    /**
     * Update an API key
     */
    async update(id, updates) {
        const existing = this.keysById.get(id);
        if (!existing) {
            return null;
        }
        // Merge updates
        const updated = {
            ...existing,
            ...updates,
            // Prevent modifying immutable fields
            id: existing.id,
            prefix: existing.prefix,
            hashedKey: existing.hashedKey,
            tenantId: existing.tenantId,
            createdAt: existing.createdAt,
            createdBy: existing.createdBy,
        };
        this.keysById.set(id, updated);
        this.keysByPrefix.set(updated.prefix, updated);
        logger.info({ keyId: id }, 'API key updated in store');
        return updated;
    }
    /**
     * Delete an API key
     */
    async delete(id) {
        const existing = this.keysById.get(id);
        if (!existing) {
            return false;
        }
        this.keysById.delete(id);
        this.keysByPrefix.delete(existing.prefix);
        this.rateLimitStates.delete(id);
        logger.info({ keyId: id, prefix: existing.prefix }, 'API key deleted from store');
        return true;
    }
    /**
     * List API keys with filters
     */
    async list(filters) {
        let keys = Array.from(this.keysById.values());
        // Apply filters
        keys = keys.filter((key) => {
            // Tenant filter (required)
            if (key.tenantId !== filters.tenantId) {
                return false;
            }
            // Status filter
            if (filters.status && key.status !== filters.status) {
                return false;
            }
            // Scope filter
            if (filters.scope && !key.scopes.includes(filters.scope)) {
                return false;
            }
            // Created by filter
            if (filters.createdBy && key.createdBy !== filters.createdBy) {
                return false;
            }
            return true;
        });
        // Sort by creation date descending (newest first)
        keys.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
        const total = keys.length;
        // Apply pagination
        const offset = filters.offset ?? 0;
        const limit = filters.limit ?? 50;
        keys = keys.slice(offset, offset + limit);
        return { keys, total };
    }
    /**
     * Get rate limit state for a key
     */
    async getRateLimitState(keyId) {
        return this.rateLimitStates.get(keyId) ?? null;
    }
    /**
     * Set rate limit state for a key
     */
    async setRateLimitState(state) {
        this.rateLimitStates.set(state.keyId, state);
    }
    /**
     * Update last used timestamp
     */
    async updateLastUsed(id) {
        const existing = this.keysById.get(id);
        if (existing) {
            const updated = { ...existing, lastUsedAt: new Date() };
            this.keysById.set(id, updated);
            this.keysByPrefix.set(updated.prefix, updated);
        }
    }
    /**
     * Reset the store (for testing)
     */
    reset() {
        this.keysById.clear();
        this.keysByPrefix.clear();
        this.rateLimitStates.clear();
        logger.info('API key store reset');
    }
    /**
     * Start periodic cleanup of stale rate limit states
     */
    startCleanup() {
        // Clean up every 5 minutes
        this.cleanupInterval = setInterval(() => {
            const now = Date.now();
            const staleThreshold = now - 3600000; // 1 hour
            for (const [keyId, state] of this.rateLimitStates.entries()) {
                // Remove if all windows have been expired for over an hour
                if (state.second.resetAt < staleThreshold &&
                    state.minute.resetAt < staleThreshold &&
                    state.hour.resetAt < staleThreshold) {
                    this.rateLimitStates.delete(keyId);
                }
            }
        }, 300000);
        // Don't prevent process exit
        this.cleanupInterval.unref();
    }
    /**
     * Stop the cleanup interval
     */
    stop() {
        if (this.cleanupInterval) {
            clearInterval(this.cleanupInterval);
            this.cleanupInterval = null;
        }
    }
    /**
     * Get store statistics
     */
    getStats() {
        const byStatus = {
            active: 0,
            revoked: 0,
            expired: 0,
        };
        const byTenant = {};
        for (const key of this.keysById.values()) {
            byStatus[key.status] = (byStatus[key.status] ?? 0) + 1;
            byTenant[key.tenantId] = (byTenant[key.tenantId] ?? 0) + 1;
        }
        return {
            totalKeys: this.keysById.size,
            byStatus,
            byTenant,
            rateLimitStates: this.rateLimitStates.size,
        };
    }
}
// =============================================================================
// SINGLETON
// =============================================================================
let store = null;
/**
 * Get the API key store singleton
 */
export function getApiKeyStore() {
    if (!store) {
        store = new InMemoryApiKeyStore();
        logger.info('API key store initialized');
    }
    return store;
}
/**
 * Create a new API key store instance (for testing)
 */
export function createApiKeyStore() {
    return new InMemoryApiKeyStore();
}
/**
 * Reset the API key store singleton (for testing)
 */
export function resetApiKeyStore() {
    if (store) {
        store.stop();
        store.reset();
        store = null;
    }
    if (apiKeyStoreInstance) {
        apiKeyStoreInstance.reset();
        apiKeyStoreInstance = null;
    }
    logger.info('API key store singleton reset');
}
/** Singleton instance for factory-created store */
let apiKeyStoreInstance = null;
/**
 * Determine the store type based on environment configuration
 */
export function getStoreType() {
    // Check explicit environment variable
    const envStoreType = process.env['VORION_API_KEY_STORE'];
    if (envStoreType === 'database' || envStoreType === 'memory') {
        return envStoreType;
    }
    // Default based on environment
    const env = process.env['VORION_ENV'] ?? process.env['NODE_ENV'] ?? 'development';
    if (env === 'production' || env === 'staging') {
        return 'database';
    }
    return 'memory';
}
/**
 * Factory function to create the appropriate API key store.
 *
 * In production/staging environments, this returns a database-backed store.
 * In development/test environments, this returns an in-memory store.
 *
 * The store type can be explicitly set via VORION_API_KEY_STORE environment variable:
 * - 'database': Use PostgreSQL database
 * - 'memory': Use in-memory store
 *
 * @param options - Configuration options
 * @returns Promise resolving to an API key store instance
 *
 * @example
 * ```typescript
 * // Get default store based on environment
 * const store = await createApiKeyStoreFactory();
 *
 * // Force database store with Redis for rate limiting
 * const store = await createApiKeyStoreFactory({
 *   storeType: 'database',
 *   redis: redisClient,
 * });
 *
 * // Force in-memory store for testing
 * const store = await createApiKeyStoreFactory({
 *   storeType: 'memory',
 * });
 * ```
 */
export async function createApiKeyStoreFactory(options) {
    const storeType = options?.storeType ?? getStoreType();
    if (storeType === 'database') {
        const DbStore = await getDbStoreClass();
        const dbStore = new DbStore({
            database: options?.database,
            redis: options?.redis,
        });
        logger.info('Created database-backed API key store');
        return dbStore;
    }
    const memoryStore = new InMemoryApiKeyStore();
    logger.info('Created in-memory API key store');
    return memoryStore;
}
/**
 * Get the API key store singleton using the factory pattern.
 *
 * This is the recommended way to get the API key store in production code.
 * It automatically selects the appropriate store type based on environment.
 *
 * @param options - Configuration options (only used on first call)
 * @returns Promise resolving to the API key store singleton
 */
export async function getApiKeyStoreFactory(options) {
    if (!apiKeyStoreInstance) {
        apiKeyStoreInstance = await createApiKeyStoreFactory(options);
    }
    return apiKeyStoreInstance;
}
/**
 * Reset the factory-created API key store singleton (for testing)
 */
export function resetApiKeyStoreFactory() {
    if (apiKeyStoreInstance) {
        apiKeyStoreInstance.reset();
        apiKeyStoreInstance = null;
    }
    logger.info('API key store factory singleton reset');
}
//# sourceMappingURL=store.js.map