"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.upsertOAuthToken = upsertOAuthToken;
exports.getOAuthToken = getOAuthToken;
exports.getFreshAccessToken = getFreshAccessToken;
exports.listOAuthConnections = listOAuthConnections;
exports.deleteOAuthConnection = deleteOAuthConnection;
exports.getUserByOAuthProvider = getUserByOAuthProvider;
const db_1 = require("../../lib/db");
const crypto_1 = require("./crypto");
const providers_1 = require("./providers");
const crud_1 = require("../user/crud");
const sql_1 = require("./sql");
// ── Database operations ──────────────────────────────────────────────────────
async function upsertOAuthToken(userId, provider, tokens, scopes, providerUserId, providerEmail, metadata, label = 'default') {
    const pool = await (0, db_1.getPool)();
    const result = await pool.query(sql_1.UPSERT_TOKEN, [
        userId,
        provider,
        label,
        (0, crypto_1.encrypt)(tokens.accessToken),
        tokens.refreshToken ? (0, crypto_1.encrypt)(tokens.refreshToken) : null,
        'bearer',
        scopes,
        tokens.accessTokenExpiresAt,
        providerUserId,
        providerEmail,
        metadata ? JSON.stringify(metadata) : null,
    ]);
    return result.rows[0].id;
}
async function getOAuthToken(userId, provider, label) {
    const pool = await (0, db_1.getPool)();
    const { rows } = label
        ? await pool.query(sql_1.GET_TOKEN, [userId, provider, label])
        : await pool.query(sql_1.GET_TOKEN_DEFAULT, [userId, provider]);
    if (rows.length === 0)
        return null;
    const row = rows[0];
    return {
        accessToken: (0, crypto_1.decrypt)(row.access_token_enc),
        refreshToken: row.refresh_token_enc ? (0, crypto_1.decrypt)(row.refresh_token_enc) : null,
        expiresAt: row.expires_at ? new Date(row.expires_at) : null,
        scopes: row.scopes || [],
        provider: row.provider,
        label: row.label,
    };
}
/**
 * Get a fresh access token, refreshing if expired.
 * Returns the access token string ready for use in API calls.
 */
async function getFreshAccessToken(userId, provider, label) {
    const token = await getOAuthToken(userId, provider, label);
    if (!token) {
        const suffix = label && label !== 'default' ? ` (label: "${label}")` : '';
        throw new Error(`No OAuth connection for provider "${provider}"${suffix} and user "${userId}"`);
    }
    // If not expired (with 60s buffer), return as-is
    if (token.expiresAt && token.expiresAt.getTime() > Date.now() + 60_000) {
        return token;
    }
    // If no expiry set (e.g., GitHub, Anthropic API keys), return as-is
    if (!token.expiresAt) {
        return token;
    }
    // Refresh the token
    if (!token.refreshToken) {
        throw new Error(`Token expired for provider "${provider}" and no refresh token available`);
    }
    const handler = (0, providers_1.getProvider)(provider);
    if (!handler) {
        throw new Error(`OAuth provider "${provider}" not configured`);
    }
    const refreshed = await handler.refreshAccessToken(token.refreshToken);
    await upsertOAuthToken(userId, provider, refreshed, token.scopes, '', // providerUserId unchanged (ON CONFLICT UPDATE keeps existing)
    null, undefined, token.label);
    return {
        accessToken: refreshed.accessToken,
        refreshToken: refreshed.refreshToken,
        expiresAt: refreshed.accessTokenExpiresAt,
        scopes: token.scopes,
        provider,
        label: token.label,
    };
}
async function listOAuthConnections(userId) {
    const pool = await (0, db_1.getPool)();
    const { rows } = await pool.query(sql_1.LIST_CONNECTIONS, [userId]);
    return rows.map((r) => ({
        provider: r.provider,
        label: r.label,
        email: r.provider_email,
        scopes: r.scopes || [],
        expires_at: r.expires_at ? new Date(r.expires_at).toISOString() : null,
        credential_type: r.metadata?.credential_type ?? null,
    }));
}
async function deleteOAuthConnection(userId, provider, label) {
    const pool = await (0, db_1.getPool)();
    const result = label
        ? await pool.query(sql_1.DELETE_TOKEN, [userId, provider, label])
        : await pool.query(sql_1.DELETE_TOKEN_DEFAULT, [userId, provider]);
    return (result.rowCount ?? 0) > 0;
}
async function getUserByOAuthProvider(provider, providerUserId) {
    const pool = await (0, db_1.getPool)();
    const { rows } = await pool.query(sql_1.GET_USER_BY_OAUTH, [provider, providerUserId]);
    if (!rows[0])
        return null;
    return (0, crud_1.attachRoles)(rows[0]);
}
