import { readKeychainOAuthTokenInfo, storeKeychainOAuthTokenInfo, readKeychainOAuthClientInfo, storeKeychainOAuthClientInfo, } from './keychain.js';
import { getAuthProfile, saveAuthProfile } from './profiles.js';
import { createLogger } from '../logger.js';
const logger = createLogger('oauth-provider');
function decodeJwtPayload(jwt) {
    try {
        const parts = jwt.split('.');
        if (parts.length !== 3) {
            return undefined;
        }
        const payload = parts[1];
        const decoded = Buffer.from(payload, 'base64url').toString('utf-8');
        return JSON.parse(decoded);
    }
    catch {
        logger.debug('Failed to decode id_token payload');
        return undefined;
    }
}
export class OAuthProvider {
    serverUrl;
    profileName;
    tokenManager;
    _clientId;
    _redirectUrl;
    _forceReauth;
    _authProfile;
    _codeVerifier;
    _clientInformation;
    constructor(options) {
        this.serverUrl = options.serverUrl;
        this.profileName = options.profileName;
        this._redirectUrl = options.redirectUrl || 'http://localhost/callback';
        this._forceReauth = options.forceReauth || false;
        if (options.tokenManager) {
            this.tokenManager = options.tokenManager;
        }
        if (options.clientId) {
            this._clientId = options.clientId;
        }
    }
    isRuntimeMode() {
        return !!this.tokenManager;
    }
    get redirectUrl() {
        return this._redirectUrl;
    }
    get clientMetadata() {
        return {
            redirect_uris: [this._redirectUrl],
            grant_types: ['authorization_code', 'refresh_token'],
            response_types: ['code'],
            token_endpoint_auth_method: 'none',
            client_name: 'mcpc',
            client_uri: 'https://github.com/apify/mcpc',
        };
    }
    async clientInformation() {
        if (this.isRuntimeMode() && this._clientId) {
            return { client_id: this._clientId };
        }
        if (!this._clientInformation) {
            const storedClient = await readKeychainOAuthClientInfo(this.serverUrl, this.profileName);
            if (storedClient) {
                this._clientInformation = {
                    client_id: storedClient.clientId,
                    client_secret: storedClient.clientSecret,
                };
            }
        }
        return this._clientInformation;
    }
    async saveClientInformation(clientInformation) {
        if (this.isRuntimeMode()) {
            return;
        }
        this._clientInformation = clientInformation;
        const clientInfo = {
            clientId: clientInformation.client_id,
        };
        if (clientInformation.client_secret) {
            clientInfo.clientSecret = clientInformation.client_secret;
        }
        await storeKeychainOAuthClientInfo(this.serverUrl, this.profileName, clientInfo);
    }
    async tokens() {
        if (this.isRuntimeMode() && this.tokenManager) {
            const accessToken = await this.tokenManager.getValidAccessToken();
            return {
                access_token: accessToken,
                token_type: 'Bearer',
            };
        }
        if (this._forceReauth) {
            return undefined;
        }
        const storedTokens = await readKeychainOAuthTokenInfo(this.serverUrl, this.profileName);
        if (!storedTokens) {
            return undefined;
        }
        const result = {
            access_token: storedTokens.accessToken,
            token_type: storedTokens.tokenType,
        };
        if (storedTokens.expiresIn !== undefined) {
            result.expires_in = storedTokens.expiresIn;
        }
        if (storedTokens.refreshToken !== undefined) {
            result.refresh_token = storedTokens.refreshToken;
        }
        if (storedTokens.scope !== undefined) {
            result.scope = storedTokens.scope;
        }
        return result;
    }
    async saveTokens(tokens) {
        if (this.isRuntimeMode()) {
            return;
        }
        const tokenInfo = {
            accessToken: tokens.access_token,
            tokenType: tokens.token_type,
        };
        if (tokens.expires_in !== undefined) {
            tokenInfo.expiresIn = tokens.expires_in;
            tokenInfo.expiresAt = Math.floor(Date.now() / 1000) + tokens.expires_in;
        }
        if (tokens.refresh_token !== undefined) {
            tokenInfo.refreshToken = tokens.refresh_token;
        }
        if (tokens.scope !== undefined) {
            tokenInfo.scope = tokens.scope;
        }
        await storeKeychainOAuthTokenInfo(this.serverUrl, this.profileName, tokenInfo);
        await this.updateProfileMetadata(tokens);
    }
    async updateProfileMetadata(tokens) {
        const now = new Date().toISOString();
        let profile = this._authProfile || (await getAuthProfile(this.serverUrl, this.profileName));
        if (!profile) {
            profile = {
                name: this.profileName,
                serverUrl: this.serverUrl,
                authType: 'oauth',
                oauthIssuer: '',
                createdAt: now,
                authenticatedAt: now,
            };
        }
        else {
            profile.authenticatedAt = now;
        }
        if (tokens.scope) {
            profile.scopes = tokens.scope.split(' ');
        }
        const idToken = tokens.id_token;
        if (idToken) {
            const claims = decodeJwtPayload(idToken);
            if (claims) {
                logger.debug('Extracted user info from id_token');
                if (claims.email) {
                    profile.userEmail = claims.email;
                }
                if (claims.name) {
                    profile.userName = claims.name;
                }
                else if (claims.preferred_username) {
                    profile.userName = claims.preferred_username;
                }
                if (claims.sub) {
                    profile.userSubject = claims.sub;
                }
            }
        }
        await saveAuthProfile(profile);
        this._authProfile = profile;
    }
    async redirectToAuthorization(authorizationUrl) {
        if (this.isRuntimeMode()) {
            throw new Error('OAuthProvider in runtime mode does not support authorization flow. Use CLI "login" command first.');
        }
        logger.warn(`MCP SDK requested redirect to authorization URL (ignoring): ${authorizationUrl.toString()}`);
    }
    async saveCodeVerifier(codeVerifier) {
        this._codeVerifier = codeVerifier;
    }
    async codeVerifier() {
        if (!this._codeVerifier) {
            throw new Error('Code verifier not found');
        }
        return this._codeVerifier;
    }
}
//# sourceMappingURL=oauth-provider.js.map