import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
import { buildOAuthAuthorizationStart, createOAuthState, createPkceVerifier, exchangeOAuthAuthorizationCode, parseOAuthScopes, refreshOAuthAccessToken, } from '../runtime/auth/oauth-core.js';
function isSubscriptionExpired(expiresAt, bufferMs = 60_000) {
    if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt))
        return false;
    return Date.now() + bufferMs >= expiresAt;
}
export class SubscriptionManager {
    path;
    constructor(path) {
        this.path = path;
    }
    read() {
        try {
            const raw = readFileSync(this.path, 'utf-8');
            return JSON.parse(raw);
        }
        catch {
            return {
                version: 1,
                subscriptions: {},
                pending: {},
            };
        }
    }
    write(store) {
        mkdirSync(dirname(this.path), { recursive: true });
        writeFileSync(this.path, `${JSON.stringify(store, null, 2)}\n`, 'utf-8');
    }
    list() {
        return Object.values(this.read().subscriptions).sort((a, b) => a.provider.localeCompare(b.provider));
    }
    listPending() {
        return Object.values(this.read().pending).sort((a, b) => a.provider.localeCompare(b.provider));
    }
    get(provider) {
        return this.read().subscriptions[provider] ?? null;
    }
    getAccessToken(provider) {
        const subscription = this.get(provider);
        if (!subscription?.overrideAmbientApiKeys)
            return null;
        return subscription.accessToken;
    }
    async resolveAccessToken(provider, config) {
        const subscription = this.get(provider);
        if (!subscription)
            return null;
        const active = isSubscriptionExpired(subscription.expiresAt)
            ? await this.refreshOAuthToken(provider, config)
            : subscription;
        return active.accessToken;
    }
    beginOAuthLogin(provider, config) {
        const store = this.read();
        const state = createOAuthState();
        const verifier = createPkceVerifier();
        const redirectUri = config.redirectUri;
        const pending = {
            provider,
            state,
            verifier,
            redirectUri,
            createdAt: Date.now(),
        };
        const started = buildOAuthAuthorizationStart(config, { state, verifier, redirectUri });
        store.pending[provider] = pending;
        this.write(store);
        return {
            authorizationUrl: started.authorizationUrl,
            pending,
        };
    }
    async completeOAuthLogin(provider, config, code) {
        const store = this.read();
        const pending = store.pending[provider];
        if (!pending) {
            throw new Error(`No pending OAuth login for ${provider}. Start with /subscription login ${provider} start.`);
        }
        const tokenResponse = await exchangeOAuthAuthorizationCode(config, {
            code,
            verifier: pending.verifier,
            redirectUri: pending.redirectUri,
            state: pending.state,
        });
        const now = Date.now();
        const subscription = {
            provider,
            accessToken: tokenResponse.accessToken,
            ...(typeof tokenResponse.refreshToken === 'string' && tokenResponse.refreshToken.length > 0
                ? { refreshToken: tokenResponse.refreshToken }
                : {}),
            tokenType: tokenResponse.tokenType,
            ...(typeof tokenResponse.expiresAt === 'number' && Number.isFinite(tokenResponse.expiresAt)
                ? { expiresAt: tokenResponse.expiresAt }
                : {}),
            ...(tokenResponse.scopes ? { scopes: tokenResponse.scopes } : {}),
            authMode: 'oauth',
            overrideAmbientApiKeys: config.overrideAmbientApiKeys ?? true,
            createdAt: store.subscriptions[provider]?.createdAt ?? now,
            updatedAt: now,
        };
        store.subscriptions[provider] = subscription;
        delete store.pending[provider];
        this.write(store);
        return subscription;
    }
    async refreshOAuthToken(provider, config) {
        const store = this.read();
        const existing = store.subscriptions[provider];
        if (!existing) {
            throw new Error(`No stored OAuth subscription for ${provider}.`);
        }
        if (!existing.refreshToken) {
            return existing;
        }
        const tokenResponse = await refreshOAuthAccessToken(config, existing.refreshToken);
        const now = Date.now();
        const refreshed = {
            ...existing,
            accessToken: tokenResponse.accessToken,
            refreshToken: typeof tokenResponse.refreshToken === 'string' && tokenResponse.refreshToken.length > 0
                ? tokenResponse.refreshToken
                : existing.refreshToken,
            tokenType: typeof tokenResponse.tokenType === 'string' && tokenResponse.tokenType.length > 0
                ? tokenResponse.tokenType
                : existing.tokenType,
            ...(typeof tokenResponse.expiresAt === 'number' && Number.isFinite(tokenResponse.expiresAt)
                ? { expiresAt: tokenResponse.expiresAt }
                : typeof existing.expiresAt === 'number' ? { expiresAt: existing.expiresAt } : {}),
            ...(tokenResponse.scopes ? { scopes: tokenResponse.scopes } : existing.scopes ? { scopes: existing.scopes } : {}),
            updatedAt: now,
        };
        store.subscriptions[provider] = refreshed;
        this.write(store);
        return refreshed;
    }
    logout(provider) {
        const store = this.read();
        const existed = provider in store.subscriptions || provider in store.pending;
        delete store.subscriptions[provider];
        delete store.pending[provider];
        this.write(store);
        return existed;
    }
    getPending(provider) {
        return this.read().pending[provider] ?? null;
    }
    savePending(pending) {
        const store = this.read();
        store.pending[pending.provider] = pending;
        this.write(store);
    }
    clearPending(provider) {
        const store = this.read();
        delete store.pending[provider];
        this.write(store);
    }
    saveSubscription(subscription) {
        const store = this.read();
        store.subscriptions[subscription.provider] = subscription;
        delete store.pending[subscription.provider];
        this.write(store);
        return subscription;
    }
}
