import { createServer } from 'net';
import { isValidSessionName, validateProfileName, isProcessAlive, getServerHost, redactHeaders } from '../../lib/index.js';
import { formatOutput, formatSuccess, formatError, formatSessionLine, formatServerDetails } from '../output.js';
import { withMcpClient, resolveTarget, resolveAuthProfile } from '../helpers.js';
import { listAuthProfiles } from '../../lib/auth/profiles.js';
import { sessionExists, deleteSession, saveSession, updateSession, consolidateSessions, getSession, } from '../../lib/sessions.js';
import { startBridge, stopBridge } from '../../lib/bridge-manager.js';
import { storeKeychainSessionHeaders, storeKeychainProxyBearerToken } from '../../lib/auth/keychain.js';
import { ClientError } from '../../lib/index.js';
import chalk from 'chalk';
import { createLogger } from '../../lib/logger.js';
import { parseProxyArg } from '../parser.js';
const logger = createLogger('sessions');
async function checkPortAvailable(host, port) {
    return new Promise((resolve) => {
        const server = createServer();
        server.once('error', (err) => {
            if (err.code === 'EADDRINUSE') {
                resolve(false);
            }
            else {
                resolve(false);
            }
        });
        server.once('listening', () => {
            server.close(() => {
                resolve(true);
            });
        });
        server.listen(port, host);
    });
}
export async function connectSession(name, target, options) {
    try {
        if (!isValidSessionName(name)) {
            throw new ClientError(`Invalid session name: ${name}\n` +
                `Session names must start with @ and be followed by 1-64 characters, alphanumeric with hyphens or underscores only (e.g., @my-session).`);
        }
        if (options.profile) {
            validateProfileName(options.profile);
        }
        let proxyConfig;
        if (options.proxy) {
            proxyConfig = parseProxyArg(options.proxy);
            logger.debug(`Proxy config: ${proxyConfig.host}:${proxyConfig.port}`);
            const portAvailable = await checkPortAvailable(proxyConfig.host, proxyConfig.port);
            if (!portAvailable) {
                throw new ClientError(`Port ${proxyConfig.port} is already in use on ${proxyConfig.host}. ` +
                    `Choose a different port with --proxy [host:]port`);
            }
        }
        if (options.proxyBearerToken && !options.proxy) {
            throw new ClientError('--proxy-bearer-token requires --proxy to be specified');
        }
        const existingSession = await getSession(name);
        if (existingSession) {
            const bridgeStatus = getBridgeStatus(existingSession);
            if (bridgeStatus === 'live') {
                if (options.outputMode === 'human') {
                    console.log(formatSuccess(`Session ${name} is already active`));
                }
                await showServerDetails(name, { ...options, hideTarget: false });
                return;
            }
            if (options.outputMode === 'human') {
                console.log(chalk.yellow(`Session ${name} exists but bridge is ${bridgeStatus}, reconnecting...`));
            }
            try {
                await stopBridge(name);
            }
            catch {
            }
        }
        const serverConfig = await resolveTarget(target, options);
        let profileName;
        if (serverConfig.url) {
            profileName = await resolveAuthProfile(serverConfig.url, target, options.profile, { sessionName: name });
        }
        let headers;
        if (Object.keys(serverConfig.headers || {}).length > 0) {
            headers = { ...serverConfig.headers };
            if (profileName && headers.Authorization?.startsWith('Bearer ')) {
                logger.debug(`Skipping OAuth Authorization header storage for session ${name} (handled via profile)`);
                delete headers.Authorization;
            }
            if (Object.keys(headers).length > 0) {
                logger.debug(`Storing ${Object.keys(headers).length} headers for session ${name} in keychain`);
                await storeKeychainSessionHeaders(name, headers);
            }
            else {
                headers = undefined;
            }
        }
        if (options.proxyBearerToken) {
            logger.debug(`Storing proxy bearer token for session ${name} in keychain`);
            await storeKeychainProxyBearerToken(name, options.proxyBearerToken);
        }
        const isReconnect = !!existingSession;
        const { headers: _originalHeaders, ...baseTransportConfig } = serverConfig;
        const sessionTransportConfig = {
            ...baseTransportConfig,
            ...(headers && { headers: redactHeaders(headers) }),
        };
        const sessionUpdate = {
            server: sessionTransportConfig,
            ...(profileName && { profileName }),
            ...(proxyConfig && { proxy: proxyConfig }),
        };
        if (isReconnect) {
            await updateSession(name, sessionUpdate);
            logger.debug(`Session record updated for reconnect: ${name}`);
        }
        else {
            await saveSession(name, { server: sessionTransportConfig, createdAt: new Date().toISOString(), ...sessionUpdate });
            logger.debug(`Initial session record created for: ${name}`);
        }
        try {
            const bridgeOptions = {
                sessionName: name,
                serverConfig: serverConfig,
                verbose: options.verbose || false,
            };
            if (headers) {
                bridgeOptions.headers = headers;
            }
            if (profileName) {
                bridgeOptions.profileName = profileName;
            }
            if (proxyConfig) {
                bridgeOptions.proxyConfig = proxyConfig;
            }
            const { pid } = await startBridge(bridgeOptions);
            await updateSession(name, { pid });
            logger.debug(`Session ${name} updated with bridge PID: ${pid}`);
        }
        catch (error) {
            logger.debug(`Bridge start failed, cleaning up session ${name}`);
            if (!isReconnect) {
                try {
                    await deleteSession(name);
                }
                catch {
                }
            }
            throw error;
        }
        if (options.outputMode === 'human') {
            console.log(formatSuccess(`Session ${name} ${isReconnect ? 'reconnected' : 'created'}`));
        }
        await showServerDetails(name, {
            ...options,
            hideTarget: false,
        });
    }
    catch (error) {
        if (options.outputMode === 'human') {
            console.error(formatError(error.message));
        }
        else {
            console.error(formatOutput({
                sessionName: name,
                target,
                created: false,
                error: error.message,
            }, 'json'));
        }
        throw error;
    }
}
function getBridgeStatus(session) {
    if (session.status === 'expired') {
        return 'expired';
    }
    if (!session.pid || !isProcessAlive(session.pid)) {
        return 'crashed';
    }
    return 'live';
}
function formatBridgeStatus(status) {
    switch (status) {
        case 'live':
            return { dot: chalk.green('●'), text: chalk.green('live') };
        case 'crashed':
            return { dot: chalk.yellow('○'), text: chalk.yellow('crashed') };
        case 'expired':
            return { dot: chalk.red('○'), text: chalk.red('expired') };
    }
}
function formatTimeAgo(isoDate) {
    if (!isoDate)
        return '';
    const date = new Date(isoDate);
    const now = new Date();
    const diffMs = now.getTime() - date.getTime();
    const diffSecs = Math.floor(diffMs / 1000);
    const diffMins = Math.floor(diffSecs / 60);
    const diffHours = Math.floor(diffMins / 60);
    const diffDays = Math.floor(diffHours / 24);
    if (diffSecs < 60)
        return 'just now';
    if (diffMins < 60)
        return `${diffMins}m ago`;
    if (diffHours < 24)
        return `${diffHours}h ago`;
    if (diffDays === 1)
        return 'yesterday';
    if (diffDays < 7)
        return `${diffDays} days ago`;
    if (diffDays < 30)
        return `${Math.floor(diffDays / 7)} weeks ago`;
    return `${Math.floor(diffDays / 30)} months ago`;
}
export async function listSessionsAndAuthProfiles(options) {
    const consolidateResult = await consolidateSessions(false);
    const sessions = Object.values(consolidateResult.sessions);
    const profiles = await listAuthProfiles();
    if (options.outputMode === 'json') {
        const sessionsWithStatus = sessions.map((session) => ({
            ...session,
            status: getBridgeStatus(session),
        }));
        console.log(formatOutput({
            sessions: sessionsWithStatus,
            profiles,
        }, 'json'));
    }
    else {
        if (sessions.length === 0) {
            console.log(chalk.dim('No active MCP sessions.'));
        }
        else {
            console.log(chalk.bold('MCP sessions:'));
            for (const session of sessions) {
                const status = getBridgeStatus(session);
                const { dot, text } = formatBridgeStatus(status);
                let statusStr = `${dot} ${text}`;
                if (session.lastSeenAt) {
                    const lastSeenMs = Date.now() - new Date(session.lastSeenAt).getTime();
                    const isStale = lastSeenMs > 5 * 60 * 1000;
                    if (status !== 'live' || isStale) {
                        const timeAgo = formatTimeAgo(session.lastSeenAt);
                        if (timeAgo) {
                            statusStr += chalk.dim(`, ${timeAgo}`);
                        }
                    }
                }
                console.log(`  ${formatSessionLine(session)} ${statusStr}`);
            }
        }
        console.log('');
        if (profiles.length === 0) {
            console.log(chalk.bold('No OAuth profiles.'));
        }
        else {
            console.log(chalk.bold('Available OAuth profiles:'));
            for (const profile of profiles) {
                const hostStr = getServerHost(profile.serverUrl);
                const nameStr = chalk.magenta(profile.name);
                const userStr = profile.userEmail || profile.userName || '';
                const timeAgo = formatTimeAgo(profile.refreshedAt || profile.createdAt);
                const timeLabel = profile.refreshedAt ? 'refreshed' : 'created';
                let line = `  ${hostStr} / ${nameStr}`;
                if (userStr) {
                    line += chalk.dim(` (${userStr})`);
                }
                if (timeAgo) {
                    line += chalk.dim(`, ${timeLabel} ${timeAgo}`);
                }
                console.log(line);
            }
        }
    }
}
export async function closeSession(name, options) {
    try {
        if (!(await sessionExists(name))) {
            throw new ClientError(`Session not found: ${name}`);
        }
        await stopBridge(name);
        await deleteSession(name);
        if (options.outputMode === 'human') {
            console.log(formatSuccess(`Session ${name} closed successfully`));
        }
        else {
            console.log(formatOutput({
                sessionName: name,
                closed: true,
            }, 'json'));
        }
    }
    catch (error) {
        if (options.outputMode === 'human') {
            console.error(formatError(error.message));
        }
        else {
            console.error(formatOutput({
                sessionName: name,
                closed: false,
                error: error.message,
            }, 'json'));
        }
        throw error;
    }
}
export async function showServerDetails(target, options) {
    await withMcpClient(target, options, async (client, context) => {
        const serverDetails = await client.getServerDetails();
        const { serverInfo, capabilities, instructions, protocolVersion } = serverDetails;
        if (options.outputMode === 'human') {
            console.log(formatServerDetails(serverDetails, target));
        }
        else {
            const server = {
                ...context.serverConfig,
                ...(context.serverConfig?.headers && { headers: redactHeaders(context.serverConfig.headers) }),
            };
            console.log(formatOutput({
                _mcpc: {
                    sessionName: context.sessionName,
                    profileName: context.profileName,
                    server,
                },
                protocolVersion,
                capabilities,
                serverInfo,
                instructions,
            }, 'json'));
        }
    });
}
export async function restartSession(name, options) {
    try {
        const session = await getSession(name);
        if (!session) {
            throw new ClientError(`Session not found: ${name}`);
        }
        if (options.outputMode === 'human') {
            console.log(chalk.yellow(`Restarting session ${name}...`));
        }
        try {
            await stopBridge(name);
        }
        catch {
        }
        const serverConfig = session.server;
        if (!serverConfig) {
            throw new ClientError(`Session ${name} has no server configuration`);
        }
        const { readKeychainSessionHeaders } = await import('../../lib/auth/keychain.js');
        const headers = await readKeychainSessionHeaders(name);
        const bridgeOptions = {
            sessionName: name,
            serverConfig: { ...serverConfig, ...(headers && { headers }) },
            verbose: options.verbose || false,
        };
        if (headers) {
            bridgeOptions.headers = headers;
        }
        if (session.profileName) {
            bridgeOptions.profileName = session.profileName;
        }
        if (session.proxy) {
            bridgeOptions.proxyConfig = session.proxy;
        }
        const { pid } = await startBridge(bridgeOptions);
        await updateSession(name, { pid, status: 'active' });
        logger.debug(`Session ${name} restarted with bridge PID: ${pid}`);
        if (options.outputMode === 'human') {
            console.log(formatSuccess(`Session ${name} restarted`));
        }
        await showServerDetails(name, {
            ...options,
            hideTarget: false,
        });
    }
    catch (error) {
        if (options.outputMode === 'human') {
            console.error(formatError(error.message));
        }
        else {
            console.error(formatOutput({
                sessionName: name,
                restarted: false,
                error: error.message,
            }, 'json'));
        }
        throw error;
    }
}
export async function showHelp(target, options) {
    await showServerDetails(target, options);
}
export async function openShell(target) {
    const { startShell } = await import('../shell.js');
    await startShell(target);
}
//# sourceMappingURL=sessions.js.map