import fs from 'node:fs';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { ensureYagrHomeDir, getYagrPaths } from '../config/yagr-home.js';
import { resolvePackageManagerCommand, resolvePackageManagerSpawnOptions } from '../system/package-manager.js';
import { ensureGitHubCopilotSession, fetchGitHubCopilotModels, resolveCopilotApiToken, validateGitHubCopilotRuntime, } from './copilot-account.js';
import { writeCachedModelCatalog } from './model-catalog-cache.js';
import { ANTHROPIC_ACCOUNT_DEFAULT_MODEL, ensureAnthropicAccountSession, fetchAnthropicAccountModels, validateAnthropicAccountRuntime, } from './anthropic-account.js';
import { ensureOpenAiAccountSession, fetchOpenAiAccountModels, OPENAI_ACCOUNT_BASE_URL, OPENAI_ACCOUNT_DEFAULT_MODEL, validateOpenAiAccountRuntime } from './openai-account.js';
import { fetchAvailableModels } from './provider-discovery.js';
import { getProviderPlugin } from './provider-plugin.js';
import { getDefaultBaseUrlForProvider, getProviderDefinition, isOAuthAccountProvider } from './provider-registry.js';
export async function prepareProviderRuntime(provider, options = {}) {
    if (provider === 'openai-proxy') {
        const session = await ensureOpenAiAccountSession();
        if (!session) {
            return {
                ready: false,
                reason: 'No OpenAI account session found. Run `yagr setup` to authenticate with OpenAI.',
                notes: ['OpenAI account login uses a native PKCE OAuth flow managed by Yagr.'],
            };
        }
        const probe = await validateOpenAiAccountRuntime(OPENAI_ACCOUNT_DEFAULT_MODEL);
        if (!probe.ok) {
            return {
                ready: false,
                reason: probe.error || 'OpenAI account runtime validation failed.',
                notes: ['OpenAI session found, but the API endpoint did not validate successfully.'],
            };
        }
        let models = [];
        let discoveryError;
        try {
            const discovered = await withTimeout(fetchOpenAiAccountModels(session.accessToken), 6_000);
            if (discovered.length > 0) {
                models = discovered;
                writeCachedModelCatalog(provider, discovered);
            }
        }
        catch (error) {
            discoveryError = error instanceof Error ? error.message : String(error);
        }
        const sessionNote = session.source === 'codex'
            ? 'Connected through backward-compatible Codex CLI session.'
            : 'Connected through Yagr-managed OpenAI OAuth.';
        const notes = [sessionNote];
        if (discoveryError) {
            notes.push(`Model discovery failed: ${discoveryError}`);
        }
        else if (models.length > 0) {
            notes.push('Model discovery completed from OpenAI account.');
        }
        else {
            notes.push('Model discovery returned no models for this account.');
        }
        return {
            ready: true,
            runtime: {
                provider,
                baseUrl: OPENAI_ACCOUNT_BASE_URL,
                apiKey: session.accessToken,
                models,
                notes,
                autoStarted: false,
            },
            notes,
        };
    }
    if (provider === 'anthropic-proxy') {
        const manualCredential = options.apiKey?.trim();
        const session = await ensureAnthropicAccountSession();
        const resolvedCredential = manualCredential || session?.apiKey;
        if (!resolvedCredential) {
            return {
                ready: false,
                reason: 'No Anthropic credential found. Paste a setup-token or Anthropic API key in setup.',
                notes: ['Anthropic credentials can come from setup input, Claude Code config (~/.claude/config.json), or ANTHROPIC_API_KEY.'],
            };
        }
        const probe = await validateAnthropicAccountRuntime(ANTHROPIC_ACCOUNT_DEFAULT_MODEL, resolvedCredential);
        if (!probe.ok) {
            return {
                ready: false,
                reason: probe.error || 'Anthropic account runtime validation failed.',
                notes: ['Anthropic credentials found, but the API endpoint did not validate successfully.'],
            };
        }
        let models = [];
        let discoveryError;
        try {
            const discovered = await withTimeout(fetchAnthropicAccountModels(resolvedCredential), 6_000);
            if (discovered.length > 0) {
                models = discovered;
                writeCachedModelCatalog(provider, discovered);
            }
        }
        catch (error) {
            discoveryError = error instanceof Error ? error.message : String(error);
        }
        const sourceNote = manualCredential
            ? 'Connected through credential pasted during setup.'
            : session?.source === 'env'
                ? 'Connected through ANTHROPIC_API_KEY environment variable.'
                : 'Connected through Claude Code CLI credentials.';
        const notes = [sourceNote];
        if (discoveryError) {
            notes.push(`Model discovery failed: ${discoveryError}`);
        }
        else if (models.length > 0) {
            notes.push('Model discovery completed from Anthropic API.');
        }
        else {
            notes.push('Model discovery returned no models for this account.');
        }
        return {
            ready: true,
            runtime: {
                provider,
                baseUrl: '',
                apiKey: resolvedCredential,
                models,
                notes,
                autoStarted: false,
            },
            notes,
        };
    }
    if (provider === 'copilot-proxy') {
        const session = await ensureGitHubCopilotSession();
        if (!session) {
            return {
                ready: false,
                reason: 'Unable to sign in to GitHub Copilot. Complete the device login flow and retry.',
                notes: ['GitHub Copilot OAuth is handled directly by Yagr with GitHub device flow.'],
            };
        }
        const runtimeAuth = await resolveCopilotApiToken(session.githubToken);
        const probe = await validateGitHubCopilotRuntime();
        if (!probe.ok) {
            return {
                ready: false,
                reason: probe.error || 'GitHub Copilot runtime validation failed after login.',
                notes: ['GitHub sign-in exists, but the Copilot runtime did not validate successfully.'],
            };
        }
        let models = [];
        let discoveryError;
        try {
            const discovered = await withTimeout(fetchGitHubCopilotModels(runtimeAuth.token, runtimeAuth.baseUrl), 6_000);
            if (discovered.length > 0) {
                models = discovered;
                writeCachedModelCatalog(provider, discovered);
            }
        }
        catch (error) {
            discoveryError = error instanceof Error ? error.message : String(error);
        }
        const copilotNotes = [
            'Connected through Yagr-managed GitHub device login and Copilot token exchange.',
            'Runtime validated with GitHub Copilot.',
        ];
        if (discoveryError) {
            copilotNotes.push(`Model discovery failed: ${discoveryError}`);
        }
        else if (models.length > 0) {
            copilotNotes.push('Model discovery completed from Copilot runtime.');
        }
        else {
            copilotNotes.push('Model discovery returned no models for this account.');
        }
        return {
            ready: true,
            runtime: {
                provider,
                baseUrl: runtimeAuth.baseUrl,
                models,
                notes: copilotNotes,
                autoStarted: false,
            },
            notes: copilotNotes,
        };
    }
    const definition = getProviderDefinition(provider);
    const plugin = getProviderPlugin(provider);
    const managedProxy = definition.managedProxy;
    const baseUrl = options.baseUrl || getDefaultBaseUrlForProvider(provider);
    if (!plugin.transport.usesOpenAiCompatibleApi || !baseUrl) {
        return {
            ready: false,
            notes: [],
        };
    }
    const existingModels = await fetchAvailableModels(provider, options.apiKey, baseUrl);
    if (existingModels.length > 0) {
        return {
            ready: true,
            runtime: {
                provider,
                baseUrl,
                apiKey: options.apiKey,
                models: existingModels,
                notes: [],
                autoStarted: false,
            },
            notes: [],
        };
    }
    if (!plugin.transport.managedProxy || !managedProxy) {
        if (isOAuthAccountProvider(provider)) {
            return {
                ready: false,
                reason: `OAuth runtime for ${provider} is not ready.`,
                notes: [],
            };
        }
        return {
            ready: false,
            notes: [],
        };
    }
    const stateEntry = getRuntimeState().providers?.[provider];
    if (stateEntry?.pid && isProcessRunning(stateEntry.pid)) {
        const runningModels = await waitForModels(provider, options.apiKey, stateEntry.baseUrl, managedProxy.readyTimeoutMs ?? 30_000);
        if (runningModels.length > 0) {
            return {
                ready: true,
                runtime: {
                    provider,
                    baseUrl: stateEntry.baseUrl,
                    apiKey: options.apiKey,
                    models: runningModels,
                    notes: managedProxy.startupNotes ?? [],
                    logPath: stateEntry.logPath,
                    autoStarted: false,
                },
                notes: managedProxy.startupNotes ?? [],
            };
        }
    }
    const started = startManagedProxy(provider, baseUrl);
    const models = await waitForModels(provider, options.apiKey, started.baseUrl, managedProxy.readyTimeoutMs ?? 30_000);
    if (models.length > 0) {
        return {
            ready: true,
            runtime: {
                provider,
                baseUrl: started.baseUrl,
                apiKey: options.apiKey,
                models,
                notes: managedProxy.startupNotes ?? [],
                logPath: started.logPath,
                autoStarted: true,
            },
            notes: managedProxy.startupNotes ?? [],
        };
    }
    return {
        ready: false,
        reason: `Managed proxy for ${provider} did not become ready. Check ${started.logPath}.`,
        notes: [
            ...(managedProxy.startupNotes ?? []),
            `Proxy logs: ${started.logPath}`,
        ],
    };
}
function startManagedProxy(provider, baseUrl) {
    const definition = getProviderPlugin(provider).definition;
    const managed = definition.managedProxy;
    if (!managed) {
        throw new Error(`Provider ${provider} does not have a managed proxy runtime.`);
    }
    ensureYagrHomeDir();
    const paths = getYagrPaths();
    const logDir = path.join(paths.proxyRuntimeDir, 'logs');
    fs.mkdirSync(logDir, { recursive: true });
    const logPath = path.join(logDir, `${provider}.log`);
    const logFd = fs.openSync(logPath, 'a');
    const args = ['--yes', '--package', managed.packageName, managed.executable, ...(managed.args ?? [])];
    const child = spawn(resolvePackageManagerCommand('npx'), args, {
        detached: true,
        stdio: ['ignore', logFd, logFd],
        ...resolvePackageManagerSpawnOptions(),
        env: process.env,
    });
    child.unref();
    fs.closeSync(logFd);
    const entry = {
        provider,
        pid: child.pid,
        command: `${resolvePackageManagerCommand('npx')} ${args.join(' ')}`,
        baseUrl,
        logPath,
        startedAt: new Date().toISOString(),
    };
    updateRuntimeState((state) => ({
        ...state,
        providers: {
            ...(state.providers ?? {}),
            [provider]: entry,
        },
    }));
    return entry;
}
export function startProviderProxy(provider, options = {}) {
    const definition = getProviderPlugin(provider).definition;
    if (!definition.managedProxy) {
        throw new Error(`Provider ${provider} does not have a managed proxy runtime yet.`);
    }
    const baseUrl = options.baseUrl || getDefaultBaseUrlForProvider(provider);
    if (!baseUrl) {
        throw new Error(`Provider ${provider} does not define a default proxy base URL.`);
    }
    const existing = getProxyRuntimeStatus(provider);
    if (existing.running) {
        return existing;
    }
    const entry = startManagedProxy(provider, baseUrl);
    return {
        provider,
        configuredBaseUrl: entry.baseUrl,
        running: true,
        pid: entry.pid,
        command: entry.command,
        logPath: entry.logPath,
        startedAt: entry.startedAt,
        managed: true,
    };
}
export function stopProviderProxy(provider) {
    const currentState = getRuntimeState();
    const entry = currentState.providers?.[provider];
    if (entry?.pid && isProcessRunning(entry.pid)) {
        process.kill(entry.pid, 'SIGTERM');
    }
    updateRuntimeState((state) => ({
        ...state,
        providers: {
            ...(state.providers ?? {}),
            [provider]: undefined,
        },
    }));
    return getProxyRuntimeStatus(provider);
}
export function getProxyRuntimeStatus(provider) {
    const plugin = getProviderPlugin(provider);
    const entry = getRuntimeState().providers?.[provider];
    return {
        provider,
        configuredBaseUrl: entry?.baseUrl || getDefaultBaseUrlForProvider(provider),
        running: Boolean(entry?.pid && isProcessRunning(entry.pid)),
        pid: entry?.pid,
        command: entry?.command,
        logPath: entry?.logPath,
        startedAt: entry?.startedAt,
        managed: plugin.transport.managedProxy,
    };
}
export function listProxyRuntimeStatuses() {
    return Object.keys(getRuntimeState().providers ?? {}).map((provider) => getProxyRuntimeStatus(provider));
}
async function waitForModels(provider, apiKey, baseUrl, timeoutMs) {
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
        const models = await fetchAvailableModels(provider, apiKey, baseUrl);
        if (models.length > 0) {
            return models;
        }
        await delay(1_000);
    }
    return [];
}
function getRuntimeState() {
    const paths = getYagrPaths();
    if (!fs.existsSync(paths.proxyRuntimeStatePath)) {
        return {};
    }
    try {
        return JSON.parse(fs.readFileSync(paths.proxyRuntimeStatePath, 'utf-8'));
    }
    catch {
        return {};
    }
}
function updateRuntimeState(updater) {
    const paths = getYagrPaths();
    fs.mkdirSync(paths.proxyRuntimeDir, { recursive: true });
    fs.writeFileSync(paths.proxyRuntimeStatePath, JSON.stringify(updater(getRuntimeState()), null, 2));
}
function isProcessRunning(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
async function withTimeout(promise, timeoutMs) {
    return await new Promise((resolve, reject) => {
        const timer = setTimeout(() => reject(new Error('timeout')), timeoutMs);
        promise
            .then((value) => {
            clearTimeout(timer);
            resolve(value);
        })
            .catch((error) => {
            clearTimeout(timer);
            reject(error);
        });
    });
}
function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
//# sourceMappingURL=proxy-runtime.js.map