"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var HetznerFirewallService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HetznerFirewallService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const generated_1 = require("../../../modules/providers/implementations/hetzner/generated");
const cloud_provider_enum_1 = require("../enums/cloud-provider.enum");
const label_service_1 = require("../../common/services/label.service");
const axios_1 = __importDefault(require("axios"));
const https = __importStar(require("node:https"));
let HetznerFirewallService = HetznerFirewallService_1 = class HetznerFirewallService {
    constructor(configService, credentialProvider, labelService) {
        this.configService = configService;
        this.credentialProvider = credentialProvider;
        this.labelService = labelService;
        this.logger = new common_1.Logger(HetznerFirewallService_1.name);
        this.basePath = this.configService.get('HETZNER_API_BASE_PATH', 'https://api.hetzner.cloud/v1');
        this.timeout = Number.parseInt(this.configService.get('HETZNER_TIMEOUT', '10000'), 10);
    }
    /**
     * Create custom Axios instance with optimized configuration
     */
    createAxiosInstance() {
        const httpsAgent = new https.Agent({
            keepAlive: true,
            keepAliveMsecs: 30000,
            maxSockets: 50,
            maxFreeSockets: 10,
            timeout: this.timeout,
            rejectUnauthorized: true,
            family: 4,
        });
        const instance = axios_1.default.create({
            timeout: this.timeout,
            httpsAgent,
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                'User-Agent': 'Flui-Cloud-API/1.0',
                Connection: 'keep-alive',
            },
            validateStatus: (status) => status >= 200 && status < 300,
        });
        // Request interceptor for logging
        instance.interceptors.request.use((config) => {
            this.logger.debug(`Hetzner Firewall API Request: ${config.method?.toUpperCase()} ${config.url}`);
            return config;
        }, (error) => {
            this.logger.error('Hetzner Firewall API Request Error:', error);
            return Promise.reject(error);
        });
        // Response interceptor for logging
        instance.interceptors.response.use((response) => {
            this.logger.debug(`Hetzner Firewall API Response: ${response.status} ${response.statusText}`);
            return response;
        }, (error) => {
            if (error.response) {
                this.logger.error(`Hetzner Firewall API Error Response: ${error.response.status} ${error.response.statusText}`);
                this.logger.error(`Error Data: ${JSON.stringify(error.response.data, null, 2)}`);
            }
            else if (error.request) {
                this.logger.error(`Hetzner Firewall API No Response Received - Possible network/timeout issue`);
            }
            else {
                this.logger.error(`Hetzner Firewall API Error: ${error.message}`);
            }
            return Promise.reject(error);
        });
        return instance;
    }
    /**
     * Create configured API client with proper headers
     */
    async createConfiguration() {
        const token = await this.credentialProvider.getActiveApiToken(cloud_provider_enum_1.CloudProvider.HETZNER);
        return new generated_1.Configuration({
            accessToken: token,
            basePath: this.basePath,
            baseOptions: {
                headers: {
                    'User-Agent': 'Flui-Cloud-API/1.0',
                    'Content-Type': 'application/json',
                    Accept: 'application/json',
                },
            },
        });
    }
    /**
     * Create FirewallsApi instance with custom Axios configuration
     */
    async createFirewallsApi() {
        const configuration = await this.createConfiguration();
        const axiosInstance = this.createAxiosInstance();
        return new generated_1.FirewallsApi(configuration, this.basePath, axiosInstance);
    }
    /**
     * Create FirewallActionsApi instance with custom Axios configuration
     */
    async createFirewallActionsApi() {
        const configuration = await this.createConfiguration();
        const axiosInstance = this.createAxiosInstance();
        return new generated_1.FirewallActionsApi(configuration, this.basePath, axiosInstance);
    }
    /**
     * Create ServersApi instance with custom Axios configuration
     */
    async createServersApi() {
        const configuration = await this.createConfiguration();
        const axiosInstance = this.createAxiosInstance();
        return new generated_1.ServersApi(configuration, this.basePath, axiosInstance);
    }
    async createFirewall(config) {
        this.logger.log(`Creating firewall ${config.name}`);
        try {
            const firewallsApi = await this.createFirewallsApi();
            // Validate and transform rules to Hetzner API format
            const hetznerRules = this.validateAndTransformRules(config.rules);
            const createRequest = {
                name: config.name,
                labels: this.labelService.toRecord(config.labels),
                rules: hetznerRules,
            };
            // Apply to servers via label selector if provided
            if (config.applyToLabelSelector) {
                createRequest.apply_to = [
                    {
                        type: 'label_selector',
                        label_selector: { selector: config.applyToLabelSelector },
                    },
                ];
            }
            else if (config.applyToServerIds &&
                config.applyToServerIds.length > 0) {
                createRequest.apply_to = config.applyToServerIds.map((id) => ({
                    type: 'server',
                    server: { id: Number.parseInt(id) },
                }));
            }
            this.logger.log(`Sending create firewall request to Hetzner API for ${config.name}`);
            const response = await firewallsApi.createFirewall(createRequest);
            const result = {
                firewallId: response.data.firewall.id.toString(),
                appliedToServerIds: config.applyToServerIds,
            };
            this.logger.log(`Firewall ${config.name} created with ID ${response.data.firewall.id}`, result);
            return result;
        }
        catch (error) {
            this.logger.error(`Failed to create firewall ${config.name}`, error);
            if (error.response?.data?.error) {
                const apiError = error.response.data.error;
                throw new Error(`Hetzner API Error: ${apiError.message} (${apiError.code})`);
            }
            throw new Error(`Firewall creation failed: ${error.message}`);
        }
    }
    async updateFirewallRules(firewallId, rules) {
        this.logger.log(`Updating firewall ${firewallId} rules`);
        try {
            const actionsApi = await this.createFirewallActionsApi();
            // Validate and transform rules to Hetzner API format
            const hetznerRules = this.validateAndTransformRules(rules);
            await actionsApi.setFirewallRules(Number.parseInt(firewallId), {
                rules: hetznerRules,
            });
            this.logger.log(`Firewall ${firewallId} rules updated successfully`);
        }
        catch (error) {
            this.logger.error(`Failed to update firewall ${firewallId} rules`, error);
            if (error.response?.data?.error) {
                const apiError = error.response.data.error;
                throw new Error(`Hetzner API Error: ${apiError.message} (${apiError.code})`);
            }
            throw new Error(`Failed to update firewall rules: ${error.message}`);
        }
    }
    async getFirewall(firewallId) {
        try {
            const firewallsApi = await this.createFirewallsApi();
            const response = await firewallsApi.getFirewall(Number.parseInt(firewallId));
            const firewall = response.data.firewall;
            return {
                id: firewall.id.toString(),
                name: firewall.name,
                rules: firewall.rules.map((rule) => ({
                    description: rule.description || '',
                    direction: rule.direction,
                    protocol: rule.protocol,
                    port: rule.port,
                    sourceIps: rule.source_ips,
                    destinationIps: rule.destination_ips,
                })),
                labels: firewall.labels || {},
                appliedTo: firewall.applied_to.map((resource) => ({
                    serverId: resource.server?.id?.toString() || '',
                    serverName: undefined, // Not available in API response
                })),
            };
        }
        catch (error) {
            if (error.response?.status === 404) {
                this.logger.warn(`Firewall ${firewallId} not found`);
                return null;
            }
            this.logger.error(`Failed to get firewall ${firewallId}`, error);
            throw new Error(`Failed to get firewall: ${error.message}`);
        }
    }
    async listFirewalls(filters) {
        try {
            const firewallsApi = await this.createFirewallsApi();
            let labelSelector;
            if (filters?.clusterId) {
                labelSelector = `flui-cluster-id=${filters.clusterId}`;
            }
            else if (filters?.labelSelector) {
                labelSelector = filters.labelSelector;
            }
            const response = await firewallsApi.listFirewalls(undefined, filters?.name, labelSelector);
            return response.data.firewalls.map((firewall) => ({
                id: firewall.id.toString(),
                name: firewall.name,
                rules: firewall.rules.map((rule) => ({
                    description: rule.description || '',
                    direction: rule.direction,
                    protocol: rule.protocol,
                    port: rule.port,
                    sourceIps: rule.source_ips,
                    destinationIps: rule.destination_ips,
                })),
                labels: firewall.labels || {},
                appliedTo: firewall.applied_to.map((resource) => ({
                    serverId: resource.server?.id?.toString() || '',
                    serverName: undefined, // Not available in API response
                })),
            }));
        }
        catch (error) {
            this.logger.error('Failed to list firewalls', error);
            return [];
        }
    }
    async deleteFirewall(firewallId) {
        this.logger.log(`Deleting firewall ${firewallId}`);
        try {
            const firewallsApi = await this.createFirewallsApi();
            await firewallsApi.deleteFirewall(Number.parseInt(firewallId));
            this.logger.log(`Firewall ${firewallId} deleted successfully`);
            return;
        }
        catch (error) {
            // Already deleted - success
            if (error.response?.status === 404) {
                this.logger.warn(`Firewall ${firewallId} not found, already deleted`);
                return;
            }
            // Resource in use - retry once after brief delay
            if (error.response?.data?.error?.code === 'resource_in_use') {
                const retryDelay = 3000; // 3 seconds
                this.logger.warn(`Firewall ${firewallId} is in use. Waiting ${retryDelay}ms before retry...`);
                await this.sleep(retryDelay);
                // Single retry attempt
                try {
                    const firewallsApi = await this.createFirewallsApi();
                    await firewallsApi.deleteFirewall(Number.parseInt(firewallId));
                    this.logger.log(`Firewall ${firewallId} deleted successfully on retry`);
                    return;
                }
                catch (retryError) {
                    // Still in use after retry - throw meaningful error
                    if (retryError.response?.data?.error?.code === 'resource_in_use') {
                        throw new Error(`Firewall ${firewallId} is still in use. Servers may still be deleting or detaching. Please retry manually later.`);
                    }
                    // Different error on retry - throw it
                    this.logger.error(`Failed to delete firewall ${firewallId} on retry`, retryError);
                    if (retryError.response?.data?.error) {
                        const apiError = retryError.response.data.error;
                        throw new Error(`Hetzner API Error on retry: ${apiError.message} (${apiError.code})`);
                    }
                    throw new Error(`Failed to delete firewall on retry: ${retryError.message}`);
                }
            }
            // Other errors - log and throw immediately
            this.logger.error(`Failed to delete firewall ${firewallId}`, error);
            if (error.response?.data?.error) {
                const apiError = error.response.data.error;
                throw new Error(`Hetzner API Error: ${apiError.message} (${apiError.code})`);
            }
            throw new Error(`Failed to delete firewall: ${error.message}`);
        }
    }
    /**
     * Validate and transform firewall rules to Hetzner API format
     * Ensures required IP fields are present based on rule direction
     */
    validateAndTransformRules(rules) {
        const defaultIps = ['0.0.0.0/0', '::/0']; // Allow from/to anywhere
        return rules.map((rule, index) => {
            // Validate inbound rules have sourceIps
            if (rule.direction === 'in') {
                if (!rule.sourceIps || rule.sourceIps.length === 0) {
                    this.logger.warn(`Rule ${index + 1} ("${rule.description}"): Inbound rule missing sourceIps. Using default: ${defaultIps.join(', ')}`);
                }
            }
            // Validate outbound rules have destinationIps
            if (rule.direction === 'out') {
                if (!rule.destinationIps || rule.destinationIps.length === 0) {
                    this.logger.warn(`Rule ${index + 1} ("${rule.description}"): Outbound rule missing destinationIps. Using default: ${defaultIps.join(', ')}`);
                }
            }
            let sourceIps;
            if (rule.direction === 'in') {
                sourceIps =
                    rule.sourceIps && rule.sourceIps.length > 0
                        ? rule.sourceIps
                        : defaultIps;
            }
            let destinationIps;
            if (rule.direction === 'out') {
                destinationIps =
                    rule.destinationIps && rule.destinationIps.length > 0
                        ? rule.destinationIps
                        : defaultIps;
            }
            // Transform to Hetzner format with safe defaults
            return {
                description: rule.description,
                direction: rule.direction,
                protocol: rule.protocol,
                port: rule.port || undefined,
                source_ips: sourceIps,
                destination_ips: destinationIps,
            };
        });
    }
    sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }
    async getServerIdsByLabelSelector(labelSelector) {
        this.logger.log(`Finding servers with label selector: ${labelSelector}`);
        try {
            const serversApi = await this.createServersApi();
            const response = await serversApi.listServers(undefined, labelSelector);
            const servers = response.data.servers || [];
            const serverIds = servers.map((s) => s.id.toString());
            this.logger.log(`Found ${serverIds.length} server(s) matching label selector`);
            return serverIds;
        }
        catch (error) {
            this.logger.error(`Failed to find servers with label selector ${labelSelector}`, error);
            return [];
        }
    }
    async applyToServers(firewallId, serverIds) {
        this.logger.log(`Applying firewall ${firewallId} to ${serverIds.length} servers`);
        try {
            const actionsApi = await this.createFirewallActionsApi();
            const resources = serverIds.map((id) => ({
                type: 'server',
                server: { id: Number.parseInt(id) },
            }));
            //firewallsIdActionsApplyToResourcesPost
            await actionsApi.applyFirewallToResources(Number.parseInt(firewallId), {
                apply_to: resources,
            });
            this.logger.log(`Firewall ${firewallId} applied to ${serverIds.length} servers successfully`);
        }
        catch (error) {
            this.logger.error(`Failed to apply firewall ${firewallId}`, error);
            if (error.response?.data?.error) {
                const apiError = error.response.data.error;
                throw new Error(`Hetzner API Error: ${apiError.message} (${apiError.code})`);
            }
            throw new Error(`Failed to apply firewall: ${error.message}`);
        }
    }
    async removeFromServers(firewallId, serverIds) {
        this.logger.log(`Removing firewall ${firewallId} from ${serverIds.length} servers`);
        try {
            const actionsApi = await this.createFirewallActionsApi();
            const resources = serverIds.map((id) => ({
                type: 'server',
                server: { id: Number.parseInt(id) },
            }));
            await actionsApi.removeFirewallFromResources(Number.parseInt(firewallId), {
                remove_from: resources,
            });
            this.logger.log(`Firewall ${firewallId} removed from ${serverIds.length} servers successfully`);
        }
        catch (error) {
            this.logger.error(`Failed to remove firewall ${firewallId}`, error);
            if (error.response?.data?.error) {
                const apiError = error.response.data.error;
                throw new Error(`Hetzner API Error: ${apiError.message} (${apiError.code})`);
            }
            throw new Error(`Failed to remove firewall: ${error.message}`);
        }
    }
    async updateFirewallLabels(firewallId, labels) {
        this.logger.log(`Updating firewall ${firewallId} labels`);
        try {
            const firewallsApi = await this.createFirewallsApi();
            await firewallsApi.updateFirewall(Number.parseInt(firewallId), {
                labels,
            });
            this.logger.log(`Firewall ${firewallId} labels updated successfully: ${JSON.stringify(labels)}`);
        }
        catch (error) {
            this.logger.error(`Failed to update firewall ${firewallId} labels`, error);
            if (error.response?.data?.error) {
                const apiError = error.response.data.error;
                throw new Error(`Hetzner API Error: ${apiError.message} (${apiError.code})`);
            }
            throw new Error(`Failed to update firewall labels: ${error.message}`);
        }
    }
};
exports.HetznerFirewallService = HetznerFirewallService;
exports.HetznerFirewallService = HetznerFirewallService = HetznerFirewallService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(1, (0, common_1.Inject)('ICredentialProvider')),
    __metadata("design:paramtypes", [config_1.ConfigService, Object, label_service_1.LabelService])
], HetznerFirewallService);
