import { StoredTokenSchema } from "./types.js";
import { refreshToken, logout } from "./flow.js";
const DEFAULT_NAMESPACE = 'oauth-tokens';
/**
 * Check whether a stored token's access token has expired.
 *
 * @param token - The stored token to check
 * @returns true if the token has an expires_at timestamp that is in the past
 *
 * @example
 * ```typescript
 * const token = await storage.get('user:123');
 * if (token && isTokenExpired(token)) {
 *   // Token is expired and auto-refresh wasn't available
 * }
 * ```
 */
export function isTokenExpired(token) {
    if (!token.expires_at)
        return false;
    return Math.floor(Date.now() / 1000) >= token.expires_at;
}
/**
 * Convert an OAuth token response to a StoredToken with computed expires_at.
 */
function toStoredToken(token) {
    return {
        access_token: token.access_token,
        token_type: token.token_type,
        refresh_token: token.refresh_token,
        scope: token.scope,
        id_token: token.id_token,
        expires_at: token.expires_in ? Math.floor(Date.now() / 1000) + token.expires_in : undefined,
    };
}
/**
 * Token storage backed by Agentuity's Key-Value storage service.
 *
 * Stores tokens as JSON in a KV namespace. Supports automatic token refresh
 * on retrieval when tokens expire (if OAuth config is provided).
 *
 * @example
 * ```typescript
 * import { KeyValueTokenStorage } from '@agentuity/core/oauth';
 *
 * // Create storage with auto-refresh enabled
 * const storage = new KeyValueTokenStorage(ctx.kv, {
 *   config: { issuer: 'https://auth.example.com' },
 * });
 *
 * // Store a token after initial exchange
 * await storage.set('user:123', tokenResponse);
 *
 * // Retrieve — auto-refreshes if expired
 * const token = await storage.get('user:123');
 *
 * // Logout — revokes server-side and removes from storage
 * await storage.invalidate('user:123');
 * ```
 */
export class KeyValueTokenStorage {
    #kv;
    #namespace;
    #prefix;
    #config;
    constructor(kv, options) {
        this.#kv = kv;
        this.#namespace = options?.namespace ?? DEFAULT_NAMESPACE;
        this.#prefix = options?.prefix ?? '';
        this.#config = options?.config;
    }
    async get(key) {
        const result = await this.#kv.get(this.#namespace, this.#resolveKey(key));
        if (!result.exists)
            return null;
        const parsed = StoredTokenSchema.safeParse(result.data);
        if (!parsed.success)
            return null;
        const token = parsed.data;
        // Auto-refresh if expired and refresh_token + config are available
        if (isTokenExpired(token) && token.refresh_token && this.#config) {
            try {
                const newTokenResponse = await refreshToken(token.refresh_token, this.#config);
                const newStored = toStoredToken(newTokenResponse);
                await this.#store(key, newStored);
                return newStored;
            }
            catch {
                // Refresh failed — return the expired token, caller can check isTokenExpired()
                return token;
            }
        }
        return token;
    }
    async set(key, token) {
        const stored = toStoredToken(token);
        await this.#store(key, stored);
    }
    async invalidate(key) {
        const resolvedKey = this.#resolveKey(key);
        const result = await this.#kv.get(this.#namespace, resolvedKey);
        if (!result.exists)
            return null;
        const parsed = StoredTokenSchema.safeParse(result.data);
        const token = parsed.success ? parsed.data : null;
        // Revoke server-side (best effort)
        if (token && this.#config) {
            const tokenToRevoke = token.refresh_token ?? token.access_token;
            try {
                await logout(tokenToRevoke, this.#config);
            }
            catch {
                // Best effort — continue with storage cleanup
            }
        }
        // Remove from storage regardless of revocation result
        await this.#kv.delete(this.#namespace, resolvedKey);
        return token;
    }
    async #store(key, token) {
        // Only set explicit TTL for tokens without a refresh_token.
        // Tokens with refresh capability persist until explicitly invalidated
        // (auto-refresh on get() will keep them fresh).
        let ttl;
        if (!token.refresh_token && token.expires_at) {
            const remaining = token.expires_at - Math.floor(Date.now() / 1000);
            if (remaining > 0) {
                ttl = Math.max(remaining, 60); // KV minimum is 60 seconds
            }
        }
        await this.#kv.set(this.#namespace, this.#resolveKey(key), token, {
            ttl,
            contentType: 'application/json',
        });
    }
    #resolveKey(key) {
        return this.#prefix ? `${this.#prefix}${key}` : key;
    }
}
//# sourceMappingURL=token-storage.js.map