/**
 * Krill Installer - Simplified Refactoring
 * ./src/krill/prepare.ts
 * penguins-eggs v.25.7.x / ecmascript 2020
 * author: Piero Proietti
 * email: piero.proietti@gmail.com
 * license: MIT
 */
import axios from 'axios';
import fs from 'fs';
import os from 'os';
import Keyboards from '../../classes/keyboards.js';
import Locales from '../../classes/locales.js';
import Systemctl from '../../classes/systemctl.js';
import Utils from '../../classes/utils.js';
import { exec, shx } from '../../lib/utils.js';
import { FsType, InstallationMode, SwapChoice } from './krill_enums.js';
import { keyboard } from './prepare.d/keyboard.js';
import { location } from './prepare.d/location.js';
import { network } from './prepare.d/network.js';
import { partitions } from './prepare.d/partitions.js';
import { summary } from './prepare.d/summary.js';
import { users } from './prepare.d/users.js';
// UI Components
import { welcome } from './prepare.d/welcome.js';
import Sequence from './sequence.js';
const config_file = '/etc/penguins-eggs.d/krill.yaml';
/**
 * Main Krill installer class - Simplified refactoring
 */
export default class Krill {
    chroot = false;
    halt = false;
    keyboard = keyboard;
    keyboards = new Keyboards();
    // Configuration
    krillConfig = {};
    locales = new Locales();
    location = location;
    network = network;
    nointeractive = false;
    partitions = partitions;
    summary = summary;
    // Installation flags
    unattended = false;
    users = users;
    // UI Components
    welcome = welcome;
    /**
     * Constructor
     */
    constructor(unattended = false, nointeractive = false, halt = false, chroot = false) {
        this.unattended = unattended;
        this.nointeractive = nointeractive;
        this.chroot = chroot;
        this.halt = halt;
    }
    /**
     * Main prepare method - Simplified and cleaner
     */
    async prepare(krillConfig = {}, ip = false, random = false, domain = '', suspend = false, small = false, none = false, crypted = false, pve = false, btrfs = false, replace = '', testing = false, verbose = false) {
        try {
            // System validation
            await this.checkSystemRequirements();
            // Configuration setup
            this.krillConfig = krillConfig;
            await this.setupConfiguration();
            // Stop services
            await this.stopSystemServices(verbose, testing);
            // Build configurations
            const configs = await this.buildConfigurations(ip, random, suspend, small, none);
            // Interactive or unattended mode
            const finalConfigs = this.unattended ? this.applyUnattendedOptions(configs, crypted, pve, btrfs, replace) : await this.runInteractiveMode(crypted, pve, btrfs, replace);
            // Install
            await this.performInstallation(finalConfigs, domain, testing, verbose);
        }
        catch (error) {
            await Utils.pressKeyToExit(`${error.message}\nkrill installer refuses to continue`);
            process.exit();
        }
    }
    /**
     * Apply options for unattended installation
     */
    applyUnattendedOptions(configs, crypted, pve, btrfs, replace = '') {
        const { oPartitions, oUsers } = configs;
        // Set defaults for unattended
        oPartitions.installationMode = InstallationMode.EraseDisk;
        if (replace != '') {
            oPartitions.installationMode = InstallationMode.Replace;
            oPartitions.replacedPartition = replace;
            oPartitions.filesystemType = 'ext4';
        }
        // Apply options
        if (btrfs)
            oPartitions.filesystemType = FsType.btrfs;
        if (crypted || pve)
            oPartitions.installationMode = InstallationMode.Luks;
        // Set default installation device if empty
        if (oPartitions.installationDevice === '') {
            const cmd = `lsblk -d -n -p -o NAME,RM,RO,TYPE | awk '$2 == 0 && $3 == 0 && $4 == "disk" {print $1}'`;
            const result = shx.exec(cmd, { silent: true }).stdout.trim();
            const drives = result ? result.split('\n') : [];
            if (drives.length > 0) {
                oPartitions.installationDevice = drives[0];
            }
            else {
                console.error('[Krll] No suitable disc found for installation. Debug info:');
                shx.exec('lsblk -o NAME,RM,RO,TYPE,SIZE,MODEL', { silent: false });
                throw new Error('Unable to find installation drive');
            }
        }
        return configs;
    }
    /**
     * Auto-configure timezone from internet
     */
    async autoConfigureTimezone() {
        try {
            const response = await axios.get('https://geoip.kde.org/v1/calamares');
            if (response.statusText === 'OK') {
                const timeZone = response.data.time_zone;
                this.krillConfig.region = timeZone.slice(0, Math.max(0, timeZone.indexOf('/')));
                this.krillConfig.zone = timeZone.slice(Math.max(0, timeZone.indexOf('/') + 1));
            }
        }
        catch (error) {
            console.error('Error auto-configuring timezone:', error);
        }
    }
    /**
     * Build default configurations
     */
    async buildConfigurations(ip, random, suspend, small, none) {
        // Generate hostname
        const hostname = this.generateHostname(ip, random);
        // Build configuration objects
        const oWelcome = { language: this.krillConfig.language };
        const oLocation = {
            language: this.krillConfig.language,
            region: this.krillConfig.region,
            zone: this.krillConfig.zone
        };
        const oKeyboard = {
            keyboardLayout: this.krillConfig.keyboardLayout,
            keyboardModel: this.krillConfig.keyboardModel,
            keyboardOption: this.krillConfig.keyboardOption,
            keyboardVariant: this.krillConfig.keyboardVariant
        };
        let { userSwapChoice } = this.krillConfig;
        if (suspend)
            userSwapChoice = SwapChoice.Suspend;
        else if (small)
            userSwapChoice = SwapChoice.Small;
        else if (none)
            userSwapChoice = SwapChoice.None;
        const oPartitions = {
            filesystemType: this.krillConfig.filesystemType,
            installationDevice: this.krillConfig.installationDevice,
            installationMode: this.krillConfig.installationMode,
            replacedPartition: this.krillConfig.replacedPartition,
            userSwapChoice
        };
        const oUsers = {
            autologin: this.krillConfig.autologin,
            fullname: this.krillConfig.fullname,
            hostname,
            password: this.krillConfig.password,
            rootPassword: this.krillConfig.rootPassword,
            username: this.krillConfig.name
        };
        const oNetwork = {
            address: Utils.address(),
            addressType: this.krillConfig.addressType,
            dns: Utils.getDns(),
            domain: Utils.getDomain(),
            gateway: Utils.gateway(),
            iface: await Utils.iface(),
            netmask: Utils.netmask()
        };
        return { oKeyboard, oLocation, oNetwork, oPartitions, oUsers, oWelcome };
    }
    /**
     * Check system requirements (disks, LVM)
     */
    async checkSystemRequirements() {
        // Check disk presence
        const drives = shx.exec('lsblk |grep disk|cut -f 1 "-d "', { silent: true }).stdout.trim().split('\n');
        if (drives[0] === '') {
            throw new Error('No disk to install the system in this machine.');
        }
        // Check LVM presence
        if (await this.pvExist()) {
            await this.createLvmRemovalScript();
            console.log('\n=========================================================================');
            console.log(' WARNING: LVM2 volume detected!');
            console.log('=========================================================================');
            console.log('To prevent accidental data loss, the installer will now exit.');
            console.log('You must manually remove the LVM partitions before proceeding.\n');
            console.log('A helper script has been generated for your convenience.');
            console.log('If you are ABSOLUTELY SURE you want to erase the disk, run:\n');
            console.log('    sudo bash /tmp/removeLvmPartitions\n');
            console.log('  DANGER: This action is IRREVERSIBLE and will DESTROY ALL DATA on the LVM!\n');
            process.exit(1); // Esce con codice di errore 1
        }
    }
    /**
     * Create script to remove LVM partitions
     */
    async createLvmRemovalScript() {
        const scriptName = '/tmp/removeLvmPartitions';
        let cmds = '#!/bin/bash\n';
        cmds += `# remove LV (Logical Volumes)\n`;
        cmds += `vg=$(vgs --noheadings -o vg_name| awk '{$1=$1};1')\n`;
        cmds += `lvs -o lv_name --noheadings | awk '{$1=$1};1' | while read -r lv; do\n`;
        cmds += ` lvremove -y /dev/mapper/$vg-$lv\n`;
        cmds += `done\n`;
        cmds += `\n`;
        cmds += `# remove VG (Volume groups)\n`;
        cmds += `vgremove --force $(vgs --noheadings -o vg_name $vg)\n`;
        cmds += `\n`;
        cmds += `# remove PV (Physical Volumes) \n`;
        cmds += `pv=$(pvs --noheading -o pv_name | awk '{$1=$1};1')\n`;
        cmds += `pvremove --force --force $pv\n`;
        cmds += `# wipe PV (Physical Volumes) \n`;
        cmds += `wipefs -a $pv\n`;
        cmds += `# clean device\n`;
        cmds += `sgdisk --zap-all $pv\n`;
        cmds += `dd if=/dev/zero of=$pv bs=1M count=10\n`;
        fs.writeFileSync(scriptName, cmds);
        await exec(`chmod +x ${scriptName}`);
    }
    /**
     * Generate hostname based on options
     */
    generateHostname(ip, random) {
        let { hostname } = this.krillConfig;
        if (hostname === '') {
            hostname = shx.exec('cat /etc/hostname', { silent: true }).stdout.trim();
        }
        if (ip) {
            hostname = 'ip-' + Utils.address().replaceAll('.', '-');
        }
        if (random) {
            const fl = shx.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).stdout.trim();
            const n = shx.exec(`tr -dc 0-9 </dev/urandom | head -c 3 ; echo ''`, { silent: true }).stdout.trim();
            const sl = shx.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).stdout.trim();
            hostname = `${os.hostname()}-${fl}${n}${sl}`;
        }
        return hostname;
    }
    /**
     * Perform installation or testing
     */
    async performInstallation(configs, domain, testing, verbose) {
        const { oKeyboard, oLocation, oNetwork, oPartitions, oUsers } = configs;
        await this.summary(oLocation, oKeyboard, oPartitions, oUsers);
        if (testing) {
            console.log();
            Utils.titles('install --testing');
            console.log('Just testing krill, the process will end!');
            console.log('^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^');
            console.log(oPartitions);
            process.exit();
        }
        else {
            const sequence = new Sequence(oLocation, oKeyboard, oPartitions, oUsers, oNetwork);
            await sequence.start(domain, this.unattended, this.nointeractive, this.chroot, this.halt, verbose);
        }
    }
    /**
     * Check if Physical Volumes exist
     */
    async pvExist() {
        const check = `#!/bin/sh\npvdisplay |grep "PV Name" >/dev/null && echo 1|| echo 0`;
        return shx.exec(check, { silent: true }).stdout.trim() === '1';
    }
    /**
     * Run interactive mode with UI
     */
    async runInteractiveMode(crypted, pve, btrfs, replace) {
        const oWelcome = await this.welcome();
        const oLocation = await this.location(oWelcome.language);
        const oKeyboard = await this.keyboard();
        const oPartitions = await this.partitions(this.krillConfig.installationDevice, crypted, pve, btrfs, replace);
        const oUsers = await this.users();
        const oNetwork = await this.network();
        return { oKeyboard, oLocation, oNetwork, oPartitions, oUsers, oWelcome };
    }
    /**
     * Setup configuration files and auto-configure timezone
     */
    async setupConfiguration() {
        // Check config file
        if (!fs.existsSync(config_file)) {
            throw new Error(`Cannot find configuration file ${config_file}`);
        }
        // Check calamares/krill configuration
        let configRoot = '/etc/penguins-eggs.d/krill/';
        if (fs.existsSync('/etc/calamares/settings.conf')) {
            configRoot = '/etc/calamares/';
        }
        if (!fs.existsSync(configRoot + 'settings.conf')) {
            throw new Error('Cannot find calamares/krill configuration file, please create it running: sudo eggs calamares');
        }
        // Auto-configure timezone
        await this.autoConfigureTimezone();
    }
    /**
     * Stop system services
     */
    async stopSystemServices(verbose, testing) {
        const systemdCtl = new Systemctl(verbose);
        if ((await systemdCtl.isActive('udisks2.service')) && !testing) {
            await systemdCtl.stop('udisks2.service');
        }
    }
}
/**
 * Utility function for netmask conversion
 */
function netmask2CIDR(mask) {
    const countCharOccurences = (string, char) => string.split(char).length - 1;
    const decimalToBinary = (dec) => (dec >>> 0).toString(2);
    const getNetMaskParts = (nmask) => nmask.split('.').map(Number);
    return countCharOccurences(getNetMaskParts(mask)
        .map((part) => decimalToBinary(part))
        .join(''), '1');
}
