import { decodeCursor, encodeCursor } from '../../../storage/core/storageValidation.js';
import { configurationError, JsonRpcErrorCode, McpError } from '../../../types-global/errors.js';
import { ErrorHandler } from '../../../utils/internal/error-handler/errorHandler.js';
import { logger } from '../../../utils/internal/logger.js';
const DEFAULT_LIST_LIMIT = 1000;
export class KvProvider {
    kv;
    constructor(kv) {
        if (!kv) {
            throw configurationError('KvProvider requires a valid KVNamespace instance.');
        }
        this.kv = kv;
    }
    getKvKey(tenantId, key) {
        return `${tenantId}:${key}`;
    }
    async get(tenantId, key, context) {
        const kvKey = this.getKvKey(tenantId, key);
        return await ErrorHandler.tryCatch(async () => {
            logger.debug(`[KvProvider] Getting key: ${kvKey}`, context);
            try {
                const result = await this.kv.get(kvKey, 'json');
                return result; // null indicates not found
            }
            catch (error) {
                throw new McpError(JsonRpcErrorCode.SerializationError, `[KvProvider] Failed to parse JSON for key: ${kvKey}`, { ...context, error });
            }
        }, {
            operation: 'KvProvider.get',
            context,
            input: { tenantId, key },
        });
    }
    async set(tenantId, key, value, context, options) {
        const kvKey = this.getKvKey(tenantId, key);
        return await ErrorHandler.tryCatch(async () => {
            logger.debug(`[KvProvider] Setting key: ${kvKey}`, {
                ...context,
                options,
            });
            const valueToStore = JSON.stringify(value);
            const putOptions = {};
            if (options?.ttl !== undefined) {
                // Cloudflare KV requires expirationTtl >= 60 seconds
                putOptions.expirationTtl = Math.max(options.ttl, 60);
            }
            await this.kv.put(kvKey, valueToStore, putOptions);
            logger.debug(`[KvProvider] Successfully set key: ${kvKey}`, context);
        }, {
            operation: 'KvProvider.set',
            context,
            input: { tenantId, key },
        });
    }
    /**
     * Deletes a key from the KV namespace.
     *
     * Note: Cloudflare KV `delete()` is idempotent and does not report whether
     * the key existed. This method always returns `true` on success, which is a
     * backend limitation — the KV API provides no existence check on delete.
     * A pre-delete `get()` would add latency and still be racy under eventual
     * consistency, so we accept the imprecise return value.
     */
    async delete(tenantId, key, context) {
        const kvKey = this.getKvKey(tenantId, key);
        return await ErrorHandler.tryCatch(async () => {
            logger.debug(`[KvProvider] Deleting key: ${kvKey}`, context);
            // KV delete is idempotent — always succeeds regardless of key existence
            await this.kv.delete(kvKey);
            logger.debug(`[KvProvider] Successfully deleted key: ${kvKey}`, context);
            return true;
        }, {
            operation: 'KvProvider.delete',
            context,
            input: { tenantId, key },
        });
    }
    async list(tenantId, prefix, context, options) {
        const kvPrefix = this.getKvKey(tenantId, prefix);
        return await ErrorHandler.tryCatch(async () => {
            logger.debug(`[KvProvider] Listing keys with prefix: ${kvPrefix}`, {
                ...context,
                options,
            });
            const limit = options?.limit ?? DEFAULT_LIST_LIMIT;
            const listOptions = {
                prefix: kvPrefix,
                limit,
            };
            if (options?.cursor) {
                // Decode tenant-bound cursor to get the native KV cursor
                listOptions.cursor = decodeCursor(options.cursor, tenantId, context);
            }
            const listed = await this.kv.list(listOptions);
            const tenantPrefix = `${tenantId}:`;
            const keys = listed.keys.map((keyInfo) => keyInfo.name.startsWith(tenantPrefix)
                ? keyInfo.name.substring(tenantPrefix.length)
                : keyInfo.name);
            // Wrap native KV cursor in tenant-bound envelope
            const nativeCursor = 'cursor' in listed && !listed.list_complete ? listed.cursor : undefined;
            const nextCursor = nativeCursor ? encodeCursor(nativeCursor, tenantId) : undefined;
            logger.debug(`[KvProvider] Found ${keys.length} keys with prefix: ${kvPrefix}`, context);
            return {
                keys,
                nextCursor,
            };
        }, {
            operation: 'KvProvider.list',
            context,
            input: { tenantId, prefix, options },
        });
    }
    async getMany(tenantId, keys, context) {
        return await ErrorHandler.tryCatch(async () => {
            if (keys.length === 0) {
                return new Map();
            }
            const entries = await Promise.all(keys.map(async (key) => {
                const value = await this.get(tenantId, key, context);
                return [key, value];
            }));
            const results = new Map();
            for (const [key, value] of entries) {
                if (value !== null) {
                    results.set(key, value);
                }
            }
            return results;
        }, {
            operation: 'KvProvider.getMany',
            context,
            input: { tenantId, keyCount: keys.length },
        });
    }
    async setMany(tenantId, entries, context, options) {
        return await ErrorHandler.tryCatch(async () => {
            if (entries.size === 0) {
                return;
            }
            const promises = Array.from(entries.entries()).map(([key, value]) => this.set(tenantId, key, value, context, options));
            await Promise.all(promises);
        }, {
            operation: 'KvProvider.setMany',
            context,
            input: { tenantId, entryCount: entries.size },
        });
    }
    async deleteMany(tenantId, keys, context) {
        return await ErrorHandler.tryCatch(async () => {
            if (keys.length === 0) {
                return 0;
            }
            const promises = keys.map((key) => this.delete(tenantId, key, context));
            const results = await Promise.all(promises);
            return results.filter((deleted) => deleted).length;
        }, {
            operation: 'KvProvider.deleteMany',
            context,
            input: { tenantId, keyCount: keys.length },
        });
    }
    async clear(tenantId, context) {
        return await ErrorHandler.tryCatch(async () => {
            const kvPrefix = `${tenantId}:`;
            let deletedCount = 0;
            let cursor;
            let listComplete = false;
            while (!listComplete) {
                const listOpts = {
                    prefix: kvPrefix,
                    limit: 1000,
                };
                if (cursor) {
                    listOpts.cursor = cursor;
                }
                const listed = await this.kv.list(listOpts);
                const deletePromises = listed.keys.map((keyInfo) => this.kv.delete(keyInfo.name));
                await Promise.all(deletePromises);
                deletedCount += listed.keys.length;
                listComplete = listed.list_complete;
                cursor = 'cursor' in listed ? listed.cursor : undefined;
            }
            logger.info(`[KvProvider] Cleared ${deletedCount} keys for tenant: ${tenantId}`, context);
            return deletedCount;
        }, {
            operation: 'KvProvider.clear',
            context,
            input: { tenantId },
        });
    }
}
//# sourceMappingURL=kvProvider.js.map