const { spawn } = require('child_process');
const net = require('net');
const http = require('http');
const https = require('https');
const NetworkDevice = require('../models/NetworkDevice');
const { broadcast } = require('./networkSseManager');

let snmpLib = null;
try { snmpLib = require('net-snmp'); } catch (_) {}

// Map<deviceId, NodeJS.Timeout>
const timers = new Map();
let alertCallback = null; // set by server.js to send emails

// ─── Check Implementations ────────────────────────────────────────────────────

function checkIcmp(device) {
    return new Promise((resolve, reject) => {
        const timeout = device.timeout_sec || 5;
        const proc = spawn('ping', ['-c', '2', '-W', String(timeout), device.host]);
        let timedOut = false;
        const kill = setTimeout(() => {
            timedOut = true;
            proc.kill();
            reject(new Error('Ping timeout'));
        }, (timeout + 2) * 1000);

        proc.on('close', code => {
            clearTimeout(kill);
            if (timedOut) return;
            code === 0 ? resolve() : reject(new Error('Host unreachable'));
        });
        proc.on('error', err => { clearTimeout(kill); reject(err); });
    });
}

function checkTcp(device) {
    return new Promise((resolve, reject) => {
        if (!device.port) return reject(new Error('No port configured'));
        const sock = new net.Socket();
        sock.setTimeout((device.timeout_sec || 5) * 1000);
        sock.connect(device.port, device.host, () => { sock.destroy(); resolve(); });
        sock.on('error', err => { sock.destroy(); reject(err); });
        sock.on('timeout', () => { sock.destroy(); reject(new Error('TCP timeout')); });
    });
}

function checkHttp(device, secure) {
    return new Promise((resolve, reject) => {
        const lib = secure ? https : http;
        const port = device.port || (secure ? 443 : 80);
        const path = device.http_path || '/';
        const options = {
            hostname: device.host, port, path, method: 'GET',
            timeout: (device.timeout_sec || 5) * 1000,
            rejectUnauthorized: false,
        };
        const req = lib.request(options, res => {
            let body = '';
            res.on('data', chunk => body += chunk);
            res.on('end', () => {
                if (res.statusCode >= 500) return reject(new Error(`HTTP ${res.statusCode}`));
                if (device.http_keyword && !body.includes(device.http_keyword))
                    return reject(new Error(`Keyword "${device.http_keyword}" nicht gefunden`));
                resolve();
            });
        });
        req.on('error', err => reject(err));
        req.on('timeout', () => { req.destroy(); reject(new Error('HTTP timeout')); });
        req.end();
    });
}

function checkSnmp(device) {
    return new Promise((resolve, reject) => {
        if (!snmpLib) return reject(new Error('net-snmp not installed'));
        const community = device.snmp_community || 'public';
        const version = device.snmp_version === '1' ? snmpLib.Version1 : snmpLib.Version2c;
        const session = snmpLib.createSession(device.host, community, {
            version,
            timeout: (device.timeout_sec || 5) * 1000,
            retries: 1,
        });
        session.get(['1.3.6.1.2.1.1.3.0'], (error) => {
            session.close();
            error ? reject(error) : resolve();
        });
    });
}

// ─── Core Check Logic ─────────────────────────────────────────────────────────

async function runCheck(device) {
    const prevStatus = device.last_status;
    const start = Date.now();
    let status, rtt_ms, error_msg;

    try {
        if      (device.check_type === 'icmp')  await checkIcmp(device);
        else if (device.check_type === 'tcp')   await checkTcp(device);
        else if (device.check_type === 'http')  await checkHttp(device, false);
        else if (device.check_type === 'https') await checkHttp(device, true);
        else if (device.check_type === 'snmp')  await checkSnmp(device);
        rtt_ms = Date.now() - start;
        status = 'up';
    } catch (err) {
        status = 'down';
        error_msg = err.message;
    }

    // Persist
    NetworkDevice.updateStatus(device.id, status, rtt_ms);
    NetworkDevice.addCheck(device.id, status, rtt_ms, error_msg);

    // Alert on status change
    if (prevStatus !== 'unknown' && prevStatus !== status && alertCallback) {
        alertCallback(device, status);
    }

    // Broadcast via SSE
    const updated = NetworkDevice.getById(device.id);
    broadcast('device_update', { ...updated, rtt_ms, error_msg });
}

// ─── Scheduler ────────────────────────────────────────────────────────────────

function scheduleDevice(device) {
    cancelDevice(device.id);
    if (!device.enabled) return;

    // Run immediately once
    runCheck(device).catch(() => {});

    const timer = setInterval(() => {
        const fresh = NetworkDevice.getById(device.id);
        if (!fresh || !fresh.enabled) { cancelDevice(device.id); return; }
        runCheck(fresh).catch(() => {});
    }, device.interval_sec * 1000);

    timers.set(device.id, timer);
}

function cancelDevice(deviceId) {
    if (timers.has(deviceId)) {
        clearInterval(timers.get(deviceId));
        timers.delete(deviceId);
    }
}

function startPoller(emailAlertCb) {
    alertCallback = emailAlertCb || null;
    const devices = NetworkDevice.getEnabled();
    for (const d of devices) scheduleDevice(d);
    console.log(`🌐 Netzwerk-Polling gestartet (${devices.length} Geräte)`);
}

function stopPoller() {
    for (const [id] of timers) cancelDevice(id);
}

module.exports = { startPoller, stopPoller, scheduleDevice, cancelDevice, runCheck };
