import { z } from 'zod';
import { getMonitorNode, listDistressedNodes, listMonitorNodeContainers, listMonitorNodes, MonitorWebSocketClient, } from '@agentuity/server';
import { getAPIBaseURL } from "../../api.js";
import { getCommand } from "../../command-prefix.js";
import { createSubcommand } from "../../types.js";
import * as tui from "../../tui.js";
const monitorOptionsSchema = z.object({
    machine: z.string().optional().describe('Monitor a specific machine id'),
    deployment: z.string().optional().describe('Monitor machines for a deployment id'),
    distressed: z.boolean().optional().describe('Only include distressed machines'),
    snapshot: z.boolean().optional().describe('One-shot snapshot (no stream watch)'),
});
export const monitorSubcommand = createSubcommand({
    name: 'monitor',
    description: 'Monitor infrastructure machines in real time',
    tags: ['read-only', 'slow', 'requires-auth'],
    requires: { auth: true, apiClient: true },
    optional: { org: true },
    idempotent: true,
    examples: [
        { command: getCommand('cloud monitor --snapshot'), description: 'Show a monitor snapshot' },
        {
            command: getCommand('cloud monitor --distressed'),
            description: 'Watch distressed machines',
        },
        { command: getCommand('cloud monitor --machine mach_123'), description: 'Watch one machine' },
    ],
    schema: {
        options: monitorOptionsSchema,
    },
    webUrl: '/infrastructure/monitoring',
    async handler(ctx) {
        const { apiClient, options, opts, auth, config, orgId } = ctx;
        if (opts.machine && opts.distressed) {
            ctx.logger.fatal('--machine and --distressed are mutually exclusive.');
        }
        if (opts.deployment && opts.distressed) {
            ctx.logger.fatal('--deployment and --distressed are mutually exclusive.');
        }
        if (opts.snapshot || options.json) {
            const machines = await getSnapshotMachines({
                apiClient,
                machineId: opts.machine,
                deploymentId: opts.deployment,
                distressed: opts.distressed,
            });
            if (options.json) {
                console.log(JSON.stringify(machines, null, 2));
                return machines;
            }
            renderMachineTable(machines);
            return machines;
        }
        const machineMap = new Map();
        const initialMachines = await getSnapshotMachines({
            apiClient,
            machineId: opts.machine,
            deploymentId: opts.deployment,
            distressed: opts.distressed,
        });
        for (const machine of initialMachines) {
            machineMap.set(machine.machineId, machine);
        }
        renderWatchTable(machineMap, {
            mode: 'snapshot',
            machineId: opts.machine,
            deploymentId: opts.deployment,
            distressed: opts.distressed,
        });
        tui.info('Connecting to monitoring stream...');
        let processingChain = Promise.resolve();
        let resolveWait = null;
        const monitorClient = new MonitorWebSocketClient({
            baseUrl: getAPIBaseURL(config),
            token: auth.apiKey,
            orgId,
            scope: toMonitorScope(opts.machine, opts.deployment),
            onOpen: () => {
                tui.success('Connected to monitoring stream');
            },
            onError: (error) => {
                tui.error(`Monitoring stream error: ${error.message}`);
            },
            onClose: () => {
                tui.info('Monitoring stream disconnected');
                resolveWait?.();
            },
            onMessage: (message) => {
                processingChain = processingChain.then(async () => {
                    await applyMonitorMessage(machineMap, message, apiClient, opts.deployment);
                    renderWatchTable(machineMap, {
                        mode: 'watch',
                        machineId: opts.machine,
                        deploymentId: opts.deployment,
                        distressed: opts.distressed,
                        lastMessage: message,
                    });
                });
            },
        });
        monitorClient.connect();
        await new Promise((resolve) => {
            resolveWait = resolve;
            const onSigInt = () => {
                monitorClient.close();
                process.off('SIGINT', onSigInt);
                resolve();
            };
            process.on('SIGINT', onSigInt);
        });
        return Array.from(machineMap.values());
    },
});
async function getSnapshotMachines(params) {
    const { apiClient, machineId, deploymentId, distressed } = params;
    let machines;
    if (distressed) {
        machines = await listDistressedNodes(apiClient);
    }
    else if (machineId) {
        machines = [await getMonitorNode(apiClient, machineId)];
    }
    else {
        machines = await listMonitorNodes(apiClient);
    }
    if (!deploymentId) {
        return machines;
    }
    const results = await Promise.all(machines.map(async (machine) => {
        const containers = await listMonitorNodeContainers(apiClient, machine.machineId);
        const hasDeployment = containers.some((container) => container.deploymentId === deploymentId);
        return hasDeployment ? machine : null;
    }));
    return results.filter((m) => m !== null);
}
function toMonitorScope(machineId, deploymentId) {
    if (machineId) {
        return { scope: 'machine', machineId };
    }
    if (deploymentId) {
        return { scope: 'deployment', deploymentId };
    }
    return { scope: 'org' };
}
async function applyMonitorMessage(machineMap, message, apiClient, deploymentId) {
    if (message.type === 'snapshot') {
        machineMap.clear();
        for (const machine of message.machines) {
            machineMap.set(machine.machineId, machine);
        }
        if (deploymentId) {
            await filterMapByDeployment(machineMap, apiClient, deploymentId);
        }
        return;
    }
    if (message.type === 'update') {
        const existing = machineMap.get(message.machineId);
        const next = {
            machineId: message.machineId,
            orgId: existing?.orgId ?? '',
            report: message.report,
            compositeScore: message.report.capacity?.compositeScore ?? existing?.compositeScore ?? 0,
            health: message.health,
            reportedAt: usecToISO(message.report.reportedAtUs) ?? existing?.reportedAt ?? '',
            updatedAt: new Date().toISOString(),
            gravity: existing?.gravity ?? '',
        };
        if (deploymentId) {
            const containers = await listMonitorNodeContainers(apiClient, message.machineId);
            const include = containers.some((container) => container.deploymentId === deploymentId);
            if (!include) {
                machineMap.delete(message.machineId);
                return;
            }
        }
        machineMap.set(message.machineId, next);
        return;
    }
    const existing = machineMap.get(message.machineId);
    if (existing) {
        existing.health = message.health;
        existing.updatedAt = new Date().toISOString();
        machineMap.set(message.machineId, existing);
    }
}
async function filterMapByDeployment(machineMap, apiClient, deploymentId) {
    const entries = Array.from(machineMap.keys());
    const results = await Promise.all(entries.map(async (machineId) => {
        const containers = await listMonitorNodeContainers(apiClient, machineId);
        const include = containers.some((container) => container.deploymentId === deploymentId);
        return { machineId, include };
    }));
    for (const { machineId, include } of results) {
        if (!include) {
            machineMap.delete(machineId);
        }
    }
}
function renderWatchTable(machineMap, params) {
    console.clear();
    const subtitle = [
        params.mode === 'watch' ? 'Live mode' : 'Snapshot mode',
        params.machineId ? `machine=${params.machineId}` : undefined,
        params.deploymentId ? `deployment=${params.deploymentId}` : undefined,
        params.distressed ? 'distressed=true' : undefined,
    ]
        .filter(Boolean)
        .join(' • ');
    tui.header('Cloud Monitor');
    if (subtitle) {
        tui.info(subtitle);
    }
    if (params.lastMessage && params.lastMessage.type === 'state_change') {
        tui.warning(`${params.lastMessage.machineId}: ${params.lastMessage.previousHealth} -> ${params.lastMessage.health}`);
    }
    renderMachineTable(Array.from(machineMap.values()));
    tui.info('Press Ctrl+C to stop watching.');
}
function renderMachineTable(machines) {
    if (machines.length === 0) {
        tui.info('No machines found');
        return;
    }
    const rows = machines
        .slice()
        .sort((a, b) => a.machineId.localeCompare(b.machineId))
        .map((machine) => ({
        Machine: machine.machineId,
        Health: formatHealth(machine.health),
        CPU: formatPercent(machine.report?.host?.cpu?.usagePercent),
        Memory: formatPercent(machine.report?.host?.memory?.usagePercent),
        Disk: formatPercent(maxDiskUsage(machine.report?.host?.disks)),
        Pressure: formatScore(machine.compositeScore),
        Containers: `${machine.report?.capacity?.runningContainers ?? 0}/${machine.report?.capacity?.totalContainers ?? 0}`,
        'Last Report': formatAge(machine.reportedAt),
    }));
    tui.table(rows, [
        { name: 'Machine' },
        { name: 'Health' },
        { name: 'CPU', alignment: 'right' },
        { name: 'Memory', alignment: 'right' },
        { name: 'Disk', alignment: 'right' },
        { name: 'Pressure', alignment: 'right' },
        { name: 'Containers', alignment: 'right' },
        { name: 'Last Report' },
    ]);
}
function formatHealth(health) {
    if (health === 'CONNECTED')
        return '● CONNECTED';
    if (health === 'STALE')
        return '◌ STALE';
    if (health === 'DISCONNECTED')
        return '○ DISCONNECTED';
    return health;
}
function formatPercent(value) {
    if (value === undefined || value === null || Number.isNaN(value)) {
        return '-';
    }
    return `${value.toFixed(1)}%`;
}
function formatScore(score) {
    if (score === undefined || score === null || Number.isNaN(score)) {
        return '-';
    }
    if (score >= 0.85) {
        return `${score.toFixed(2)} ⚠`;
    }
    return score.toFixed(2);
}
function maxDiskUsage(disks) {
    if (!disks || disks.length === 0) {
        return undefined;
    }
    return Math.max(...disks.map((d) => d.usagePercent));
}
function formatAge(timestamp) {
    const date = new Date(timestamp);
    const time = date.getTime();
    if (Number.isNaN(time)) {
        return '-';
    }
    const diff = Date.now() - time;
    if (diff < 60_000)
        return `${Math.max(0, Math.floor(diff / 1000))}s ago`;
    if (diff < 3_600_000)
        return `${Math.floor(diff / 60_000)}m ago`;
    if (diff < 86_400_000)
        return `${Math.floor(diff / 3_600_000)}h ago`;
    return `${Math.floor(diff / 86_400_000)}d ago`;
}
function usecToISO(us) {
    if (us === undefined || us <= 0 || Number.isNaN(us)) {
        return undefined;
    }
    return new Date(Math.floor(us / 1000)).toISOString();
}
//# sourceMappingURL=monitor.js.map