"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setOAuthConfig = setOAuthConfig;
const express_1 = require("express");
const auth_1 = require("../modules/auth");
const logger_1 = require("../lib/logger");
const oauth_1 = require("../services/oauth");
const user_1 = require("../services/user");
const router = (0, express_1.Router)();
// ── OAuth startup config (set by initializeOAuth) ────────────────────────────
let _oauthConfig = {
    autoProvision: true,
    defaultRoleType: 'member',
    baseUrl: '',
};
function setOAuthConfig(cfg) {
    if (cfg.autoProvision !== undefined)
        _oauthConfig.autoProvision = cfg.autoProvision;
    if (cfg.defaultRoleType)
        _oauthConfig.defaultRoleType = cfg.defaultRoleType;
    if (cfg.baseUrl)
        _oauthConfig.baseUrl = cfg.baseUrl;
}
/**
 * GET /api/auth/oauth/providers
 * List configured OAuth providers (for login page buttons).
 */
router.get('/providers', (_req, res) => {
    res.json((0, oauth_1.listProviders)());
});
/**
 * GET /api/auth/oauth/connections
 * List all OAuth connections for the authenticated user.
 * Must be registered BEFORE /:provider to avoid catch-all match.
 */
router.get('/connections', auth_1.requireAuth, async (req, res) => {
    const userId = req.auth?.userId;
    if (!userId) {
        res.status(401).json({ error: 'Unauthorized' });
        return;
    }
    try {
        const connections = await (0, oauth_1.listOAuthConnections)(userId);
        res.json({ connections });
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
});
/**
 * GET /api/auth/oauth/:provider
 * Initiate the OAuth flow — redirects the browser to the provider.
 */
router.get('/:provider', async (req, res) => {
    const { provider } = req.params;
    const handler = (0, oauth_1.getProvider)(provider);
    if (!handler) {
        res.status(404).json({ error: `OAuth provider "${provider}" not configured` });
        return;
    }
    const returnTo = req.query.returnTo || '/';
    const { state, codeVerifier } = (0, oauth_1.createOAuthState)(provider, returnTo);
    // Compute redirect URI dynamically if not set in provider config
    const baseUrl = _oauthConfig.baseUrl || `${req.protocol}://${req.get('host')}`;
    if (!handler.config.redirectUri) {
        handler.config.redirectUri = `${baseUrl}/api/auth/oauth/${provider}/callback`;
    }
    const url = await handler.createAuthorizationURL(state, codeVerifier);
    res.redirect(url.toString());
});
/**
 * GET /api/auth/oauth/:provider/callback
 * Handle the OAuth callback — exchange code, provision user, issue JWT.
 */
router.get('/:provider/callback', async (req, res) => {
    const { provider } = req.params;
    const { code, state } = req.query;
    if (!code || !state) {
        res.status(400).json({ error: 'Missing code or state parameter' });
        return;
    }
    // Validate state (CSRF protection)
    const oauthState = (0, oauth_1.consumeOAuthState)(state);
    if (!oauthState || oauthState.provider !== provider) {
        res.status(400).json({ error: 'Invalid or expired OAuth state' });
        return;
    }
    const handler = (0, oauth_1.getProvider)(provider);
    if (!handler) {
        res.status(404).json({ error: `OAuth provider "${provider}" not configured` });
        return;
    }
    try {
        // Exchange authorization code for tokens
        const tokens = await handler.validateAuthorizationCode(code, oauthState.codeVerifier);
        // Fetch user info from the provider
        const userInfo = await handler.fetchUserInfo(tokens.accessToken);
        // ── Credential-only providers (e.g., Anthropic) ──────────────────────
        // These providers store API keys for an already-authenticated user.
        // They don't create users or issue JWTs — the user must be logged in.
        if (oauthState.connectOnly) {
            const userId = oauthState.userId;
            if (!userId) {
                res.status(401).json({ error: 'Authentication required to connect a credential provider' });
                return;
            }
            const label = oauthState.label || 'default';
            await (0, oauth_1.upsertOAuthToken)(userId, provider, tokens, handler.config.scopes, userInfo.providerUserId, userInfo.email, { display_name: userInfo.displayName, ...userInfo.raw }, label);
            logger_1.loggerRegistry.info(`[oauth] stored ${provider} credentials (label: ${label}) for user ${userId}`);
            const returnTo = oauthState.returnTo || '/';
            res.redirect(`${returnTo}${returnTo.includes('?') ? '&' : '?'}connected=${provider}&label=${encodeURIComponent(label)}`);
            return;
        }
        // ── Identity providers (Google, GitHub, Microsoft, etc.) ─────────────
        // Find or create the user
        let user = await (0, oauth_1.getUserByOAuthProvider)(provider, userInfo.providerUserId);
        if (!user && userInfo.email) {
            // Try matching by email
            user = await (0, user_1.getUserByEmail)(userInfo.email);
        }
        if (!user) {
            if (!_oauthConfig.autoProvision) {
                res.status(403).json({ error: 'No account found. Contact an administrator.' });
                return;
            }
            // Auto-provision new user
            user = await (0, user_1.createUser)({
                external_id: `${provider}:${userInfo.providerUserId}`,
                email: userInfo.email || undefined,
                display_name: userInfo.displayName || undefined,
                oauth_provider: provider,
                oauth_provider_id: userInfo.providerUserId,
                roles: [{ role: _oauthConfig.defaultRoleType, type: _oauthConfig.defaultRoleType }],
            });
            logger_1.loggerRegistry.info(`[oauth] auto-provisioned user ${user.id} from ${provider}`);
        }
        // Store the OAuth tokens (encrypted)
        await (0, oauth_1.upsertOAuthToken)(user.id, provider, tokens, handler.config.scopes, userInfo.providerUserId, userInfo.email, { avatar: userInfo.raw?.picture || userInfo.raw?.avatar_url });
        // Determine highest role type
        const roles = user.roles || [];
        const highestType = roles.some((r) => r.type === 'superadmin')
            ? 'superadmin'
            : roles.some((r) => r.type === 'admin')
                ? 'admin'
                : 'member';
        // Issue JWT (same as password login)
        const jwt = (0, auth_1.signToken)({
            userId: user.id,
            role: highestType,
            roles: roles.map((r) => ({ role: r.role, type: r.type })),
        }, '24h');
        // Redirect to login page with token + user info — plays the comet animation, then navigates
        const returnTo = oauthState.returnTo || '/';
        const displayName = user.display_name || userInfo.displayName || '';
        const userName = user.external_id || '';
        res.redirect(`/login?token=${encodeURIComponent(jwt)}` +
            `&returnTo=${encodeURIComponent(returnTo)}` +
            `&displayName=${encodeURIComponent(displayName)}` +
            `&username=${encodeURIComponent(userName)}`);
    }
    catch (err) {
        logger_1.loggerRegistry.error(`[oauth] callback error for ${provider}: ${err.message}`);
        res.redirect(`/login?error=${encodeURIComponent('OAuth login failed. Please try again.')}`);
    }
});
/**
 * GET /api/auth/oauth/connect/:provider
 * Initiate a credential-only connection flow for an already-authenticated user.
 * Unlike the login flow (GET /:provider), this stores the provider's token
 * against the current user without issuing a new JWT or creating a user.
 * Used for resource providers like Anthropic (API key storage).
 */
// Bridge for browser redirects: accept token from query param since browsers
// can't set Authorization headers during navigation.
router.get('/connect/:provider', (req, res, next) => {
    const token = req.query.token;
    if (token && !req.headers.authorization) {
        req.headers.authorization = `Bearer ${token}`;
    }
    next();
}, auth_1.requireAuth, async (req, res) => {
    const userId = req.auth.userId;
    const provider = req.params.provider;
    const handler = (0, oauth_1.getProvider)(provider);
    if (!handler) {
        res.status(404).json({ error: `OAuth provider "${provider}" not configured` });
        return;
    }
    const returnTo = req.query.returnTo || '/';
    const label = req.query.label || 'default';
    const { state, codeVerifier } = (0, oauth_1.createOAuthState)(provider, returnTo, {
        connectOnly: true,
        userId,
        label,
    });
    const baseUrl = _oauthConfig.baseUrl || `${req.protocol}://${req.get('host')}`;
    if (!handler.config.redirectUri) {
        handler.config.redirectUri = `${baseUrl}/api/auth/oauth/${provider}/callback`;
    }
    const url = await handler.createAuthorizationURL(state, codeVerifier);
    res.redirect(url.toString());
});
/**
 * DELETE /api/auth/oauth/connections/:provider
 * Revoke a stored OAuth connection (requires auth — applied by parent router).
 */
router.delete('/connections/:provider', async (req, res) => {
    const userId = req.auth?.userId;
    if (!userId) {
        res.status(401).json({ error: 'Unauthorized' });
        return;
    }
    const label = req.query.label || undefined;
    const deleted = await (0, oauth_1.deleteOAuthConnection)(userId, req.params.provider, label);
    res.json({ deleted });
});
exports.default = router;
