"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 NativeSSHConnectionService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NativeSSHConnectionService = void 0;
const common_1 = require("@nestjs/common");
const node_child_process_1 = require("node:child_process");
const node_fs_1 = require("node:fs");
const path = __importStar(require("node:path"));
const os = __importStar(require("node:os"));
/**
 * Service for managing native SSH connections with certificate support
 * Uses native SSH command instead of ssh2 library to support SSH certificates
 */
let NativeSSHConnectionService = NativeSSHConnectionService_1 = class NativeSSHConnectionService {
    constructor() {
        this.logger = new common_1.Logger(NativeSSHConnectionService_1.name);
    }
    /**
     * Create a native SSH connection with certificate authentication
     *
     * @param host - Server IP or hostname
     * @param username - SSH username (typically 'root')
     * @param credentials - Private key and certificate
     * @param onData - Callback for stdout data
     * @param onError - Callback for errors
     * @param onClose - Callback when connection closes
     * @param options - Connection options (terminal size)
     * @returns SSH connection object with cleanup function
     */
    async createConnection(host, username, credentials, onData, onError, onClose, options) {
        let tempDir = null;
        try {
            // Create temporary directory for credentials
            tempDir = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'flui-ssh-'));
            this.logger.debug(`Created temp directory: ${tempDir}`);
            // Write private key to temp file
            const keyPath = path.join(tempDir, 'key');
            await node_fs_1.promises.writeFile(keyPath, credentials.privateKey, { mode: 0o600 });
            this.logger.debug(`Wrote key: ${keyPath}`);
            // Write certificate only if provided (ephemeral cert mode)
            const certArgs = [];
            if (credentials.certificate) {
                const certPath = path.join(tempDir, 'key-cert.pub');
                await node_fs_1.promises.writeFile(certPath, credentials.certificate, { mode: 0o644 });
                this.logger.debug(`Wrote cert: ${certPath}`);
                certArgs.push('-o', `CertificateFile=${certPath}`);
            }
            // Spawn native SSH process
            this.logger.log(`🔌 Spawning native SSH connection to ${username}@${host} (${credentials.certificate ? 'certificate auth' : 'publickey auth'})`);
            const rows = options?.rows || 24;
            const cols = options?.cols || 80;
            this.logger.log(`📏 Terminal size: ${cols}x${rows}`);
            const sshProcess = (0, node_child_process_1.spawn)('ssh', [
                '-i',
                keyPath,
                ...certArgs,
                '-o',
                'StrictHostKeyChecking=no',
                '-o',
                'UserKnownHostsFile=/dev/null',
                '-o',
                'ServerAliveInterval=30',
                '-o',
                'ServerAliveCountMax=3',
                '-o',
                'LogLevel=ERROR',
                '-tt', // Force PTY allocation (double -t for better compatibility)
                `${username}@${host}`,
            ], {
                env: {
                    ...process.env,
                    TERM: 'xterm-256color',
                    COLUMNS: cols.toString(),
                    LINES: rows.toString(),
                },
                windowsHide: true,
                stdio: ['pipe', 'pipe', 'pipe'],
            });
            // Terminal size is set via COLUMNS and LINES environment variables above
            // No need to send stty command which can interfere with SSH authentication
            // Handle stdout
            sshProcess.stdout?.on('data', (data) => {
                const str = data.toString('utf-8');
                this.logger.debug(`📤 Received ${str.length} chars from stdout`);
                onData(str);
            });
            // Handle stderr
            sshProcess.stderr?.on('data', (data) => {
                const str = data.toString('utf-8');
                this.logger.debug(`📤 Received ${str.length} chars from stderr`);
                this.logger.debug(`📤 stderr content: ${str}`);
                // SSH protocol messages go to stderr, pass to onData
                onData(str);
            });
            // Handle process exit
            sshProcess.on('close', async (code, signal) => {
                this.logger.log(`SSH process closed (code: ${code}, signal: ${signal})`);
                // Cleanup temp files
                if (tempDir) {
                    try {
                        await node_fs_1.promises.rm(tempDir, { recursive: true, force: true });
                        this.logger.debug(`Cleaned up temp directory: ${tempDir}`);
                    }
                    catch (cleanupError) {
                        this.logger.warn(`Failed to cleanup temp dir: ${cleanupError.message}`);
                    }
                }
                onClose();
            });
            // Handle process errors
            sshProcess.on('error', (error) => {
                this.logger.error(`SSH process error: ${error.message}`);
                onError(error);
            });
            // Cleanup function
            const cleanup = async () => {
                this.logger.log('🧹 Cleaning up SSH connection...');
                // Kill process if still running
                if (sshProcess && !sshProcess.killed) {
                    sshProcess.kill('SIGTERM');
                    this.logger.debug('SSH process killed');
                }
                // Remove temp directory
                if (tempDir) {
                    try {
                        await node_fs_1.promises.rm(tempDir, { recursive: true, force: true });
                        this.logger.debug(`Cleaned up temp directory: ${tempDir}`);
                    }
                    catch (cleanupError) {
                        this.logger.warn(`Failed to cleanup temp dir: ${cleanupError.message}`);
                    }
                }
            };
            return {
                process: sshProcess,
                cleanup,
            };
        }
        catch (error) {
            this.logger.error(`Failed to create SSH connection: ${error.message}`);
            // Cleanup on error
            if (tempDir) {
                try {
                    await node_fs_1.promises.rm(tempDir, { recursive: true, force: true });
                }
                catch (cleanupError) {
                    this.logger.warn(`Failed to cleanup temp dir on error: ${cleanupError.message}`);
                }
            }
            throw error;
        }
    }
    /**
     * Execute a single command over SSH and return stdout.
     * Uses plain private-key auth (no certificate) — suitable for bootstrap key access.
     */
    async execCommand(host, username, privateKey, command, timeoutMs = 30000) {
        let tempDir = null;
        try {
            tempDir = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'flui-ssh-exec-'));
            const keyPath = path.join(tempDir, 'key');
            await node_fs_1.promises.writeFile(keyPath, privateKey, { mode: 0o600 });
            return await new Promise((resolve, reject) => {
                const timer = setTimeout(() => reject(new Error(`SSH exec timeout after ${timeoutMs}ms`)), timeoutMs);
                const proc = (0, node_child_process_1.spawn)('ssh', [
                    '-i',
                    keyPath,
                    '-o',
                    'StrictHostKeyChecking=no',
                    '-o',
                    'UserKnownHostsFile=/dev/null',
                    '-o',
                    'LogLevel=ERROR',
                    '-o',
                    'ConnectTimeout=10',
                    `${username}@${host}`,
                    command,
                ], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
                let stdout = '';
                let stderr = '';
                proc.stdout?.on('data', (d) => (stdout += d.toString()));
                proc.stderr?.on('data', (d) => (stderr += d.toString()));
                proc.on('close', (code) => {
                    clearTimeout(timer);
                    if (code === 0) {
                        resolve(stdout);
                    }
                    else {
                        reject(new Error(`SSH exec failed (code ${code}): ${stderr.trim()}`));
                    }
                });
                proc.on('error', (err) => {
                    clearTimeout(timer);
                    reject(err);
                });
            });
        }
        finally {
            if (tempDir) {
                await node_fs_1.promises.rm(tempDir, { recursive: true, force: true }).catch(() => { });
            }
        }
    }
    /**
     * Write data to SSH stdin
     */
    writeData(sshProcess, data) {
        if (sshProcess.stdin?.writable) {
            sshProcess.stdin.write(data);
            this.logger.debug(`✍️ Wrote ${data.length} chars to SSH stdin: ${JSON.stringify(data.substring(0, 20))}`);
        }
        else {
            this.logger.error('❌ SSH stdin is not writable');
            throw new Error('SSH stdin is not writable');
        }
    }
    /**
     * Resize terminal using stty command
     * Sends stty command to the remote shell to update terminal size
     */
    resizeTerminal(sshProcess, rows, cols) {
        if (sshProcess.stdin?.writable) {
            // \x15 = Ctrl+U clears the current input line (no echo)
            // stty updates the PTY dimensions on the remote shell
            // \x0c = Ctrl+L triggers a silent screen redraw (no visible command)
            sshProcess.stdin.write(`\x15stty rows ${rows} cols ${cols}\r`);
            // Small delay to let stty complete, then redraw cleanly
            setTimeout(() => {
                if (sshProcess.stdin?.writable) {
                    sshProcess.stdin.write('\x0c');
                }
            }, 50);
            this.logger.debug(`Terminal resized to ${cols}x${rows}`);
        }
        else {
            this.logger.warn(`Cannot resize terminal - stdin not writable`);
        }
    }
};
exports.NativeSSHConnectionService = NativeSSHConnectionService;
exports.NativeSSHConnectionService = NativeSSHConnectionService = NativeSSHConnectionService_1 = __decorate([
    (0, common_1.Injectable)()
], NativeSSHConnectionService);
