/**
 * API Validator - Backend skill validation with authentication
 *
 * Tests API endpoints using curl/fetch, handles various auth strategies,
 * and generates Postman collections.
 *
 * @since v1.0.130
 */
import * as fs from '../../utils/fs-native.js';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import chalk from 'chalk';
const execAsync = promisify(exec);
/**
 * API Validator
 *
 * Validates backend APIs with authentication support
 */
export class ApiValidator {
    constructor(config, options) {
        this.config = {
            timeout_ms: 30000,
            auth_prefix: 'Bearer ',
            ...config,
        };
        this.verbose = options?.verbose ?? false;
    }
    /**
     * Run full API validation
     */
    async validate() {
        const startTime = Date.now();
        const results = [];
        // 1. Check if server is running
        const serverRunning = await this.checkServerRunning();
        if (!serverRunning) {
            return {
                passed: false,
                server_running: false,
                auth_valid: false,
                endpoints: [],
                duration_ms: Date.now() - startTime,
                summary: '❌ Server not running at ' + this.config.base_url,
            };
        }
        // 2. Get auth token if needed
        let authValid = true;
        if (this.config.auth_strategy !== 'none') {
            authValid = await this.setupAuth();
            if (!authValid) {
                return {
                    passed: false,
                    server_running: true,
                    auth_valid: false,
                    endpoints: [],
                    duration_ms: Date.now() - startTime,
                    summary: '❌ Authentication failed - check ' + (this.config.auth_env_var || 'auth config'),
                };
            }
        }
        // 3. Test each endpoint
        for (const endpoint of this.config.endpoints_to_test) {
            const result = await this.testEndpoint(endpoint);
            results.push(result);
        }
        const passed = results.every(r => r.passed);
        const passedCount = results.filter(r => r.passed).length;
        return {
            passed,
            server_running: true,
            auth_valid: authValid,
            endpoints: results,
            duration_ms: Date.now() - startTime,
            summary: passed
                ? `✅ API validation PASSED: ${passedCount}/${results.length} endpoints`
                : `❌ API validation FAILED: ${passedCount}/${results.length} endpoints passed`,
        };
    }
    /**
     * Check if server is running via health check
     */
    async checkServerRunning() {
        try {
            const url = this.config.base_url + this.config.health_check;
            const response = await this.makeRequest('GET', url, {}, null, false);
            return response.status >= 200 && response.status < 500;
        }
        catch {
            return false;
        }
    }
    /**
     * Setup authentication based on strategy
     */
    async setupAuth() {
        switch (this.config.auth_strategy) {
            case 'jwt':
                return this.setupJwtAuth();
            case 'api_key':
                return this.setupApiKeyAuth();
            case 'basic':
                return this.setupBasicAuth();
            case 'oauth2':
                return this.setupOAuth2Auth();
            default:
                return true;
        }
    }
    /**
     * Setup JWT authentication from env var
     */
    async setupJwtAuth() {
        const envVar = this.config.auth_env_var || 'API_TEST_TOKEN';
        this.authToken = process.env[envVar];
        if (!this.authToken) {
            // Try to read from .env file
            try {
                const envPath = path.join(process.cwd(), '.env');
                if (fs.existsSync(envPath)) {
                    const envContent = fs.readFileSync(envPath, 'utf-8');
                    const match = envContent.match(new RegExp(`^${envVar}=(.+)$`, 'm'));
                    if (match) {
                        this.authToken = match[1].replace(/["']/g, '');
                    }
                }
            }
            catch {
                // Ignore
            }
        }
        return !!this.authToken;
    }
    /**
     * Setup API key authentication
     */
    async setupApiKeyAuth() {
        const envVar = this.config.auth_env_var || 'API_KEY';
        this.authToken = process.env[envVar];
        if (!this.authToken) {
            try {
                const envPath = path.join(process.cwd(), '.env');
                if (fs.existsSync(envPath)) {
                    const envContent = fs.readFileSync(envPath, 'utf-8');
                    const match = envContent.match(new RegExp(`^${envVar}=(.+)$`, 'm'));
                    if (match) {
                        this.authToken = match[1].replace(/["']/g, '');
                    }
                }
            }
            catch {
                // Ignore
            }
        }
        return !!this.authToken;
    }
    /**
     * Setup Basic authentication
     */
    async setupBasicAuth() {
        const userEnv = this.config.auth_env_var || 'API_USER';
        const passEnv = 'API_PASSWORD';
        const user = process.env[userEnv];
        const pass = process.env[passEnv];
        if (user && pass) {
            this.authToken = Buffer.from(`${user}:${pass}`).toString('base64');
            return true;
        }
        return false;
    }
    /**
     * Setup OAuth2 authentication (simplified)
     */
    async setupOAuth2Auth() {
        // For OAuth2, expect token to be pre-obtained and stored in env
        const envVar = this.config.auth_env_var || 'OAUTH2_TOKEN';
        this.authToken = process.env[envVar];
        return !!this.authToken;
    }
    /**
     * Test a single endpoint
     */
    async testEndpoint(endpoint) {
        const startTime = Date.now();
        const url = this.config.base_url + endpoint.path;
        try {
            // Prepare body
            let body = null;
            if (endpoint.body) {
                body = JSON.stringify(endpoint.body);
            }
            else if (endpoint.body_file) {
                const bodyPath = path.join(process.cwd(), endpoint.body_file);
                if (fs.existsSync(bodyPath)) {
                    body = fs.readFileSync(bodyPath, 'utf-8');
                }
            }
            // Make request
            const response = await this.makeRequest(endpoint.method, url, endpoint.headers || {}, body, true);
            const passed = response.status === endpoint.expected_status;
            return {
                endpoint: endpoint.path,
                method: endpoint.method,
                passed,
                status_code: response.status,
                expected_status: endpoint.expected_status,
                duration_ms: Date.now() - startTime,
                response_body: response.body?.slice(0, 500),
            };
        }
        catch (error) {
            const err = error;
            return {
                endpoint: endpoint.path,
                method: endpoint.method,
                passed: false,
                expected_status: endpoint.expected_status,
                duration_ms: Date.now() - startTime,
                error: err.message || 'Request failed',
            };
        }
    }
    /**
     * Make HTTP request using curl
     */
    async makeRequest(method, url, headers, body, useAuth) {
        const curlArgs = [
            '-s', // Silent
            '-w', '\\n%{http_code}', // Append status code
            '-X', method,
            '--max-time', String(Math.floor((this.config.timeout_ms || 30000) / 1000)),
        ];
        // Add auth header
        if (useAuth && this.authToken) {
            const authHeader = this.config.auth_header || 'Authorization';
            let authValue;
            switch (this.config.auth_strategy) {
                case 'jwt':
                    authValue = `${this.config.auth_prefix || 'Bearer '}${this.authToken}`;
                    break;
                case 'api_key':
                    authValue = this.authToken;
                    break;
                case 'basic':
                    authValue = `Basic ${this.authToken}`;
                    break;
                default:
                    authValue = this.authToken;
            }
            curlArgs.push('-H', `${authHeader}: ${authValue}`);
        }
        // Add custom headers
        for (const [key, value] of Object.entries(headers)) {
            curlArgs.push('-H', `${key}: ${value}`);
        }
        // Add body for POST/PUT/PATCH
        if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
            curlArgs.push('-H', 'Content-Type: application/json');
            curlArgs.push('-d', body);
        }
        curlArgs.push(url);
        const command = `curl ${curlArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`;
        if (this.verbose) {
            console.log(chalk.gray(`Running: ${command}`));
        }
        const { stdout } = await execAsync(command, {
            timeout: (this.config.timeout_ms || 30000) + 5000,
        });
        // Parse response - status code is on last line
        const lines = stdout.trim().split('\n');
        const statusCode = parseInt(lines[lines.length - 1], 10);
        const responseBody = lines.slice(0, -1).join('\n');
        return {
            status: statusCode,
            body: responseBody,
        };
    }
    /**
     * Generate Postman collection from endpoint tests
     */
    generatePostmanCollection(name) {
        const collection = {
            info: {
                name,
                schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
                description: `API tests generated by SpecWeave for ${name}`,
            },
            item: [],
            variable: [
                { key: 'baseUrl', value: this.config.base_url },
            ],
        };
        // Add auth based on strategy
        if (this.config.auth_strategy !== 'none') {
            switch (this.config.auth_strategy) {
                case 'jwt':
                    collection.auth = {
                        type: 'bearer',
                        bearer: [{ key: 'token', value: '{{authToken}}' }],
                    };
                    collection.variable?.push({ key: 'authToken', value: '' });
                    break;
                case 'api_key':
                    collection.auth = {
                        type: 'apikey',
                        apikey: [
                            { key: 'key', value: this.config.auth_header || 'X-API-Key', in: 'header' },
                            { key: 'value', value: '{{apiKey}}', in: 'header' },
                        ],
                    };
                    collection.variable?.push({ key: 'apiKey', value: '' });
                    break;
                case 'basic':
                    collection.auth = {
                        type: 'basic',
                        basic: [
                            { key: 'username', value: '{{username}}' },
                            { key: 'password', value: '{{password}}' },
                        ],
                    };
                    collection.variable?.push({ key: 'username', value: '' });
                    collection.variable?.push({ key: 'password', value: '' });
                    break;
            }
        }
        // Add endpoints
        for (const endpoint of this.config.endpoints_to_test) {
            const item = {
                name: endpoint.description || `${endpoint.method} ${endpoint.path}`,
                request: {
                    method: endpoint.method,
                    url: {
                        raw: `{{baseUrl}}${endpoint.path}`,
                    },
                    header: endpoint.headers
                        ? Object.entries(endpoint.headers).map(([key, value]) => ({ key, value }))
                        : undefined,
                },
            };
            // Add body if present
            if (endpoint.body || endpoint.body_file) {
                item.request.body = {
                    mode: 'raw',
                    raw: endpoint.body ? JSON.stringify(endpoint.body, null, 2) : '{}',
                };
            }
            collection.item.push(item);
        }
        return collection;
    }
    /**
     * Save Postman collection to file
     * Also generates environment file automatically
     */
    async savePostmanCollection(outputPath, name) {
        const collection = this.generatePostmanCollection(name);
        fs.writeFileSync(outputPath, JSON.stringify(collection, null, 2));
        // Also generate environment file
        const envPath = outputPath.replace('.json', '.environment.json');
        const env = this.generatePostmanEnvironment(name);
        fs.writeFileSync(envPath, JSON.stringify(env, null, 2));
    }
    /**
     * Generate Postman environment file
     */
    generatePostmanEnvironment(name) {
        const env = {
            id: `env-${Date.now()}`,
            name: `${name} Environment`,
            values: [
                { key: 'baseUrl', value: this.config.base_url, enabled: true },
            ],
            _postman_variable_scope: 'environment',
        };
        // Add auth variables (empty, user fills in)
        switch (this.config.auth_strategy) {
            case 'jwt':
                env.values.push({ key: 'authToken', value: '', enabled: true });
                break;
            case 'api_key':
                env.values.push({ key: 'apiKey', value: '', enabled: true });
                break;
            case 'basic':
                env.values.push({ key: 'username', value: '', enabled: true }, { key: 'password', value: '', enabled: true });
                break;
        }
        return env;
    }
    /**
     * Format validation result for display
     */
    formatResult(result) {
        const lines = [];
        lines.push('');
        lines.push(chalk.bold('API Validation Results'));
        lines.push('─'.repeat(50));
        lines.push(`Server: ${result.server_running ? chalk.green('✅ Running') : chalk.red('❌ Not running')}`);
        lines.push(`Auth: ${result.auth_valid ? chalk.green('✅ Valid') : chalk.red('❌ Failed')}`);
        lines.push('');
        if (result.endpoints.length > 0) {
            lines.push(chalk.bold('Endpoints:'));
            for (const ep of result.endpoints) {
                const icon = ep.passed ? '✅' : '❌';
                const status = ep.status_code
                    ? `${ep.status_code} (expected ${ep.expected_status})`
                    : `error: ${ep.error}`;
                lines.push(`  ${icon} ${ep.method} ${ep.endpoint}: ${status} (${ep.duration_ms}ms)`);
            }
        }
        lines.push('');
        lines.push('─'.repeat(50));
        lines.push(result.summary);
        return lines.join('\n');
    }
}
/**
 * Run API validation with default config detection
 */
export async function validateApi(config, options) {
    const validator = new ApiValidator(config, options);
    return validator.validate();
}
/**
 * Generate Postman collection from API config
 */
export async function generatePostmanFromConfig(config, outputPath, name) {
    const validator = new ApiValidator(config);
    await validator.savePostmanCollection(outputPath, name);
    // Also generate environment file
    const envPath = outputPath.replace('.json', '.environment.json');
    const env = validator.generatePostmanEnvironment(name);
    fs.writeFileSync(envPath, JSON.stringify(env, null, 2));
}
//# sourceMappingURL=api-validator.js.map