/**
 * @license
 * Copyright 2025 OElite Platform
 * SPDX-License-Identifier: Apache-2.0
 */
import { OAuth2Client } from 'google-auth-library';
import * as http from 'node:http';
import * as url from 'node:url';
import * as crypto from 'node:crypto';
import * as net from 'node:net';
import open from 'open';
import { promises as fs } from 'node:fs';
import * as path from 'node:path';
import os from 'node:os';
import { EventEmitter } from 'node:events';
// OAuth Client ID for Google Gemini API (from gemini-cli)
const OAUTH_CLIENT_ID = '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com';
// OAuth Client Secret (public for installed applications)
const OAUTH_CLIENT_SECRET = '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';
// OAuth Scopes for Gemini API
const OAUTH_SCOPE = [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/userinfo.email',
    'https://www.googleapis.com/auth/userinfo.profile',
];
const HTTP_REDIRECT = 301;
const SIGN_IN_SUCCESS_URL = 'https://developers.google.com/gemini-code-assist/auth_success_gemini';
const SIGN_IN_FAILURE_URL = 'https://developers.google.com/gemini-code-assist/auth_failure_gemini';
// Event emitter for OAuth flow
export const geminiOAuth2Events = new EventEmitter();
export var GeminiOAuth2Event;
(function (GeminiOAuth2Event) {
    GeminiOAuth2Event["AuthUri"] = "authUri";
    GeminiOAuth2Event["AuthProgress"] = "authProgress";
    GeminiOAuth2Event["AuthCancel"] = "authCancel";
    GeminiOAuth2Event["AuthSuccess"] = "authSuccess";
    GeminiOAuth2Event["AuthError"] = "authError";
})(GeminiOAuth2Event || (GeminiOAuth2Event = {}));
/**
 * Get the path where Gemini OAuth credentials are stored
 */
function getGeminiOAuthCredsPath() {
    const configDir = process.env['XDG_CONFIG_HOME'] ||
        path.join(os.homedir(), '.config', 'arc-cli');
    return path.join(configDir, 'gemini-oauth-creds.json');
}
/**
 * Load cached Gemini OAuth credentials from disk
 */
async function fetchCachedCredentials() {
    try {
        const filePath = getGeminiOAuthCredsPath();
        const keyFileString = await fs.readFile(filePath, 'utf-8');
        return JSON.parse(keyFileString);
    }
    catch (_error) {
        console.debug('No cached Gemini OAuth credentials found');
        return null;
    }
}
/**
 * Cache Gemini OAuth credentials to disk
 */
async function cacheCredentials(credentials) {
    const filePath = getGeminiOAuthCredsPath();
    await fs.mkdir(path.dirname(filePath), { recursive: true });
    const credString = JSON.stringify(credentials, null, 2);
    await fs.writeFile(filePath, credString, { mode: 0o600 });
    try {
        await fs.chmod(filePath, 0o600);
    }
    catch {
        // Ignore chmod errors
    }
}
/**
 * Clear cached Gemini OAuth credentials
 */
export async function clearGeminiCredentials() {
    try {
        const filePath = getGeminiOAuthCredsPath();
        await fs.rm(filePath, { force: true });
        console.log('[Gemini OAuth] Cleared cached credentials');
    }
    catch (error) {
        console.warn('[Gemini OAuth] Failed to clear credentials:', error);
    }
}
/**
 * Get an available port for the Gemini OAuth callback server
 */
async function getGeminiAvailablePort() {
    return new Promise((resolve, reject) => {
        try {
            const portStr = process.env['OAUTH_CALLBACK_PORT'];
            if (portStr) {
                const port = parseInt(portStr, 10);
                if (isNaN(port) || port <= 0 || port > 65535) {
                    return reject(new Error(`Invalid value for OAUTH_CALLBACK_PORT: "${portStr}"`));
                }
                return resolve(port);
            }
            const server = net.createServer();
            let port = 0;
            server.listen(0, () => {
                const address = server.address();
                if (address && typeof address !== 'string') {
                    port = address.port;
                }
            });
            server.on('listening', () => {
                server.close();
                server.unref();
            });
            server.on('error', (e) => reject(e));
            server.on('close', () => resolve(port));
        }
        catch (e) {
            reject(e);
        }
    });
}
/**
 * Initiate browser-based OAuth flow for Gemini
 */
export async function authWithGeminiWeb(client) {
    const port = await getGeminiAvailablePort();
    const host = process.env['OAUTH_CALLBACK_HOST'] || 'localhost';
    const redirectUri = `http://localhost:${port}/oauth2callback`;
    const state = crypto.randomBytes(32).toString('hex');
    const authUrl = client.generateAuthUrl({
        redirect_uri: redirectUri,
        access_type: 'offline',
        scope: OAUTH_SCOPE,
        state,
    });
    // Emit auth URI event for UI to display
    const authResponse = {
        verification_uri: authUrl,
        verification_uri_complete: authUrl,
        expires_in: 300, // 5 minutes
    };
    geminiOAuth2Events.emit(GeminiOAuth2Event.AuthUri, authResponse);
    // Open browser
    try {
        geminiOAuth2Events.emit(GeminiOAuth2Event.AuthProgress, 'Opening browser for authentication...');
        const childProcess = await open(authUrl);
        childProcess.on('error', (error) => {
            console.error(`Failed to open browser:`, error);
            geminiOAuth2Events.emit(GeminiOAuth2Event.AuthProgress, 'Failed to open browser. Please navigate to the URL manually.');
        });
    }
    catch (err) {
        console.error(`Failed to open browser:`, err);
        geminiOAuth2Events.emit(GeminiOAuth2Event.AuthProgress, 'Failed to open browser. Please navigate to the URL manually.');
    }
    const loginCompletePromise = new Promise((resolve, reject) => {
        const server = http.createServer(async (req, res) => {
            try {
                if (!req.url || req.url.indexOf('/oauth2callback') === -1) {
                    res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
                    res.end();
                    const error = new Error('OAuth callback not received. Unexpected request: ' + req.url);
                    geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
                    reject(error);
                    return;
                }
                const qs = new url.URL(req.url, `http://localhost:${port}`)
                    .searchParams;
                if (qs.get('error')) {
                    res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
                    res.end();
                    const errorCode = qs.get('error');
                    const errorDescription = qs.get('error_description') || 'No additional details provided';
                    const error = new Error(`Google OAuth error: ${errorCode}. ${errorDescription}`);
                    geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
                    reject(error);
                }
                else if (qs.get('state') !== state) {
                    res.end('State mismatch. Possible CSRF attack');
                    const error = new Error('OAuth state mismatch. Possible CSRF attack or browser session issue.');
                    geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
                    reject(error);
                }
                else if (qs.get('code')) {
                    try {
                        geminiOAuth2Events.emit(GeminiOAuth2Event.AuthProgress, 'Exchanging authorization code for tokens...');
                        const { tokens } = await client.getToken({
                            code: qs.get('code'),
                            redirect_uri: redirectUri,
                        });
                        client.setCredentials(tokens);
                        // Cache the credentials
                        await cacheCredentials(tokens);
                        res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_SUCCESS_URL });
                        res.end();
                        geminiOAuth2Events.emit(GeminiOAuth2Event.AuthSuccess, 'Authentication successful!');
                        resolve();
                    }
                    catch (error) {
                        res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
                        res.end();
                        const err = new Error(`Failed to exchange authorization code for tokens: ${error}`);
                        geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, err);
                        reject(err);
                    }
                }
                else {
                    const error = new Error('No authorization code received from Google OAuth. Please try authenticating again.');
                    geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
                    reject(error);
                }
            }
            catch (e) {
                const error = new Error(`Unexpected error during OAuth authentication: ${e}`);
                geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
                reject(error);
            }
            finally {
                server.close();
            }
        });
        server.listen(port, host, () => {
            console.log(`[Gemini OAuth] Callback server listening on ${host}:${port}`);
        });
        server.on('error', (err) => {
            const error = new Error(`OAuth callback server error: ${err}`);
            geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
            reject(error);
        });
    });
    return {
        authUrl,
        loginCompletePromise,
    };
}
/**
 * Initialize and return a Gemini OAuth2 client
 * Automatically triggers OAuth flow if no cached credentials exist
 */
export async function getGeminiOAuthClient(config) {
    const client = new OAuth2Client({
        clientId: OAUTH_CLIENT_ID,
        clientSecret: OAUTH_CLIENT_SECRET,
    });
    // Set up token refresh handler
    client.on('tokens', async (tokens) => {
        await cacheCredentials(tokens);
    });
    // Try to load cached credentials
    const credentials = await fetchCachedCredentials();
    if (credentials) {
        client.setCredentials(credentials);
        try {
            // Verify credentials are still valid
            const { token } = await client.getAccessToken();
            if (token) {
                await client.getTokenInfo(token);
                console.log('[Gemini OAuth] Loaded cached credentials');
                return client;
            }
        }
        catch (error) {
            console.debug('[Gemini OAuth] Cached credentials are not valid:', error);
            // Clear invalid credentials
            await clearGeminiCredentials();
        }
    }
    // No valid cached credentials, start OAuth flow
    console.log('[Gemini OAuth] No valid credentials found, starting auth flow');
    if (config && config.isBrowserLaunchSuppressed()) {
        throw new Error('Gemini OAuth requires browser authentication. Please run without NO_BROWSER=true');
    }
    let isCancelled = false;
    // Set up cancellation listener
    const cancelHandler = () => {
        isCancelled = true;
    };
    geminiOAuth2Events.once(GeminiOAuth2Event.AuthCancel, cancelHandler);
    try {
        const webLogin = await authWithGeminiWeb(client);
        // Add 5-minute timeout
        const authTimeout = 5 * 60 * 1000;
        const timeoutPromise = new Promise((_, reject) => {
            setTimeout(() => {
                if (!isCancelled) {
                    const error = new Error('Authentication timed out after 5 minutes. Please try again.');
                    geminiOAuth2Events.emit(GeminiOAuth2Event.AuthError, error);
                    reject(error);
                }
            }, authTimeout);
        });
        const cancelPromise = new Promise((_, reject) => {
            const checkCancelled = setInterval(() => {
                if (isCancelled) {
                    clearInterval(checkCancelled);
                    reject(new Error('Gemini OAuth authentication was cancelled by user'));
                }
            }, 100);
        });
        await Promise.race([
            webLogin.loginCompletePromise,
            timeoutPromise,
            cancelPromise,
        ]);
        if (isCancelled) {
            throw new Error('Gemini OAuth authentication was cancelled by user');
        }
        console.log('[Gemini OAuth] Authentication successful!');
        return client;
    }
    finally {
        geminiOAuth2Events.off(GeminiOAuth2Event.AuthCancel, cancelHandler);
    }
}
//# sourceMappingURL=geminiOAuth2.js.map