import { z } from "zod";
import { apiClient, stripSensitiveData, withProviderErrorHandling, assertValidServerId, uploadSshKeyWithConflict } from "./base.js";
import { withRetry } from "../utils/retry.js";
import { formatSnapshotCost } from "../constants.js";
export const HetznerServerSchema = z.object({
    server: z.object({
        id: z.number(),
        status: z.string(),
        public_net: z.object({
            ipv4: z.object({ ip: z.string() }).nullable().optional(),
        }).optional(),
    }),
});
function extractHetznerError(data) {
    const d = data;
    return d?.error?.message;
}
export class HetznerProvider {
    name = "hetzner";
    displayName = "Hetzner Cloud";
    apiToken;
    baseUrl = "https://api.hetzner.cloud/v1";
    constructor(apiToken) {
        this.apiToken = apiToken;
    }
    async validateToken(token) {
        try {
            await withRetry(async () => {
                await apiClient.get(`${this.baseUrl}/servers`, {
                    headers: { Authorization: `Bearer ${token}` },
                });
            });
            return true;
        }
        catch {
            return false;
        }
    }
    async uploadSshKey(name, publicKey) {
        return uploadSshKeyWithConflict(name, publicKey, {
            apiToken: this.apiToken,
            baseUrl: this.baseUrl,
            createPath: "/ssh_keys",
            bodyKeyField: "public_key",
            listPath: "/ssh_keys?per_page=200",
            listArrayField: "ssh_keys",
            listKeyField: "public_key",
            conflictStatuses: [409],
        });
    }
    async createServer(config) {
        return withProviderErrorHandling("create server", async () => {
            const body = {
                name: config.name,
                server_type: config.size,
                location: config.region,
                image: "ubuntu-24.04",
                user_data: config.cloudInit,
            };
            if (config.sshKeyIds?.length) {
                body.ssh_keys = config.sshKeyIds.map(Number);
            }
            const response = await apiClient.post(`${this.baseUrl}/servers`, body, {
                headers: {
                    Authorization: `Bearer ${this.apiToken}`,
                    "Content-Type": "application/json",
                },
            });
            const parsed = HetznerServerSchema.parse(response.data);
            return {
                id: parsed.server.id.toString(),
                ip: parsed.server.public_net?.ipv4?.ip || "pending",
                status: parsed.server.status,
            };
        }, extractHetznerError);
    }
    async getServerDetails(serverId) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("get server details", () => withRetry(async () => {
            const response = await apiClient.get(`${this.baseUrl}/servers/${serverId}`, {
                headers: { Authorization: `Bearer ${this.apiToken}` },
            });
            const parsed = HetznerServerSchema.parse(response.data);
            return {
                id: parsed.server.id.toString(),
                ip: parsed.server.public_net?.ipv4?.ip || "pending",
                status: parsed.server.status,
            };
        }));
    }
    async getServerStatus(serverId) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("get server status", () => withRetry(async () => {
            const response = await apiClient.get(`${this.baseUrl}/servers/${serverId}`, {
                headers: { Authorization: `Bearer ${this.apiToken}` },
            });
            const parsed = HetznerServerSchema.parse(response.data);
            return parsed.server.status;
        }));
    }
    async destroyServer(serverId) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("destroy server", async () => {
            await apiClient.delete(`${this.baseUrl}/servers/${serverId}`, {
                headers: { Authorization: `Bearer ${this.apiToken}` },
            });
        }, extractHetznerError);
    }
    async rebootServer(serverId) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("reboot server", async () => {
            await apiClient.post(`${this.baseUrl}/servers/${serverId}/actions/reboot`, {}, {
                headers: {
                    Authorization: `Bearer ${this.apiToken}`,
                    "Content-Type": "application/json",
                },
            });
        }, extractHetznerError);
    }
    getRegions() {
        return [
            { id: "nbg1", name: "Nuremberg", location: "Germany" },
            { id: "fsn1", name: "Falkenstein", location: "Germany" },
            { id: "hel1", name: "Helsinki", location: "Finland" },
            { id: "ash", name: "Ashburn", location: "USA" },
            { id: "sin1", name: "Singapore", location: "Singapore" },
        ];
    }
    getServerSizes() {
        return [
            { id: "cax11", name: "CAX11", vcpu: 2, ram: 4, disk: 40, price: "€3.79/mo" },
            { id: "cx23", name: "CX23", vcpu: 2, ram: 4, disk: 40, price: "€3.49/mo" },
            { id: "cax21", name: "CAX21", vcpu: 4, ram: 8, disk: 80, price: "€6.49/mo" },
            { id: "cx33", name: "CX33", vcpu: 4, ram: 8, disk: 80, price: "€5.49/mo" },
        ];
    }
    async getAvailableLocations() {
        try {
            return await withRetry(async () => {
                const response = await apiClient.get(`${this.baseUrl}/locations`, {
                    headers: { Authorization: `Bearer ${this.apiToken}` },
                });
                return response.data.locations.map((loc) => ({
                    id: loc.name,
                    name: loc.city,
                    location: loc.country,
                }));
            });
        }
        catch (error) {
            stripSensitiveData(error);
            return this.getRegions();
        }
    }
    async getAvailableServerTypes(location, mode) {
        try {
            return await withRetry(async () => {
                // Get actually available server type IDs from datacenter
                const dcResponse = await apiClient.get(`${this.baseUrl}/datacenters`, {
                    headers: { Authorization: `Bearer ${this.apiToken}` },
                });
                // Merge available IDs from ALL datacenters in same location (e.g. nbg1-dc3, nbg1-dc4)
                // A single location can have multiple DCs with different server types (ARM vs x86)
                const availableIds = dcResponse.data.datacenters
                    .filter((dc) => dc.location.name === location)
                    .flatMap((dc) => dc.server_types?.available || []);
                // Get server type details
                const response = await apiClient.get(`${this.baseUrl}/server_types`, {
                    headers: { Authorization: `Bearer ${this.apiToken}` },
                });
                const MIN_RAM_GB = mode === "bare" ? 0 : 2; // Coolify requires at least 2GB RAM, bare has no minimum
                const types = response.data.server_types.filter((type) => !type.deprecation &&
                    type.memory >= MIN_RAM_GB &&
                    availableIds.includes(type.id) &&
                    type.prices.some((p) => p.location === location));
                if (types.length === 0) {
                    return this.getServerSizes();
                }
                return types.map((type) => {
                    const price = type.prices.find((p) => p.location === location);
                    const rawPrice = price?.price_monthly?.net;
                    const priceMonthly = rawPrice ? parseFloat(rawPrice).toFixed(2) : "N/A";
                    return {
                        id: type.name,
                        name: type.name.toUpperCase(),
                        vcpu: type.cores,
                        ram: type.memory,
                        disk: type.disk,
                        price: `€${priceMonthly}/mo`,
                    };
                });
            });
        }
        catch (error) {
            stripSensitiveData(error);
            return this.getServerSizes();
        }
    }
    async createSnapshot(serverId, name) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("create snapshot", async () => {
            const response = await apiClient.post(`${this.baseUrl}/servers/${serverId}/actions/create_image`, { type: "snapshot", description: name }, {
                headers: {
                    Authorization: `Bearer ${this.apiToken}`,
                    "Content-Type": "application/json",
                },
            });
            const image = response.data.image;
            return {
                id: image.id.toString(),
                serverId,
                name: image.description || name,
                status: image.status,
                sizeGb: image.image_size || 0,
                createdAt: image.created,
                costPerMonth: formatSnapshotCost("hetzner", image.image_size || 0),
            };
        }, extractHetznerError);
    }
    async listSnapshots(serverId) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("list snapshots", () => withRetry(async () => {
            const response = await apiClient.get(`${this.baseUrl}/images?type=snapshot&sort=created:desc&per_page=100`, { headers: { Authorization: `Bearer ${this.apiToken}` } });
            const images = response.data.images.filter((img) => img.created_from && img.created_from.id === Number(serverId));
            return images.map((img) => ({
                id: img.id.toString(),
                serverId,
                name: img.description || "",
                status: img.status,
                sizeGb: img.image_size || 0,
                createdAt: img.created,
                costPerMonth: formatSnapshotCost("hetzner", img.image_size || 0),
            }));
        }), extractHetznerError);
    }
    async deleteSnapshot(snapshotId) {
        assertValidServerId(snapshotId);
        return withProviderErrorHandling("delete snapshot", async () => {
            await apiClient.delete(`${this.baseUrl}/images/${snapshotId}`, {
                headers: { Authorization: `Bearer ${this.apiToken}` },
            });
        }, extractHetznerError);
    }
    async restoreSnapshot(serverId, snapshotId) {
        assertValidServerId(serverId);
        assertValidServerId(snapshotId);
        return withProviderErrorHandling("restore snapshot", async () => {
            await apiClient.post(`${this.baseUrl}/servers/${serverId}/actions/rebuild`, { image: Number(snapshotId) }, { headers: { Authorization: `Bearer ${this.apiToken}`, "Content-Type": "application/json" } });
        }, extractHetznerError);
    }
    async getSnapshotCostEstimate(serverId) {
        assertValidServerId(serverId);
        return withProviderErrorHandling("get snapshot cost", () => withRetry(async () => {
            const response = await apiClient.get(`${this.baseUrl}/servers/${serverId}`, {
                headers: { Authorization: `Bearer ${this.apiToken}` },
            });
            const diskGb = response.data.server.server_type.disk;
            return formatSnapshotCost("hetzner", diskGb);
        }), extractHetznerError);
    }
    async findServerByIp(ip) {
        return withProviderErrorHandling("find server by IP", async () => {
            const response = await apiClient.get(`${this.baseUrl}/servers?per_page=100`, {
                headers: { Authorization: `Bearer ${this.apiToken}` },
            });
            const servers = response.data.servers;
            const found = servers.find((server) => server.public_net?.ipv4?.ip === ip);
            return found ? found.id.toString() : null;
        }, extractHetznerError);
    }
}
//# sourceMappingURL=hetzner.js.map