/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { OAuthTokenSchema, } from './types.js';
/**
 * Implementation of multi-provider token storage
 * Stores tokens securely in ~/.llxprt/oauth/ directory
 */
export class MultiProviderTokenStore {
    basePath;
    constructor(basePath) {
        this.basePath = basePath ?? join(homedir(), '.llxprt', 'oauth');
    }
    /**
     * Save an OAuth token for a specific provider
     */
    async saveToken(provider, token, bucket) {
        // Validate provider name
        if (!provider || provider.trim() === '') {
            throw new Error('Provider name cannot be empty');
        }
        // Validate bucket name if provided
        if (bucket) {
            this.validateBucketName(bucket);
        }
        // Validate token structure, but allow extra provider-specific fields (e.g., account_id for Codex)
        const validatedToken = OAuthTokenSchema.passthrough().parse(token);
        // Ensure directory exists with secure permissions
        await this.ensureDirectory();
        // Generate file paths
        const tokenPath = this.getTokenPath(provider, bucket);
        const tempPath = `${tokenPath}.tmp-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
        try {
            // Write to temporary file first (atomic operation)
            await fs.writeFile(tempPath, JSON.stringify(validatedToken, null, 2), {
                mode: 0o600,
            });
            // Set secure permissions explicitly (skip on Windows)
            if (process.platform !== 'win32') {
                await fs.chmod(tempPath, 0o600);
            }
            // Atomic rename to final location
            await fs.rename(tempPath, tokenPath);
        }
        catch (error) {
            // Cleanup temp file if it exists
            try {
                await fs.unlink(tempPath);
            }
            catch {
                // Ignore cleanup errors
            }
            throw error;
        }
    }
    /**
     * Retrieve an OAuth token for a specific provider
     */
    async getToken(provider, bucket) {
        // Validate bucket name if provided
        if (bucket) {
            this.validateBucketName(bucket);
        }
        try {
            const tokenPath = this.getTokenPath(provider, bucket);
            const content = await fs.readFile(tokenPath, 'utf8');
            const parsed = JSON.parse(content);
            // Validate token structure, but allow extra provider-specific fields (e.g., account_id for Codex)
            const validatedToken = OAuthTokenSchema.passthrough().parse(parsed);
            return validatedToken;
        }
        catch (_error) {
            // Token not found
            return null;
        }
    }
    /**
     * Remove an OAuth token for a specific provider
     */
    async removeToken(provider, bucket) {
        // Validate bucket name if provided
        if (bucket) {
            this.validateBucketName(bucket);
        }
        try {
            const tokenPath = this.getTokenPath(provider, bucket);
            await fs.unlink(tokenPath);
        }
        catch (error) {
            // Check if error is because file doesn't exist
            if (error &&
                typeof error === 'object' &&
                'code' in error &&
                error.code === 'ENOENT') {
                // File doesn't exist - operation succeeds silently
                return;
            }
            // Re-throw other errors
            throw error;
        }
    }
    /**
     * List all providers that have stored tokens
     */
    async listProviders() {
        try {
            const files = await fs.readdir(this.basePath);
            const providers = files
                .filter((file) => file.endsWith('.json'))
                .map((file) => file.replace('.json', ''))
                .sort();
            return providers;
        }
        catch (error) {
            // If directory doesn't exist, return empty array
            if (error &&
                typeof error === 'object' &&
                'code' in error &&
                error.code === 'ENOENT') {
                return [];
            }
            throw error;
        }
    }
    /**
     * List all buckets for a specific provider
     */
    async listBuckets(provider) {
        try {
            const files = await fs.readdir(this.basePath);
            const providerPrefix = `${provider}`;
            const buckets = files
                .filter((file) => file.startsWith(providerPrefix) && file.endsWith('.json'))
                .map((file) => {
                const name = file.slice(0, -5); // Remove .json extension
                if (name === provider) {
                    return 'default';
                }
                // Remove "provider-" prefix to get bucket name
                return name.slice(provider.length + 1);
            })
                .sort();
            return buckets;
        }
        catch (error) {
            // If directory doesn't exist, return empty array
            if (error &&
                typeof error === 'object' &&
                'code' in error &&
                error.code === 'ENOENT') {
                return [];
            }
            throw error;
        }
    }
    /**
     * Get usage statistics for a specific bucket
     * Returns placeholder statistics for now
     */
    async getBucketStats(provider, bucket) {
        // Validate bucket name if provided
        if (bucket) {
            this.validateBucketName(bucket);
        }
        // Check if bucket exists
        const token = await this.getToken(provider, bucket);
        if (!token) {
            return null;
        }
        // Return placeholder stats - actual implementation would track usage
        return {
            bucket,
            requestCount: 0,
            percentage: 0,
            lastUsed: undefined,
        };
    }
    /**
     * Ensure the OAuth directory exists with secure permissions
     */
    async ensureDirectory() {
        try {
            await fs.mkdir(this.basePath, { recursive: true, mode: 0o700 });
            // Ensure permissions are correct even if directory already existed (skip on Windows)
            if (process.platform !== 'win32') {
                await fs.chmod(this.basePath, 0o700);
                // Also ensure parent .llxprt directory has secure permissions
                const parentDir = join(homedir(), '.llxprt');
                await fs.chmod(parentDir, 0o700);
            }
        }
        catch (error) {
            // If chmod fails because directory doesn't exist, that's fine
            if (error &&
                typeof error === 'object' &&
                'code' in error &&
                error.code === 'ENOENT') {
                return;
            }
            throw error;
        }
    }
    /**
     * Generate secure file path for a provider token
     * Uses path.join to prevent path traversal attacks
     */
    getTokenPath(provider, bucket) {
        const bucketSuffix = bucket && bucket !== 'default' ? `-${bucket}` : '';
        return join(this.basePath, `${provider}${bucketSuffix}.json`);
    }
    /**
     * Validate bucket name for filesystem safety
     * Rejects characters that could cause issues with file paths
     */
    validateBucketName(bucket) {
        const invalidChars = /[:/\\<>"|?*]/;
        if (invalidChars.test(bucket)) {
            throw new Error(`Invalid bucket name: "${bucket}". Bucket names cannot contain: : / \\ < > " | ? *`);
        }
    }
}
//# sourceMappingURL=token-store.js.map