"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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StatusService = void 0;
const node_child_process_1 = require("node:child_process");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const node_process_1 = __importDefault(require("node:process"));
const node_util_1 = require("node:util");
const axios_1 = require("@nestjs/axios");
const common_1 = require("@nestjs/common");
const fs_extra_1 = require("fs-extra");
const node_cache_1 = __importDefault(require("node-cache"));
const rxjs_1 = require("rxjs");
const semver_1 = require("semver");
const systeminformation_1 = require("systeminformation");
const config_service_1 = require("../../core/config/config.service");
const homebridge_ipc_service_1 = require("../../core/homebridge-ipc/homebridge-ipc.service");
const logger_service_1 = require("../../core/logger/logger.service");
const node_version_constants_1 = require("../../core/node-version.constants");
const plugins_service_1 = require("../plugins/plugins.service");
const server_service_1 = require("../server/server.service");
const status_interfaces_1 = require("./status.interfaces");
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
let StatusService = class StatusService {
    constructor(httpService, logger, configService, pluginsService, serverService, homebridgeIpcService) {
        this.httpService = httpService;
        this.logger = logger;
        this.configService = configService;
        this.pluginsService = pluginsService;
        this.serverService = serverService;
        this.homebridgeIpcService = homebridgeIpcService;
        this.statusCache = new node_cache_1.default({ stdTTL: 3600 });
        this.homebridgeStatus = status_interfaces_1.HomebridgeStatus.DOWN;
        this.homebridgeStatusChange = new rxjs_1.Subject();
        this.cpuLoadHistory = [];
        this.memoryUsageHistory = [];
        this.rpiGetThrottledMapping = {
            0: 'Under-voltage detected',
            1: 'Arm frequency capped',
            2: 'Currently throttled',
            3: 'Soft temperature limit active',
            16: 'Under-voltage has occurred',
            17: 'Arm frequency capping has occurred',
            18: 'Throttled has occurred',
            19: 'Soft temperature limit has occurred',
        };
        this.DOCKER_GITHUB_API_URL = 'https://api.github.com/repos/homebridge/docker-homebridge/releases';
        if ((0, node_os_1.platform)() === 'freebsd') {
            this.getCpuLoadPoint = this.getCpuLoadPointAlt;
            this.getCpuTemp = this.getCpuTempAlt;
        }
        if (this.configService.ui.disableServerMetricsMonitoring !== true) {
            setInterval(async () => {
                this.getCpuLoadPoint();
                this.getMemoryUsagePoint();
            }, 10000);
        }
        else {
            this.logger.warn('Server metrics monitoring disabled.');
        }
        this.homebridgeIpcService.on('serverStatusUpdate', (data) => {
            this.homebridgeStatus = data.status === status_interfaces_1.HomebridgeStatus.OK ? status_interfaces_1.HomebridgeStatus.UP : data.status;
            if (data?.setupUri) {
                this.serverService.setupCode = data.setupUri;
                this.serverService.paired = data.paired;
            }
            this.homebridgeStatusChange.next(this.homebridgeStatus);
        });
    }
    async getCpuLoadPoint() {
        const load = (await (0, systeminformation_1.currentLoad)()).currentLoad;
        this.cpuLoadHistory = this.cpuLoadHistory.slice(-60);
        this.cpuLoadHistory.push(load);
    }
    async getMemoryUsagePoint() {
        const memory = await (0, systeminformation_1.mem)();
        this.memoryInfo = memory;
        const memoryFreePercent = ((memory.total - memory.available) / memory.total) * 100;
        this.memoryUsageHistory = this.memoryUsageHistory.slice(-60);
        this.memoryUsageHistory.push(memoryFreePercent);
    }
    async getCpuLoadPointAlt() {
        const load = ((0, node_os_1.loadavg)()[0] * 100 / (0, node_os_1.cpus)().length);
        this.cpuLoadHistory = this.cpuLoadHistory.slice(-60);
        this.cpuLoadHistory.push(load);
    }
    async getCpuTemp() {
        const cpuTempData = await (0, systeminformation_1.cpuTemperature)();
        if (cpuTempData.main === -1 && this.configService.ui.temp) {
            return this.getCpuTempLegacy();
        }
        return cpuTempData;
    }
    async getCpuTempLegacy() {
        try {
            const tempData = await (0, fs_extra_1.readFile)(this.configService.ui.temp, 'utf-8');
            const cpuTemp = Number.parseInt(tempData, 10) / 1000;
            return {
                main: cpuTemp,
                cores: [],
                max: cpuTemp,
            };
        }
        catch (e) {
            this.logger.error(`Failed to read temp from ${this.configService.ui.temp} as ${e.message}.`);
            return this.getCpuTempAlt();
        }
    }
    async getCpuTempAlt() {
        return {
            main: -1,
            cores: [],
            max: -1,
        };
    }
    async getCurrentNetworkUsage(netInterfaces) {
        if (!netInterfaces || !netInterfaces.length) {
            netInterfaces = [await (0, systeminformation_1.networkInterfaceDefault)()];
        }
        const net = await (0, systeminformation_1.networkStats)(netInterfaces.join(','));
        const txRxSec = (net[0].tx_sec + net[0].rx_sec) / 1024 / 1024;
        return { net: net[0], point: txRxSec };
    }
    async getDashboardLayout() {
        if (!this.dashboardLayout) {
            try {
                const layout = await (0, fs_extra_1.readJson)((0, node_path_1.resolve)(this.configService.storagePath, '.uix-dashboard.json'));
                this.dashboardLayout = layout;
                return layout;
            }
            catch (e) {
                return [];
            }
        }
        else {
            return this.dashboardLayout;
        }
    }
    async setDashboardLayout(layout) {
        (0, fs_extra_1.writeJsonSync)((0, node_path_1.resolve)(this.configService.storagePath, '.uix-dashboard.json'), layout);
        this.dashboardLayout = layout;
        return { status: 'ok' };
    }
    async getServerCpuInfo() {
        if (!this.memoryUsageHistory.length) {
            await this.getCpuLoadPoint();
        }
        return {
            cpuTemperature: await this.getCpuTemp(),
            currentLoad: this.cpuLoadHistory.slice(-1)[0],
            cpuLoadHistory: this.cpuLoadHistory,
        };
    }
    async getServerMemoryInfo() {
        if (!this.memoryUsageHistory.length) {
            await this.getMemoryUsagePoint();
        }
        return {
            mem: this.memoryInfo,
            memoryUsageHistory: this.memoryUsageHistory,
        };
    }
    async getServerUptimeInfo() {
        return {
            time: (0, systeminformation_1.time)(),
            processUptime: node_process_1.default.uptime(),
        };
    }
    async getHomebridgePairingPin() {
        return {
            pin: this.configService.homebridgeConfig.bridge.pin,
            setupUri: await this.serverService.getSetupCode(),
            paired: this.serverService.paired,
        };
    }
    async getHomebridgeStatus() {
        return {
            status: this.homebridgeStatus,
            consolePort: this.configService.ui.port,
            name: this.configService.homebridgeConfig.bridge.name,
            port: this.configService.homebridgeConfig.bridge.port,
            pin: this.configService.homebridgeConfig.bridge.pin,
            setupUri: this.serverService.setupCode,
            packageVersion: this.configService.package.version,
            paired: this.serverService.paired,
        };
    }
    async watchStats(client) {
        let homebridgeStatusInterval;
        client.emit('homebridge-status', await this.getHomebridgeStats());
        const homebridgeStatusChangeSub = this.homebridgeStatusChange.subscribe(async () => {
            client.emit('homebridge-status', await this.getHomebridgeStats());
        });
        const onEnd = () => {
            client.removeAllListeners('end');
            client.removeAllListeners('disconnect');
            if (homebridgeStatusInterval) {
                clearInterval(homebridgeStatusInterval);
            }
            homebridgeStatusChangeSub.unsubscribe();
        };
        client.on('end', onEnd.bind(this));
        client.on('disconnect', onEnd.bind(this));
    }
    async getHomebridgeStats() {
        return {
            consolePort: this.configService.ui.port,
            port: this.configService.homebridgeConfig.bridge.port,
            pin: this.configService.homebridgeConfig.bridge.pin,
            setupUri: await this.serverService.getSetupCode(),
            paired: this.serverService.paired,
            packageVersion: this.configService.package.version,
            status: await this.checkHomebridgeStatus(),
        };
    }
    async checkHomebridgeStatus() {
        return this.homebridgeStatus;
    }
    async getDefaultInterface() {
        const cachedResult = this.statusCache.get('defaultInterface');
        if (cachedResult) {
            return cachedResult;
        }
        const defaultInterfaceName = await (0, systeminformation_1.networkInterfaceDefault)();
        const defaultInterface = defaultInterfaceName ? (await (0, systeminformation_1.networkInterfaces)()).find(x => x.iface === defaultInterfaceName) : undefined;
        if (defaultInterface) {
            this.statusCache.set('defaultInterface', defaultInterface);
        }
        return defaultInterface;
    }
    async getOsInfo() {
        const cachedResult = this.statusCache.get('osInfo');
        if (cachedResult) {
            return cachedResult;
        }
        const osInformation = await (0, systeminformation_1.osInfo)();
        this.statusCache.set('osInfo', osInformation, 86400);
        return osInformation;
    }
    getGlibcVersion() {
        if ((0, node_os_1.platform)() !== 'linux') {
            return '';
        }
        const cachedResult = this.statusCache.get('glibcVersion');
        if (cachedResult) {
            return cachedResult;
        }
        try {
            const glibcVersion = (0, node_child_process_1.execSync)('getconf GNU_LIBC_VERSION 2>/dev/null').toString().split('glibc')[1].trim();
            this.statusCache.set('glibcVersion', glibcVersion, 86400);
            return glibcVersion;
        }
        catch (e) {
            this.logger.debug(`Could not check glibc version as ${e.message}.`);
            return '';
        }
    }
    async getHomebridgeServerInfo() {
        return {
            serviceUser: (0, node_os_1.userInfo)().username,
            homebridgeConfigJsonPath: this.configService.configPath,
            homebridgeStoragePath: this.configService.storagePath,
            homebridgeInsecureMode: this.configService.homebridgeInsecureMode,
            homebridgeCustomPluginPath: this.configService.customPluginPath,
            homebridgePluginPath: (0, node_path_1.resolve)(node_process_1.default.env.UIX_BASE_PATH, '..'),
            homebridgeRunningInDocker: this.configService.runningInDocker,
            homebridgeRunningInSynologyPackage: this.configService.runningInSynologyPackage,
            homebridgeRunningInPackageMode: this.configService.runningInPackageMode,
            nodeVersion: node_process_1.default.version,
            os: await this.getOsInfo(),
            time: (0, systeminformation_1.time)(),
            network: await this.getDefaultInterface() || {},
        };
    }
    async getHomebridgeVersion() {
        return this.pluginsService.getHomebridgePackage();
    }
    async getNodeJsVersionInfo() {
        const cachedResult = this.statusCache.get('nodeJsVersion');
        if (cachedResult) {
            return cachedResult;
        }
        const isNodeJs24Supported = (0, node_version_constants_1.isNodeV24SupportedArchitecture)();
        try {
            const versionList = (await (0, rxjs_1.firstValueFrom)(this.httpService.get('https://nodejs.org/dist/index.json'))).data;
            const latest22 = versionList.filter((x) => x.version.startsWith('v22'))[0];
            const latest24 = versionList.filter((x) => x.version.startsWith('v24'))[0];
            let updateAvailable = false;
            let latestVersion = node_process_1.default.version;
            let showNodeUnsupportedWarning = false;
            switch (node_process_1.default.version.split('.')[0]) {
                case 'v20': {
                    updateAvailable = true;
                    latestVersion = latest22.version;
                    break;
                }
                case 'v22': {
                    if ((0, semver_1.gt)(latest22.version, node_process_1.default.version)) {
                        updateAvailable = true;
                        latestVersion = latest22.version;
                    }
                    break;
                }
                case 'v24': {
                    if ((0, semver_1.gt)(latest24.version, node_process_1.default.version)) {
                        updateAvailable = true;
                        latestVersion = latest24.version;
                    }
                    break;
                }
                default: {
                    showNodeUnsupportedWarning = true;
                }
            }
            let npmVersion = null;
            try {
                const { stdout } = await execAsync('npm --version');
                npmVersion = `v${stdout.trim()}`;
            }
            catch (e) {
                this.logger.debug(`Could not check npm version as ${e.message}.`);
            }
            const versionInformation = {
                currentVersion: node_process_1.default.version,
                latestVersion,
                updateAvailable,
                showNodeUnsupportedWarning,
                installPath: (0, node_path_1.dirname)(node_process_1.default.execPath),
                npmVersion,
                architecture: node_process_1.default.arch,
                supportsNodeJs24: isNodeJs24Supported,
            };
            this.statusCache.set('nodeJsVersion', versionInformation, 86400);
            return versionInformation;
        }
        catch (e) {
            this.logger.log(`Failed to check for Node.js version updates (check your internet connection) as ${e.message}.`);
            const versionInformation = {
                currentVersion: node_process_1.default.version,
                latestVersion: node_process_1.default.version,
                updateAvailable: false,
                showNodeUnsupportedWarning: false,
                architecture: node_process_1.default.arch,
                supportsNodeJs24: isNodeJs24Supported,
            };
            this.statusCache.set('nodeJsVersion', versionInformation, 3600);
            return versionInformation;
        }
    }
    async getRaspberryPiThrottledStatus() {
        if (!this.configService.runningOnRaspberryPi) {
            throw new common_1.BadRequestException('This command is only available on Raspberry Pi');
        }
        const output = {};
        for (const bit of Object.keys(this.rpiGetThrottledMapping)) {
            output[this.rpiGetThrottledMapping[bit]] = false;
        }
        try {
            const { stdout } = await execAsync('vcgencmd get_throttled');
            const throttledHex = Number.parseInt(stdout.trim().replace('throttled=', ''));
            if (!Number.isNaN(throttledHex)) {
                for (const bit of Object.keys(this.rpiGetThrottledMapping)) {
                    output[this.rpiGetThrottledMapping[bit]] = !!((throttledHex >> Number.parseInt(bit, 10)) & 1);
                }
            }
        }
        catch (e) {
            this.logger.debug(`Could not check vcgencmd get_throttled as ${e.message}.`);
        }
        return output;
    }
    async getDockerDetails() {
        const currentVersion = node_process_1.default.env.DOCKER_HOMEBRIDGE_VERSION;
        let latestVersion = null;
        let latestReleaseBody = '';
        let updateAvailable = false;
        try {
            const { releases, rawReleases } = await this.getRecentReleases();
            if (currentVersion) {
                const lowerCurrentVersion = currentVersion.toLowerCase();
                let targetReleases = [];
                if (lowerCurrentVersion.startsWith('beta-')) {
                    targetReleases = releases
                        .filter(release => release.testTag === 'beta' && /^beta-\d{4}-\d{2}-\d{2}$/i.test(release.version))
                        .sort((a, b) => b.version.localeCompare(a.version));
                    latestVersion = targetReleases[0]?.version || null;
                }
                else if (lowerCurrentVersion.startsWith('test-')) {
                    targetReleases = releases
                        .filter(release => release.testTag === 'test' && /^test-\d{4}-\d{2}-\d{2}$/i.test(release.version))
                        .sort((a, b) => b.version.localeCompare(a.version));
                    latestVersion = targetReleases[0]?.version || null;
                }
                else {
                    const stableRelease = releases.find(release => release.isLatestStable);
                    latestVersion = stableRelease?.version || null;
                }
                if (currentVersion && latestVersion) {
                    const dateRegex = /\d{4}-\d{2}-\d{2}$/;
                    if (dateRegex.test(currentVersion) && dateRegex.test(latestVersion)) {
                        const currentDate = new Date(currentVersion.match(dateRegex)[0]);
                        const latestDate = new Date(latestVersion.match(dateRegex)[0]);
                        updateAvailable = latestDate > currentDate;
                    }
                    else {
                        updateAvailable = currentVersion !== latestVersion;
                    }
                }
            }
            else {
                const stableRelease = releases.find(release => release.isLatestStable);
                latestVersion = stableRelease?.version || null;
            }
            if (latestVersion) {
                const rawRelease = rawReleases.find(r => r.tag_name === latestVersion);
                latestReleaseBody = rawRelease?.body || '';
            }
        }
        catch (error) {
            console.error('Failed to fetch Docker details:', error instanceof Error ? error.message : error);
        }
        return {
            currentVersion,
            latestVersion,
            latestReleaseBody,
            updateAvailable,
        };
    }
    async getRecentReleases() {
        try {
            const response = await fetch(`${this.DOCKER_GITHUB_API_URL}?per_page=100`, {
                headers: {
                    Accept: 'application/vnd.github.v3+json',
                },
            });
            if (!response.ok) {
                console.error(`GitHub API error: ${response.status} ${response.statusText}`);
                return { releases: [], rawReleases: [] };
            }
            const data = await response.json();
            if (!Array.isArray(data)) {
                console.error('Invalid response from GitHub API: Expected an array');
                return { releases: [], rawReleases: [] };
            }
            const stableReleases = data
                .filter(release => /^\d{4}-\d{2}-\d{2}$/.test(release.tag_name))
                .sort((a, b) => b.tag_name.localeCompare(a.tag_name));
            const latestStableTag = stableReleases[0]?.tag_name || null;
            const releases = data.map((release) => {
                const tagName = release.tag_name.toLowerCase();
                let testTag = null;
                if (tagName.startsWith('beta-')) {
                    testTag = 'beta';
                }
                else if (tagName.startsWith('test-')) {
                    testTag = 'test';
                }
                return {
                    version: release.tag_name,
                    publishedAt: release.published_at,
                    isPrerelease: release.prerelease,
                    isTest: testTag !== null,
                    testTag,
                    isLatestStable: release.tag_name === latestStableTag,
                };
            });
            return { releases, rawReleases: data };
        }
        catch (error) {
            console.error('Failed to fetch docker-homebridge releases:', error instanceof Error ? error.message : error);
            return { releases: [], rawReleases: [] };
        }
    }
};
exports.StatusService = StatusService;
exports.StatusService = StatusService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [axios_1.HttpService,
        logger_service_1.Logger,
        config_service_1.ConfigService,
        plugins_service_1.PluginsService,
        server_service_1.ServerService,
        homebridge_ipc_service_1.HomebridgeIpcService])
], StatusService);
//# sourceMappingURL=status.service.js.map