/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * Async Benchmark Storage
 *
 * Async wrapper around OpenSearch storage for benchmarks.
 * Maps between app's Benchmark type and OpenSearch StorageBenchmark.
 */
import { benchmarkStorage as opensearchBenchmarks } from './opensearchClient.js';
/**
 * Convert OpenSearch storage format to app Benchmark format.
 * Normalizes legacy data without version fields.
 */
function toBenchmark(stored) {
    const version = stored.currentVersion ?? stored.version ?? 1;
    return {
        id: stored.id,
        name: stored.name,
        description: stored.description,
        createdAt: stored.createdAt,
        updatedAt: stored.updatedAt ?? stored.createdAt,
        currentVersion: version,
        versions: stored.versions ?? [{
                version: 1,
                createdAt: stored.createdAt,
                testCaseIds: stored.testCaseIds || [],
            }],
        testCaseIds: stored.testCaseIds,
        runs: (stored.runs || []).map(toBenchmarkRun),
    };
}
/**
 * Convert OpenSearch run config to app BenchmarkRun format.
 * Normalizes legacy data without version tracking fields.
 */
function toBenchmarkRun(stored) {
    // Convert results with proper typing for status field
    const results = {};
    if (stored.results) {
        Object.entries(stored.results).forEach(([key, value]) => {
            results[key] = {
                reportId: value.reportId,
                status: value.status,
                ...(value.error && { error: value.error }),
                ...(value.performanceMetrics && { performanceMetrics: value.performanceMetrics }),
            };
        });
    }
    return {
        id: stored.id,
        name: stored.name,
        description: stored.description,
        createdAt: stored.createdAt,
        // Handle both current (agentKey) and legacy (agentId) field names
        agentKey: stored.agentKey || stored.agentId || '',
        modelId: stored.modelId,
        headers: stored.headers,
        benchmarkVersion: stored.benchmarkVersion ?? 1,
        testCaseSnapshots: stored.testCaseSnapshots ?? [],
        status: stored.status,
        error: stored.error,
        stats: stored.stats,
        results,
        performanceMetrics: stored.performanceMetrics,
    };
}
/**
 * Convert app Benchmark format to OpenSearch storage format
 */
function toStorageFormat(benchmark) {
    const result = {
        name: benchmark.name,
        description: benchmark.description,
        testCaseIds: benchmark.testCaseIds,
    };
    // Include versioning fields
    if (benchmark.currentVersion !== undefined) {
        result.currentVersion = benchmark.currentVersion;
    }
    if (benchmark.versions !== undefined) {
        result.versions = benchmark.versions;
    }
    if (benchmark.updatedAt !== undefined) {
        result.updatedAt = benchmark.updatedAt;
    }
    // Convert runs with version tracking fields
    // Use agentKey to match server route behavior
    if (benchmark.runs) {
        result.runs = benchmark.runs.map(run => ({
            id: run.id,
            name: run.name,
            description: run.description,
            agentKey: run.agentKey,
            modelId: run.modelId,
            headers: run.headers,
            createdAt: run.createdAt,
            benchmarkVersion: run.benchmarkVersion,
            testCaseSnapshots: run.testCaseSnapshots,
            results: run.results,
            ...(run.performanceMetrics && { performanceMetrics: run.performanceMetrics }),
        }));
    }
    return result;
}
class AsyncBenchmarkStorage {
    // ==================== Benchmark CRUD Operations ====================
    /**
     * Get all benchmarks, sorted by updatedAt descending (most recently active first)
     * @param options.includeSample - whether to include sample/demo data
     */
    async getAll(options) {
        const stored = await opensearchBenchmarks.getAll(options);
        const benchmarks = stored.map(toBenchmark);
        // Sort by updatedAt descending (most recently active first), fallback to createdAt
        return benchmarks.sort((a, b) => new Date(b.updatedAt || b.createdAt || 0).getTime() - new Date(a.updatedAt || a.createdAt || 0).getTime());
    }
    /**
     * Get a single benchmark by ID
     * @param options.fields - 'polling' for lightweight payload (excludes versions, testCaseSnapshots, headers)
     * @param options.runsSize - max number of runs to return
     * @param options.runsOffset - offset into runs array for pagination
     */
    async getById(id, options) {
        const stored = await opensearchBenchmarks.getById(id, options);
        if (!stored)
            return null;
        const benchmark = toBenchmark(stored);
        // Pass through pagination metadata from server response
        const storedAny = stored;
        if (storedAny.totalRuns !== undefined) {
            benchmark.totalRuns = storedAny.totalRuns;
            benchmark.hasMoreRuns = storedAny.hasMoreRuns;
        }
        return benchmark;
    }
    /**
     * Create a new benchmark (immutable)
     */
    async create(benchmark) {
        const storageData = toStorageFormat(benchmark);
        const created = await opensearchBenchmarks.create(storageData);
        return toBenchmark(created);
    }
    /**
     * Save a benchmark (create only - benchmarks are immutable)
     * For compatibility with existing code that calls save()
     */
    async save(benchmark) {
        // Check if exists
        const existing = await this.getById(benchmark.id);
        if (existing) {
            // Benchmarks are immutable - return existing
            console.warn('Benchmark already exists and cannot be updated:', benchmark.id);
            return existing;
        }
        const storageData = toStorageFormat(benchmark);
        // Include id for existing benchmarks being saved
        const dataWithId = {
            ...storageData,
            id: benchmark.id,
        };
        const created = await opensearchBenchmarks.create(dataWithId);
        return toBenchmark(created);
    }
    /**
     * Delete a benchmark
     */
    async delete(id) {
        const result = await opensearchBenchmarks.delete(id);
        return result.deleted;
    }
    /**
     * Get total count of benchmarks
     */
    async getCount() {
        const benchmarks = await this.getAll();
        return benchmarks.length;
    }
    // ==================== Run Operations ====================
    // Note: In OpenSearch model, runs are embedded in benchmark.
    // Actual execution results go to evals_runs index, not here.
    /**
     * Get all run configs for a benchmark
     */
    async getRuns(benchmarkId) {
        const benchmark = await this.getById(benchmarkId);
        if (!benchmark)
            return [];
        return benchmark.runs || [];
    }
    /**
     * Get a specific run config by ID from a benchmark
     */
    async getRunById(benchmarkId, runId) {
        const benchmark = await this.getById(benchmarkId);
        if (!benchmark)
            return null;
        return benchmark.runs?.find(r => r.id === runId) || null;
    }
    /**
     * Add or update a run in a benchmark
     */
    async addRun(benchmarkId, run) {
        console.log('[asyncBenchmarkStorage] addRun called', { benchmarkId, runId: run.id });
        const benchmark = await this.getById(benchmarkId);
        if (!benchmark) {
            console.error('[asyncBenchmarkStorage] Benchmark not found:', benchmarkId);
            return false;
        }
        console.log('[asyncBenchmarkStorage] Found benchmark, current runs:', benchmark.runs?.length || 0);
        const currentRuns = benchmark.runs || [];
        const existingIndex = currentRuns.findIndex(r => r.id === run.id);
        let updatedRuns;
        if (existingIndex >= 0) {
            // Update existing run
            console.log('[asyncBenchmarkStorage] Updating existing run at index:', existingIndex);
            updatedRuns = [...currentRuns];
            updatedRuns[existingIndex] = run;
        }
        else {
            // Add new run
            console.log('[asyncBenchmarkStorage] Adding new run');
            updatedRuns = [...currentRuns, run];
        }
        // Convert to storage format (preserve all version tracking fields)
        // Use agentKey to match server route behavior
        const storageRuns = updatedRuns.map(r => ({
            id: r.id,
            name: r.name,
            description: r.description,
            agentKey: r.agentKey,
            modelId: r.modelId,
            headers: r.headers,
            createdAt: r.createdAt,
            status: r.status,
            benchmarkVersion: r.benchmarkVersion,
            testCaseSnapshots: r.testCaseSnapshots,
            results: r.results,
        }));
        console.log('[asyncBenchmarkStorage] Saving updated runs:', storageRuns.length);
        await opensearchBenchmarks.update(benchmarkId, { runs: storageRuns });
        console.log('[asyncBenchmarkStorage] Runs saved successfully');
        return true;
    }
    /**
     * Delete a run config from a benchmark using atomic server-side operation.
     * This avoids the read-modify-write pattern that could corrupt other runs.
     */
    async deleteRun(benchmarkId, runId) {
        try {
            const response = await fetch(`/api/storage/benchmarks/${benchmarkId}/runs/${runId}`, {
                method: 'DELETE',
            });
            if (response.status === 404) {
                // Either benchmark or run not found
                return false;
            }
            if (!response.ok) {
                const error = await response.json();
                console.error('[asyncBenchmarkStorage] deleteRun failed:', error);
                return false;
            }
            return true;
        }
        catch (error) {
            console.error('[asyncBenchmarkStorage] deleteRun failed:', error);
            return false;
        }
    }
    // ==================== Utility Functions ====================
    /**
     * Generate a unique benchmark ID
     */
    generateBenchmarkId() {
        return `bench-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
    }
    /**
     * Generate a unique run ID
     */
    generateRunId() {
        return `run-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
    }
    /**
     * Bulk create benchmarks (for migration)
     */
    async bulkCreate(benchmarks) {
        const storageData = benchmarks.map(bench => ({
            ...toStorageFormat(bench),
            id: bench.id,
            createdAt: bench.createdAt,
        }));
        return opensearchBenchmarks.bulkCreate(storageData);
    }
    // ==================== Version Operations ====================
    /**
     * Update benchmark metadata only (name, description) - no version change
     * Uses centralized opensearchClient for consistent API handling
     */
    async updateMetadata(id, updates) {
        try {
            const result = await opensearchBenchmarks.updateMetadata(id, updates);
            return toBenchmark(result);
        }
        catch (error) {
            console.error('[asyncBenchmarkStorage] updateMetadata failed:', error);
            return null;
        }
    }
    /**
     * Update test case list - creates a new version
     * Uses centralized opensearchClient for consistent API handling
     */
    async updateTestCases(id, testCaseIds) {
        try {
            const result = await opensearchBenchmarks.update(id, { testCaseIds });
            return toBenchmark(result);
        }
        catch (error) {
            console.error('[asyncBenchmarkStorage] updateTestCases failed:', error);
            return null;
        }
    }
    /**
     * Get all versions of a benchmark
     * Uses centralized opensearchClient for consistent API handling
     */
    async getVersions(benchmarkId) {
        try {
            const result = await opensearchBenchmarks.getVersions(benchmarkId);
            return result.versions;
        }
        catch (error) {
            // Handle 404 gracefully
            if (error.message?.includes('404') || error.message?.includes('not found')) {
                return [];
            }
            console.error('[asyncBenchmarkStorage] getVersions failed:', error);
            return [];
        }
    }
    /**
     * Get a specific version of a benchmark
     * Uses centralized opensearchClient for consistent API handling
     */
    async getVersion(benchmarkId, version) {
        try {
            return await opensearchBenchmarks.getVersion(benchmarkId, version);
        }
        catch (error) {
            // Handle 404 gracefully
            if (error.message?.includes('404') || error.message?.includes('not found')) {
                return null;
            }
            console.error('[asyncBenchmarkStorage] getVersion failed:', error);
            return null;
        }
    }
    /**
     * Update a benchmark (name, description, or testCaseIds).
     * If testCaseIds changed, creates a new version.
     * Uses centralized opensearchClient for consistent API handling
     */
    async update(id, updates) {
        try {
            const result = await opensearchBenchmarks.update(id, updates);
            return toBenchmark(result);
        }
        catch (error) {
            console.error('[asyncBenchmarkStorage] update failed:', error);
            return null;
        }
    }
    // ==================== Stats Refresh Operations ====================
    /**
     * Manually refresh stats for all runs in a benchmark.
     * Useful for fixing stale stats or after manual data corrections.
     */
    async refreshAllStats(benchmarkId) {
        try {
            const response = await fetch(`/api/storage/benchmarks/${benchmarkId}/refresh-all-stats`, {
                method: 'POST',
            });
            if (!response.ok) {
                const error = await response.json();
                console.error('[asyncBenchmarkStorage] refreshAllStats failed:', error);
                return null;
            }
            return await response.json();
        }
        catch (error) {
            console.error('[asyncBenchmarkStorage] refreshAllStats failed:', error);
            return null;
        }
    }
    /**
     * Manually refresh stats for a single run in a benchmark.
     * Useful for fixing stale stats for a specific run.
     */
    async refreshRunStats(benchmarkId, runId) {
        try {
            const response = await fetch(`/api/storage/benchmarks/${benchmarkId}/runs/${runId}/refresh-stats`, {
                method: 'POST',
            });
            if (!response.ok) {
                const error = await response.json();
                console.error('[asyncBenchmarkStorage] refreshRunStats failed:', error);
                return null;
            }
            return response.json();
        }
        catch (error) {
            console.error('[asyncBenchmarkStorage] refreshRunStats failed:', error);
            return null;
        }
    }
}
// Export singleton instance
export const asyncBenchmarkStorage = new AsyncBenchmarkStorage();
// Backwards compatibility alias
/** @deprecated Use asyncBenchmarkStorage instead */
export const asyncExperimentStorage = asyncBenchmarkStorage;
//# sourceMappingURL=asyncBenchmarkStorage.js.map