import { z } from "zod";
import { getServers, findServer } from "../../utils/config.js";
import { checkServerStatus, checkAllServersStatus } from "../../core/status.js";
import { getAdapter, resolvePlatform } from "../../adapters/factory.js";
import { getProviderToken, collectProviderTokensFromEnv } from "../../core/tokens.js";
import { getErrorMessage, sanitizeStderr } from "../../utils/errorMapper.js";
import { isBareServer } from "../../utils/modeGuard.js";
import { sshExec, isHostKeyMismatch } from "../../utils/ssh.js";
import { raw } from "../../utils/sshCommand.js";
import { createProviderWithToken } from "../../utils/providerFactory.js";
import { mcpSuccess, mcpError } from "../utils.js";
import { SUPPORTED_PROVIDERS, COOLIFY_PORT, DOKPLOY_PORT } from "../../constants.js";
export const serverInfoSchema = {
    action: z.enum(["list", "status", "health", "sizes"]).describe("Action to perform: 'list' all servers, 'status' check server/cloud status, 'health' check Coolify reachability (or SSH reachability for bare servers), 'sizes' list available server types with prices for a provider+region"),
    server: z.string().optional().describe("Server name or IP. Required for single-server status/health. Omit for all servers."),
    provider: z.enum(SUPPORTED_PROVIDERS).optional().describe("Cloud provider (required for 'sizes' action)"),
    region: z.string().optional().describe("Region/location ID (required for 'sizes' action, e.g. 'nbg1' for Hetzner, 'fra1' for DigitalOcean)"),
    mode: z.enum(["coolify", "bare"]).optional().describe("Server mode filter for 'sizes' action. Coolify requires min 2GB RAM. Default: coolify"),
};
// ─── Output Schema ────────────────────────────────────────────────────────────
const serverInfoListOutputSchema = z.object({
    servers: z.array(z.object({
        name: z.string(),
        ip: z.string(),
        provider: z.string(),
        region: z.string(),
        size: z.string(),
        id: z.string(),
        mode: z.string(),
        createdAt: z.string(),
    })),
    total: z.number(),
    message: z.string().optional(),
    suggested_actions: z.array(z.object({ command: z.string(), reason: z.string() })),
});
const serverInfoStatusOutputSchema = z.object({
    results: z.array(z.object({
        name: z.string(),
        ip: z.string(),
        provider: z.string(),
        region: z.string(),
        size: z.string(),
        mode: z.string(),
        serverStatus: z.string(),
        platformStatus: z.string(),
        error: z.string().optional(),
    })),
    summary: z.object({
        total: z.number(),
        running: z.number(),
        notReachable: z.number(),
        errors: z.number(),
    }),
    suggested_actions: z.array(z.object({ command: z.string(), reason: z.string() })),
});
const serverInfoHealthOutputSchema = z.object({
    results: z.array(z.record(z.string(), z.unknown())),
    summary: z.object({
        total: z.number(),
        running: z.number(),
        notReachable: z.number(),
        bare: z.number(),
    }),
    suggested_actions: z.array(z.object({ command: z.string(), reason: z.string() })),
});
const serverInfoSizesOutputSchema = z.object({
    provider: z.string(),
    region: z.string(),
    mode: z.string(),
    sizes: z.array(z.object({
        id: z.string(),
        name: z.string(),
        vcpu: z.number(),
        ram: z.string(),
        disk: z.string(),
        price: z.string(),
    })),
    total: z.number(),
    suggested_actions: z.array(z.object({ command: z.string(), reason: z.string() })),
});
export const serverInfoOutputSchema = z.object({
    result: z.discriminatedUnion("action", [
        z.object({ action: z.literal("list") }).merge(serverInfoListOutputSchema),
        z.object({ action: z.literal("status") }).merge(serverInfoStatusOutputSchema),
        z.object({ action: z.literal("health") }).merge(serverInfoHealthOutputSchema),
        z.object({ action: z.literal("sizes") }).merge(serverInfoSizesOutputSchema),
    ]),
});
function formatServerList(servers) {
    if (servers.length === 0) {
        return {
            servers: [],
            total: 0,
            message: "No servers found. Deploy one with: kastell init",
            suggested_actions: [
                { command: "kastell init", reason: "Deploy your first Coolify server" },
            ],
        };
    }
    return {
        servers: servers.map((s) => ({
            name: s.name,
            ip: s.ip,
            provider: s.provider,
            region: s.region,
            size: s.size,
            id: s.id,
            mode: resolvePlatform(s) ?? s.mode ?? "coolify",
            createdAt: s.createdAt,
        })),
        total: servers.length,
        suggested_actions: [
            { command: "server_info { action: 'status' }", reason: "Check status of all servers" },
            { command: "server_info { action: 'health' }", reason: "Check health on all servers" },
        ],
    };
}
function formatStatusResult(result) {
    return {
        name: result.server.name,
        ip: result.server.ip,
        provider: result.server.provider,
        region: result.server.region,
        size: result.server.size,
        mode: resolvePlatform(result.server) ?? result.server.mode ?? "coolify",
        serverStatus: result.serverStatus,
        platformStatus: result.platformStatus,
        ...(result.error ? { error: result.error } : {}),
    };
}
function formatStatusResults(results) {
    const suggestedActions = [];
    const notReachable = results.filter((r) => r.platformStatus === "not reachable");
    if (notReachable.length > 0) {
        for (const r of notReachable) {
            suggestedActions.push({
                command: `kastell status ${r.server.name} --autostart`,
                reason: `Coolify is not reachable on ${r.server.name}, try auto-restart`,
            });
        }
    }
    const errors = results.filter((r) => r.error);
    if (errors.length > 0) {
        suggestedActions.push({
            command: "kastell doctor --check-tokens",
            reason: "API errors detected, verify provider tokens",
        });
    }
    if (suggestedActions.length === 0) {
        suggestedActions.push({
            command: "server_logs { action: 'logs' }",
            reason: "All servers healthy, check logs for details",
        });
    }
    return {
        results: results.map(formatStatusResult),
        summary: {
            total: results.length,
            running: results.filter((r) => r.platformStatus === "running").length,
            notReachable: notReachable.length,
            errors: errors.length,
        },
        suggested_actions: suggestedActions,
    };
}
async function checkBareServerSsh(server) {
    try {
        const result = await sshExec(server.ip, raw("echo ok"));
        // Check stdout for "ok" as primary indicator — SSH banners can cause
        // non-zero exit codes on some Windows SSH binaries even when the
        // command succeeds (banner text goes to stderr, exit code becomes 1).
        if (result.code === 0 || result.stdout.trim() === "ok") {
            return { reachable: true, hostKeyMismatch: false };
        }
        if (isHostKeyMismatch(result.stderr)) {
            return { reachable: false, hostKeyMismatch: true };
        }
        return { reachable: false, hostKeyMismatch: false };
    }
    catch {
        return { reachable: false, hostKeyMismatch: false };
    }
}
export async function handleServerInfo(params) {
    try {
        switch (params.action) {
            case "list": {
                const servers = getServers();
                return mcpSuccess({ action: "list", ...formatServerList(servers) });
            }
            case "status": {
                const servers = getServers();
                if (servers.length === 0) {
                    return mcpError("No servers found", undefined, [{ command: "kastell init", reason: "Deploy a server first" }]);
                }
                if (params.server) {
                    const server = findServer(params.server);
                    if (!server) {
                        return {
                            content: [{ type: "text", text: JSON.stringify({
                                        error: `Server not found: ${params.server}`,
                                        available_servers: servers.map((s) => s.name),
                                    }) }],
                            isError: true,
                        };
                    }
                    const token = server.id.startsWith("manual-") ? "" : (getProviderToken(server.provider) ?? "");
                    if (!token && !server.id.startsWith("manual-")) {
                        return {
                            content: [{ type: "text", text: JSON.stringify({
                                        error: `No API token found for provider: ${server.provider}`,
                                        hint: `Set environment variable: ${server.provider.toUpperCase()}_TOKEN`,
                                        server: { name: server.name, ip: server.ip, provider: server.provider },
                                    }) }],
                            isError: true,
                        };
                    }
                    const result = await checkServerStatus(server, token);
                    return mcpSuccess({ action: "status", ...formatStatusResults([result]) });
                }
                // All servers
                const tokenMap = collectProviderTokensFromEnv(servers);
                const missingTokenProviders = [
                    ...new Set(servers.filter((s) => !s.id.startsWith("manual-")).map((s) => s.provider)),
                ].filter((p) => !tokenMap.has(p));
                if (missingTokenProviders.length > 0) {
                    return {
                        content: [{ type: "text", text: JSON.stringify({
                                    error: "Missing API tokens for providers",
                                    missing: missingTokenProviders.map((p) => ({
                                        provider: p,
                                        envVar: `${p.toUpperCase()}_TOKEN`,
                                    })),
                                    hint: "Set environment variables for each provider",
                                }) }],
                        isError: true,
                    };
                }
                const results = await checkAllServersStatus(servers, tokenMap);
                return mcpSuccess({ action: "status", ...formatStatusResults(results) });
            }
            case "health": {
                const servers = getServers();
                if (servers.length === 0) {
                    return mcpError("No servers found", undefined, [{ command: "kastell init", reason: "Deploy a server first" }]);
                }
                if (params.server) {
                    const server = findServer(params.server);
                    if (!server) {
                        return {
                            content: [{ type: "text", text: JSON.stringify({
                                        error: `Server not found: ${params.server}`,
                                        available_servers: servers.map((s) => s.name),
                                    }) }],
                            isError: true,
                        };
                    }
                    // Bare server: check SSH reachability
                    if (isBareServer(server)) {
                        const sshResult = await checkBareServerSsh(server);
                        const suggestedActions = [];
                        if (sshResult.hostKeyMismatch) {
                            suggestedActions.push({
                                command: `ssh-keygen -R ${server.ip}`,
                                reason: "Remove stale host key to fix SSH connection",
                            });
                        }
                        else if (sshResult.reachable) {
                            suggestedActions.push({ command: `ssh root@${server.ip}`, reason: "Connect to your bare server" });
                        }
                        else {
                            suggestedActions.push({ command: `kastell status ${server.name}`, reason: "Check server cloud status" });
                        }
                        return mcpSuccess({
                            action: "health",
                            server: server.name,
                            ip: server.ip,
                            mode: "bare",
                            sshReachable: sshResult.reachable,
                            ...(sshResult.hostKeyMismatch ? { hostKeyMismatch: true } : {}),
                            suggested_actions: suggestedActions,
                        });
                    }
                    // Platform server: use adapter health check
                    const platform = resolvePlatform(server);
                    if (!platform) {
                        return mcpSuccess({ action: "health", server: server.name, ip: server.ip, platformStatus: "unknown" });
                    }
                    const adapter = getAdapter(platform);
                    const healthResult = await adapter.healthCheck(server.ip, server.domain);
                    const port = platform === "dokploy" ? DOKPLOY_PORT : COOLIFY_PORT;
                    const suggestedActions = healthResult.status === "not reachable"
                        ? [{ command: `kastell status ${server.name} --autostart`, reason: `Try auto-restart ${platform}` }]
                        : [{ command: `http://${server.ip}:${port}`, reason: `Access ${platform} dashboard` }];
                    return mcpSuccess({
                        action: "health",
                        server: server.name,
                        ip: server.ip,
                        platformStatus: healthResult.status,
                        [`${platform}Url`]: healthResult.status === "running" ? `http://${server.ip}:${port}` : null,
                        suggested_actions: suggestedActions,
                    });
                }
                // All servers health — route based on mode
                const healthResults = await Promise.all(servers.map(async (s) => {
                    if (isBareServer(s)) {
                        const sshResult = await checkBareServerSsh(s);
                        return {
                            name: s.name,
                            ip: s.ip,
                            mode: "bare",
                            sshReachable: sshResult.reachable,
                            ...(sshResult.hostKeyMismatch ? { hostKeyMismatch: true } : {}),
                        };
                    }
                    const plat = resolvePlatform(s);
                    const platStatus = plat
                        ? (await getAdapter(plat).healthCheck(s.ip, s.domain)).status
                        : "unknown";
                    return {
                        name: s.name,
                        ip: s.ip,
                        mode: plat ?? s.mode ?? "coolify",
                        platform: plat ?? "coolify",
                        platformStatus: platStatus,
                    };
                }));
                const coolifyResults = healthResults.filter((r) => r.mode === "coolify");
                const bareResults = healthResults.filter((r) => r.mode === "bare");
                const notReachableCoolify = coolifyResults.filter((r) => "platformStatus" in r && r.platformStatus === "not reachable");
                const suggestedActions = notReachableCoolify.length > 0
                    ? notReachableCoolify.map((r) => ({
                        command: `kastell status ${r.name} --autostart`,
                        reason: `Coolify not reachable on ${r.name}`,
                    }))
                    : [{ command: "server_info { action: 'status' }", reason: "All healthy, check full status" }];
                return mcpSuccess({
                    action: "health",
                    results: healthResults,
                    summary: {
                        total: healthResults.length,
                        running: coolifyResults.filter((r) => "platformStatus" in r && r.platformStatus === "running").length,
                        notReachable: notReachableCoolify.length,
                        bare: bareResults.length,
                    },
                    suggested_actions: suggestedActions,
                });
            }
            case "sizes": {
                if (!params.provider) {
                    return mcpError("Provider is required for 'sizes' action", "Specify provider: 'hetzner', 'digitalocean', 'vultr', or 'linode'");
                }
                if (!params.region) {
                    return mcpError("Region is required for 'sizes' action", "Specify region (e.g. 'nbg1' for Hetzner, 'fra1' for DigitalOcean, 'ewr' for Vultr, 'us-east' for Linode)");
                }
                const token = getProviderToken(params.provider);
                if (!token) {
                    return mcpError(`No API token found for provider: ${params.provider}`, `Set environment variable: ${params.provider.toUpperCase()}_TOKEN`);
                }
                const provider = createProviderWithToken(params.provider, token);
                const mode = params.mode ?? "coolify";
                const sizes = await provider.getAvailableServerTypes(params.region, mode);
                return mcpSuccess({
                    action: "sizes",
                    provider: params.provider,
                    region: params.region,
                    mode,
                    sizes: sizes.map((s) => ({
                        id: s.id,
                        name: s.name,
                        vcpu: s.vcpu,
                        ram: `${s.ram}GB`,
                        disk: `${s.disk}GB`,
                        price: s.price,
                    })),
                    total: sizes.length,
                    suggested_actions: [
                        {
                            command: `server_provision { provider: '${params.provider}', name: 'my-server', region: '${params.region}', size: '${sizes[0]?.id ?? "..."}', mode: '${mode}' }`,
                            reason: "Provision a server with one of these sizes",
                        },
                    ],
                });
            }
            default:
                return mcpError(`Invalid action: ${String(params.action)}`, "Valid actions: list, status, health, sizes");
        }
    }
    catch (error) {
        return mcpError(sanitizeStderr(getErrorMessage(error)));
    }
}
//# sourceMappingURL=serverInfo.js.map