"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var ScalewayInstancesAdapter_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScalewayInstancesAdapter = exports.INSTANCE_ZONES = void 0;
const common_1 = require("@nestjs/common");
const axios_1 = __importDefault(require("axios"));
const instances_1 = require("./generated/instances");
exports.INSTANCE_ZONES = [
    instances_1.CreateServerZoneEnum.FrPar1,
    instances_1.CreateServerZoneEnum.FrPar2,
    instances_1.CreateServerZoneEnum.FrPar3,
    instances_1.CreateServerZoneEnum.NlAms1,
    instances_1.CreateServerZoneEnum.NlAms2,
    instances_1.CreateServerZoneEnum.NlAms3,
    instances_1.CreateServerZoneEnum.PlWaw1,
    instances_1.CreateServerZoneEnum.PlWaw2,
    instances_1.CreateServerZoneEnum.PlWaw3,
];
let ScalewayInstancesAdapter = ScalewayInstancesAdapter_1 = class ScalewayInstancesAdapter {
    constructor() {
        this.logger = new common_1.Logger(ScalewayInstancesAdapter_1.name);
        this.baseUrl = 'https://api.scaleway.com';
        this.timeout = 15000;
    }
    createAxiosInstance() {
        return axios_1.default.create({
            timeout: this.timeout,
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                'User-Agent': 'Flui-Cloud-API/1.0',
            },
            validateStatus: (status) => status >= 200 && status < 300,
        });
    }
    createConfiguration(token) {
        return new instances_1.Configuration({
            basePath: this.baseUrl,
            baseOptions: {
                headers: {
                    'X-Auth-Token': token,
                    'User-Agent': 'Flui-Cloud-API/1.0',
                    'Content-Type': 'application/json',
                    Accept: 'application/json',
                },
            },
        });
    }
    createInstancesApi(token) {
        return new instances_1.InstancesApi(this.createConfiguration(token), this.baseUrl, this.createAxiosInstance());
    }
    createInstanceTypesApi(token) {
        return new instances_1.InstanceTypesApi(this.createConfiguration(token), this.baseUrl, this.createAxiosInstance());
    }
    createSecurityGroupsApi(token) {
        return new instances_1.SecurityGroupsApi(this.createConfiguration(token), this.baseUrl, this.createAxiosInstance());
    }
    createPrivateNicsApi(token) {
        return new instances_1.PrivateNICsApi(this.createConfiguration(token), this.baseUrl, this.createAxiosInstance());
    }
    /**
     * Resolve a Scaleway IPAM IP UUID to its inline address (e.g. "10.10.1.3").
     * IPv6 addresses are filtered out — callers want the v4 used by K3s join.
     */
    async resolveIpamIpv4(token, region, ipId) {
        const url = `${this.baseUrl}/ipam/v1/regions/${region}/ips/${ipId}`;
        try {
            const resp = await this.createAxiosInstance().get(url, {
                headers: { 'X-Auth-Token': token },
            });
            const data = resp.data;
            if (!data?.address || data.is_ipv6)
                return null;
            // Scaleway returns "10.10.1.3/24" — strip prefix length.
            return data.address.split('/')[0];
        }
        catch (err) {
            this.logger.warn(`[Scaleway] IPAM lookup ${ipId} failed: ${err.message}`);
            return null;
        }
    }
    /**
     * Resolve the IPv4 address of the first NIC attached to a Private Network.
     * Returns null if the server has no NIC or IPAM hasn't allocated an IPv4 yet.
     */
    async getInstancePrivateIp(token, zone, server) {
        const nics = server.private_nics ?? [];
        if (nics.length === 0)
            return null;
        const region = zone.replace(/-\d+$/, '');
        for (const nic of nics) {
            const inline = nic.private_ip;
            if (inline)
                return String(inline);
            const ipIds = nic.ipam_ip_ids ?? [];
            for (const ipId of ipIds) {
                const ipv4 = await this.resolveIpamIpv4(token, region, ipId);
                if (ipv4)
                    return ipv4;
            }
        }
        return null;
    }
    /**
     * Attach a Scaleway Instance to a Private Network by creating a Private NIC.
     * Idempotent: returns the existing NIC if one already attaches the server to
     * the same private network.
     */
    async attachToPrivateNetwork(token, zone, serverId, privateNetworkId) {
        const api = this.createPrivateNicsApi(token);
        const list = await api.listPrivateNICs(zone, serverId);
        const existing = (list.data?.private_nics ?? []).find((n) => n.private_network_id === privateNetworkId);
        if (existing) {
            this.logger.log(`[Scaleway] Server ${serverId} already attached to private network ${privateNetworkId} (NIC ${existing.id})`);
            return existing;
        }
        const resp = await api.createPrivateNIC(zone, serverId, { private_network_id: privateNetworkId });
        const nic = resp.data?.private_nic;
        if (!nic) {
            throw new Error(`createPrivateNIC for server ${serverId} returned no NIC payload`);
        }
        this.logger.log(`[Scaleway] Attached server ${serverId} to private network ${privateNetworkId} (NIC ${nic.id})`);
        return nic;
    }
    // ─── Server methods ────────────────────────────────────────────────────────
    async listServers(token, zone) {
        const api = this.createInstancesApi(token);
        const allServers = [];
        let page = 1;
        const perPage = 50;
        while (true) {
            const response = await api.listServers(zone, perPage, page);
            const servers = response.data.servers || [];
            allServers.push(...servers);
            const totalCount = Number.parseInt(response.headers?.['x-total-count'] || '0', 10);
            if (allServers.length >= totalCount || servers.length < perPage) {
                break;
            }
            page++;
        }
        return allServers;
    }
    async getServer(token, zone, serverId) {
        const api = this.createInstancesApi(token);
        const response = await api.getServer(zone, serverId);
        return response.data.server || null;
    }
    async createServer(token, zone, payload) {
        const api = this.createInstancesApi(token);
        const response = await api.createServer(zone, payload);
        return response.data.server;
    }
    async deleteServer(token, zone, serverId) {
        // Scaleway instances must be terminated via the 'terminate' action, not the DELETE API.
        // The DELETE API returns 400 if the server is not in 'stopped' state.
        // The 'terminate' action works from any state (running, stopped, etc.) and also deallocates volumes.
        await this.serverAction(token, zone, serverId, instances_1.ServerActionRequestActionEnum.Terminate);
    }
    async serverAction(token, zone, serverId, action) {
        const api = this.createInstancesApi(token);
        const request = { action };
        await api.serverAction(zone, serverId, request);
    }
    async updateServerTags(token, zone, serverId, tags) {
        const api = this.createInstancesApi(token);
        const request = { tags };
        await api.updateServer(zone, serverId, request);
    }
    /**
     * Change the commercial_type of a stopped Instance. Scaleway constraints:
     * - server must be in `stopped` state
     * - cannot be in a placement group
     * - cross-OS family (Linux↔Windows) not allowed
     * - local-storage requirements of the target type must be satisfied
     *
     * The caller is responsible for stopping the server beforehand and for
     * starting it after. Returns when the API accepts the request; the change
     * is applied during the next power-on.
     */
    async updateServerCommercialType(token, zone, serverId, commercialType) {
        const api = this.createInstancesApi(token);
        const request = { commercial_type: commercialType };
        await api.updateServer(zone, serverId, request);
    }
    async listServerTypes(token, zone) {
        const api = this.createInstanceTypesApi(token);
        const response = await api.listServersTypes(zone);
        const serversMap = response.data.servers;
        if (!serversMap)
            return [];
        return Object.entries(serversMap).map(([name, type]) => ({
            ...type,
            name,
        }));
    }
    // ─── User data (cloud-init) ────────────────────────────────────────────────
    /**
     * Set cloud-init user data on an instance.
     * The generated UserDataApi uses the browser File type — bypass with raw axios.
     */
    async setUserData(token, zone, serverId, cloudInit) {
        const url = `https://api.scaleway.com/instance/v1/zones/${zone}/servers/${serverId}/user_data/cloud-init`;
        await axios_1.default.patch(url, cloudInit, {
            headers: {
                'X-Auth-Token': token,
                'Content-Type': 'text/plain',
            },
            timeout: this.timeout,
        });
    }
    // ─── Security Group methods ────────────────────────────────────────────────
    async createSecurityGroup(token, zone, payload) {
        const api = this.createSecurityGroupsApi(token);
        try {
            const response = await api.createSecurityGroup(zone, payload);
            return response.data.security_group;
        }
        catch (error) {
            this.logger.error(`createSecurityGroup 400 body: ${JSON.stringify(error?.response?.data)}`);
            throw error;
        }
    }
    async getSecurityGroup(token, zone, sgId) {
        try {
            const api = this.createSecurityGroupsApi(token);
            const response = await api.getSecurityGroup(zone, sgId);
            return response.data.security_group || null;
        }
        catch (error) {
            if (error?.response?.status === 404)
                return null;
            throw error;
        }
    }
    async listSecurityGroups(token, zone) {
        const api = this.createSecurityGroupsApi(token);
        const response = await api.listSecurityGroups(zone);
        return response.data.security_groups || [];
    }
    async listSecurityGroupRules(token, zone, sgId) {
        const api = this.createSecurityGroupsApi(token);
        const all = [];
        let page = 1;
        // Scaleway paginates rules; per_page maxes at 100.
        // eslint-disable-next-line no-constant-condition
        while (true) {
            const response = await api.listSecurityGroupRules(zone, sgId, 100, page);
            const rules = response.data.rules || [];
            all.push(...rules);
            if (rules.length < 100)
                break;
            page++;
        }
        return all;
    }
    async setSecurityGroupRules(token, zone, sgId, rules) {
        const api = this.createSecurityGroupsApi(token);
        const request = { rules };
        await api.setSecurityGroupRules(zone, sgId, request);
    }
    async deleteSecurityGroup(token, zone, sgId) {
        const api = this.createSecurityGroupsApi(token);
        await api.deleteSecurityGroup(zone, sgId);
    }
    async applySecurityGroupToServer(token, zone, serverId, sgId) {
        const api = this.createInstancesApi(token);
        const securityGroup = { id: sgId };
        const request = { security_group: securityGroup };
        await api.updateServer(zone, serverId, request);
    }
    async removeSecurityGroupFromServer(token, zone, serverId) {
        const api = this.createInstancesApi(token);
        const request = { security_group: undefined };
        await api.updateServer(zone, serverId, request);
    }
};
exports.ScalewayInstancesAdapter = ScalewayInstancesAdapter;
exports.ScalewayInstancesAdapter = ScalewayInstancesAdapter = ScalewayInstancesAdapter_1 = __decorate([
    (0, common_1.Injectable)()
], ScalewayInstancesAdapter);
