"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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var MikroTikProvider_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MikroTikProvider = void 0;
const common_1 = require("@nestjs/common");
const axios_1 = require("axios");
const config_service_1 = require("../../common/config/config.service");
const encryption_util_1 = require("../../common/utils/encryption.util");
const MAX_RETRIES = 2;
const RETRY_DELAY_MS = 1000;
const REQUEST_TIMEOUT_MS = 15000;
let MikroTikProvider = MikroTikProvider_1 = class MikroTikProvider {
    constructor(configService) {
        this.configService = configService;
        this.logger = new common_1.Logger(MikroTikProvider_1.name);
    }
    buildBaseUrl(router) {
        const protocol = router.useSsl ? 'https' : 'http';
        return `${protocol}://${router.host}:${router.apiPort}/rest`;
    }
    getDecryptedPassword(router) {
        const encryptionKey = this.configService.getEncryptionKey();
        try {
            return (0, encryption_util_1.decrypt)(router.apiPassword, encryptionKey);
        }
        catch {
            return router.apiPassword;
        }
    }
    createClient(router) {
        const baseURL = this.buildBaseUrl(router);
        const password = this.getDecryptedPassword(router);
        return axios_1.default.create({
            baseURL,
            timeout: REQUEST_TIMEOUT_MS,
            headers: {
                'Content-Type': 'application/json',
            },
            auth: {
                username: router.apiUsername,
                password,
            },
        });
    }
    async requestWithRetry(client, config, retries = MAX_RETRIES) {
        for (let attempt = 0; attempt <= retries; attempt++) {
            try {
                const response = await client.request(config);
                return response.data;
            }
            catch (error) {
                const status = error.response?.status;
                const message = error.response?.data?.message || error.message;
                if (status === 401 || status === 403) {
                    this.logger.error(`Authentication failed for ${config.url}`);
                    throw error;
                }
                if (attempt < retries) {
                    const delay = RETRY_DELAY_MS * Math.pow(2, attempt);
                    this.logger.warn(`Request to ${config.url} failed (attempt ${attempt + 1}/${retries + 1}): ${message}. Retrying in ${delay}ms...`);
                    await new Promise((resolve) => setTimeout(resolve, delay));
                }
                else {
                    this.logger.error(`Request to ${config.url} failed after ${retries + 1} attempts: ${message}`);
                    throw error;
                }
            }
        }
        throw new Error('Unreachable');
    }
    async connect(router) {
        try {
            const result = await this.testConnection(router);
            return result.success;
        }
        catch (error) {
            this.logger.error(`Failed to connect to router ${router.id}: ${error.message}`);
            return false;
        }
    }
    async disconnect(_router) {
        return;
    }
    async testConnection(router) {
        try {
            const client = this.createClient(router);
            const data = await this.requestWithRetry(client, {
                method: 'GET',
                url: '/system/resource',
            });
            return {
                success: true,
                message: 'Connection successful',
                details: {
                    platform: data['platform-name'] || 'MikroTik',
                    version: data.version,
                    uptime: data.uptime,
                },
            };
        }
        catch (error) {
            const status = error.response?.status;
            let message = 'Connection failed';
            if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
                message = `Cannot reach router at ${router.host}:${router.apiPort}`;
            }
            else if (status === 401 || status === 403) {
                message = 'Authentication failed - invalid username or password';
            }
            else if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
                message = `Connection to ${router.host}:${router.apiPort} timed out`;
            }
            this.logger.error(`Connection test failed for router ${router.id} (${router.host}): ${message}`);
            return { success: false, message, details: error.message };
        }
    }
    async createHotspotUser(router, params) {
        try {
            const client = this.createClient(router);
            const payload = {
                name: params.username,
                password: params.password,
            };
            if (params.profile) {
                payload.profile = params.profile;
            }
            if (params.uptimeLimit) {
                payload['limit-uptime'] = this.formatDuration(params.uptimeLimit);
            }
            if (params.dataLimitMb) {
                payload['limit-bytes-total'] = Math.round(params.dataLimitMb * 1024 * 1024).toString();
            }
            if (params.macAddress) {
                payload['mac-address'] = params.macAddress;
            }
            if (params.comment) {
                payload.comment = params.comment;
            }
            const rateParts = [];
            if (params.uploadKbps && params.uploadKbps > 0) {
                rateParts.push(`${Math.round(params.uploadKbps * 1000)}`);
            }
            if (params.downloadKbps && params.downloadKbps > 0) {
                if (rateParts.length === 0) {
                    rateParts.push('0');
                }
                rateParts.push(`${Math.round(params.downloadKbps * 1000)}`);
            }
            if (rateParts.length > 0) {
                payload['rate-limit'] = rateParts.join('/');
            }
            const data = await this.requestWithRetry(client, {
                method: 'PUT',
                url: '/ip/hotspot/user/add',
                data: payload,
            });
            const sessionId = data?.['.id'] || `mikrotik-${Date.now()}`;
            return {
                username: params.username,
                password: params.password,
                sessionId,
            };
        }
        catch (error) {
            this.logger.error(`Failed to create hotspot user on router ${router.id}: ${error.message}`);
            throw new Error(`Failed to create hotspot user: ${error.response?.data?.message || error.message}`);
        }
    }
    async removeHotspotUser(router, username) {
        try {
            const client = this.createClient(router);
            const existing = await this.requestWithRetry(client, {
                method: 'GET',
                url: `/ip/hotspot/user?name=${encodeURIComponent(username)}`,
            });
            if (!existing || existing.length === 0) {
                this.logger.warn(`Hotspot user "${username}" not found on router ${router.id}`);
                return;
            }
            const userId = existing[0]['.id'];
            await this.requestWithRetry(client, {
                method: 'POST',
                url: `/ip/hotspot/user/remove`,
                data: { '.id': userId },
            });
            this.logger.log(`Removed hotspot user "${username}" from router ${router.id}`);
        }
        catch (error) {
            this.logger.error(`Failed to remove hotspot user "${username}" from router ${router.id}: ${error.message}`);
        }
    }
    async disconnectUser(router, sessionId) {
        try {
            const client = this.createClient(router);
            await this.requestWithRetry(client, {
                method: 'POST',
                url: '/ip/hotspot/active/remove',
                data: { '.id': sessionId },
            });
            this.logger.log(`Disconnected session "${sessionId}" from router ${router.id}`);
        }
        catch (error) {
            this.logger.error(`Failed to disconnect session "${sessionId}" from router ${router.id}: ${error.message}`);
        }
    }
    async getActiveSessions(router) {
        try {
            const client = this.createClient(router);
            const data = await this.requestWithRetry(client, {
                method: 'GET',
                url: '/ip/hotspot/active',
            });
            if (!Array.isArray(data)) {
                return [];
            }
            return data.map((entry) => ({
                sessionId: entry['.id'] || '',
                username: entry.user || entry.name || '',
                macAddress: entry['mac-address'] || '',
                ipAddress: entry.address || '',
                uptime: this.parseDuration(entry.uptime),
                bytesIn: parseInt(entry['bytes-in'] || '0', 10),
                bytesOut: parseInt(entry['bytes-out'] || '0', 10),
                startTime: entry.uptime
                    ? new Date(Date.now() - this.parseDuration(entry.uptime) * 1000)
                    : new Date(),
                idleTime: this.parseDuration(entry['idle-time']),
            }));
        }
        catch (error) {
            this.logger.error(`Failed to get active sessions from router ${router.id}: ${error.message}`);
            return [];
        }
    }
    async getRouterStatus(router) {
        try {
            const client = this.createClient(router);
            const [resource, active] = await Promise.all([
                this.requestWithRetry(client, {
                    method: 'GET',
                    url: '/system/resource',
                }).catch(() => null),
                this.requestWithRetry(client, {
                    method: 'GET',
                    url: '/ip/hotspot/active',
                }).catch(() => []),
            ]);
            let model = 'Unknown';
            try {
                const rb = await this.requestWithRetry(client, {
                    method: 'GET',
                    url: '/system/routerboard',
                }).catch(() => null);
                if (rb?.model) {
                    model = rb.model;
                }
            }
            catch {
            }
            return {
                isOnline: true,
                cpuUsage: parseFloat(resource?.['cpu-load'] || '0'),
                memoryUsage: resource
                    ? parseFloat((((resource['total-memory'] - resource['free-memory']) /
                        resource['total-memory']) *
                        100).toFixed(2))
                    : 0,
                uptime: this.parseDuration(resource?.uptime),
                activeSessions: Array.isArray(active) ? active.length : 0,
                version: resource?.version || 'Unknown',
                model,
            };
        }
        catch (error) {
            this.logger.error(`Failed to get router status for ${router.id}: ${error.message}`);
            return {
                isOnline: false,
                cpuUsage: 0,
                memoryUsage: 0,
                uptime: 0,
                activeSessions: 0,
                version: 'Unknown',
                model: 'Unknown',
            };
        }
    }
    async setBandwidthLimit(router, username, uploadKbps, downloadKbps) {
        try {
            const client = this.createClient(router);
            const users = await this.requestWithRetry(client, {
                method: 'GET',
                url: `/ip/hotspot/user?name=${encodeURIComponent(username)}`,
            });
            if (!users || users.length === 0) {
                throw new Error(`User "${username}" not found`);
            }
            const userId = users[0]['.id'];
            const rateLimit = `${Math.round(uploadKbps * 1000)}/${Math.round(downloadKbps * 1000)}`;
            await this.requestWithRetry(client, {
                method: 'PATCH',
                url: `/ip/hotspot/user/${userId}`,
                data: { 'rate-limit': rateLimit },
            });
            this.logger.log(`Updated bandwidth for "${username}" on router ${router.id}: ${rateLimit}`);
        }
        catch (error) {
            this.logger.error(`Failed to set bandwidth for "${username}" on router ${router.id}: ${error.message}`);
            throw error;
        }
    }
    async bindMacAddress(router, macAddress, ipAddress) {
        try {
            const client = this.createClient(router);
            await this.requestWithRetry(client, {
                method: 'PUT',
                url: '/ip/hotspot/ip-binding/add',
                data: {
                    'mac-address': macAddress,
                    address: ipAddress,
                    type: 'bypassed',
                },
            });
            this.logger.log(`Bound MAC ${macAddress} to IP ${ipAddress} on router ${router.id}`);
        }
        catch (error) {
            this.logger.error(`Failed to bind MAC ${macAddress} on router ${router.id}: ${error.message}`);
            throw error;
        }
    }
    async removeMacBinding(router, macAddress) {
        try {
            const client = this.createClient(router);
            const bindings = await this.requestWithRetry(client, {
                method: 'GET',
                url: `/ip/hotspot/ip-binding?mac-address=${encodeURIComponent(macAddress)}`,
            });
            if (!bindings || bindings.length === 0) {
                this.logger.warn(`No binding found for MAC ${macAddress} on router ${router.id}`);
                return;
            }
            for (const binding of bindings) {
                await this.requestWithRetry(client, {
                    method: 'POST',
                    url: '/ip/hotspot/ip-binding/remove',
                    data: { '.id': binding['.id'] },
                });
            }
            this.logger.log(`Removed MAC binding for ${macAddress} on router ${router.id}`);
        }
        catch (error) {
            this.logger.error(`Failed to remove MAC binding for ${macAddress} on router ${router.id}: ${error.message}`);
        }
    }
    async refreshSession(router, sessionId, newExpiry) {
        try {
            const client = this.createClient(router);
            const payload = {};
            if (newExpiry) {
                const remainingSeconds = Math.max(1, Math.ceil((newExpiry.getTime() - Date.now()) / 1000));
                payload['limit-uptime'] = this.formatDuration(remainingSeconds);
            }
            if (Object.keys(payload).length > 0) {
                await this.requestWithRetry(client, {
                    method: 'PATCH',
                    url: `/ip/hotspot/user/${sessionId}`,
                    data: payload,
                });
                this.logger.log(`Refreshed session ${sessionId} on router ${router.id}${newExpiry ? ` until ${newExpiry.toISOString()}` : ''}`);
            }
        }
        catch (error) {
            this.logger.error(`Failed to refresh session ${sessionId} on router ${router.id}: ${error.message}`);
        }
    }
    formatDuration(seconds) {
        if (seconds <= 0)
            return '0';
        const d = Math.floor(seconds / 86400);
        const h = Math.floor((seconds % 86400) / 3600);
        const m = Math.floor((seconds % 3600) / 60);
        const s = seconds % 60;
        const parts = [];
        if (d > 0)
            parts.push(`${d}d`);
        if (h > 0)
            parts.push(`${h.toString().padStart(2, '0')}:`);
        if (m > 0 || h > 0)
            parts.push(`${m.toString().padStart(2, '0')}:`);
        parts.push(s.toString().padStart(2, '0'));
        return parts.join('');
    }
    parseDuration(value) {
        if (!value)
            return 0;
        let totalSeconds = 0;
        const dayMatch = value.match(/(\d+)d/);
        if (dayMatch) {
            totalSeconds += parseInt(dayMatch[1], 10) * 86400;
        }
        const timeMatch = value.match(/(\d+):(\d+):(\d+)/);
        if (timeMatch) {
            totalSeconds +=
                parseInt(timeMatch[1], 10) * 3600 +
                    parseInt(timeMatch[2], 10) * 60 +
                    parseInt(timeMatch[3], 10);
        }
        else {
            const simpleMatch = value.match(/(\d+):(\d+)/);
            if (simpleMatch) {
                totalSeconds +=
                    parseInt(simpleMatch[1], 10) * 60 +
                        parseInt(simpleMatch[2], 10);
            }
            else {
                const numeric = parseFloat(value);
                if (!isNaN(numeric)) {
                    return numeric;
                }
            }
        }
        return totalSeconds;
    }
};
exports.MikroTikProvider = MikroTikProvider;
exports.MikroTikProvider = MikroTikProvider = MikroTikProvider_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [config_service_1.ConfigService])
], MikroTikProvider);
