const NetworkDevice = require('../models/NetworkDevice');
const { subscribe, unsubscribe, broadcast } = require('../services/networkSseManager');
const { scheduleDevice, cancelDevice, runCheck } = require('../services/networkPoller.service');
const jwt = require('jsonwebtoken');
const JWT_CONFIG = require('../config/jwt');
const { spawn } = require('child_process');
const net = require('net');
const dns = require('dns').promises;

// ─── Auto-Discovery ───────────────────────────────────────────────────────────

function pingHost(ip, timeoutMs = 800) {
    return new Promise(resolve => {
        const proc = spawn('ping', ['-c', '1', '-W', '1', ip]);
        const kill = setTimeout(() => { proc.kill(); resolve(false); }, timeoutMs);
        proc.on('close', code => { clearTimeout(kill); resolve(code === 0); });
        proc.on('error', () => { clearTimeout(kill); resolve(false); });
    });
}

function checkPort(ip, port, timeoutMs = 500) {
    return new Promise(resolve => {
        const sock = new net.Socket();
        sock.setTimeout(timeoutMs);
        sock.connect(port, ip, () => { sock.destroy(); resolve(true); });
        sock.on('error', () => resolve(false));
        sock.on('timeout', () => { sock.destroy(); resolve(false); });
    });
}

async function resolveHostname(ip) {
    try {
        const names = await dns.reverse(ip);
        return names[0]?.replace(/\.$/, '') || null;
    } catch { return null; }
}

function guessType(openPorts) {
    if (openPorts.includes(9100)) return 'printer';
    if (openPorts.includes(23) || openPorts.includes(161)) return 'switch';
    if (openPorts.includes(5000) || openPorts.includes(5001) || openPorts.includes(2049)) return 'nas';
    if (openPorts.includes(445) || openPorts.includes(139)) return 'nas';
    if (openPorts.includes(80) || openPorts.includes(443) || openPorts.includes(22)) return 'host';
    return 'host';
}

async function scanHost(ip) {
    const alive = await pingHost(ip);
    if (!alive) return null;

    // Check common ports in parallel
    const PORT_MAP = [22, 23, 80, 443, 445, 161, 9100, 5000, 5001, 8080, 8443, 3389];
    const portResults = await Promise.all(PORT_MAP.map(p => checkPort(ip, p).then(ok => ok ? p : null)));
    const openPorts = portResults.filter(Boolean);

    const hostname = await resolveHostname(ip);
    const type = guessType(openPorts);
    const checkType = openPorts.includes(443) ? 'https' : openPorts.includes(80) ? 'http' : 'icmp';

    return { ip, hostname, type, check_type: checkType, open_ports: openPorts };
}

async function runInBatches(items, batchSize, fn) {
    const results = [];
    for (let i = 0; i < items.length; i += batchSize) {
        const batch = items.slice(i, i + batchSize);
        const res = await Promise.all(batch.map(fn));
        results.push(...res);
    }
    return results;
}

const discover = async (req, res) => {
    try {
        const { subnet } = req.body; // e.g. "192.168.0" or "192.168.0.0/24"
        if (!subnet) return res.status(400).json({ status: 'error', message: 'subnet fehlt' });

        // Parse subnet to base (first 3 octets)
        let base = subnet.replace(/\/\d+$/, '').trim();
        const parts = base.split('.');
        if (parts.length === 4) base = parts.slice(0, 3).join('.');
        if (parts.length < 3) return res.status(400).json({ status: 'error', message: 'Ungültiges Subnet' });

        const ips = Array.from({ length: 254 }, (_, i) => `${base}.${i + 1}`);

        // Existing devices to skip
        const existing = NetworkDevice.getAll().map(d => d.host);

        // Scan in batches of 40 parallel pings
        const raw = await runInBatches(ips, 40, scanHost);
        const found = raw
            .filter(r => r !== null && !existing.includes(r.ip))
            .sort((a, b) => {
                const ai = parseInt(a.ip.split('.').pop());
                const bi = parseInt(b.ip.split('.').pop());
                return ai - bi;
            });

        res.json({ status: 'success', data: found, scanned: ips.length });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

// ─── SSE Stream ───────────────────────────────────────────────────────────────

const sseStream = (req, res) => {
    res.set({
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'X-Accel-Buffering': 'no',
    });
    res.flushHeaders();

    // Send initial state
    const devices = NetworkDevice.getAll();
    res.write(`event: initial_state\ndata: ${JSON.stringify(devices)}\n\n`);

    // Heartbeat
    const hb = setInterval(() => { try { res.write(': heartbeat\n\n'); } catch (_) {} }, 15000);

    subscribe(res);
    req.on('close', () => { clearInterval(hb); unsubscribe(res); });
};

// ─── CRUD ─────────────────────────────────────────────────────────────────────

const getAll = (req, res) => {
    try {
        const devices = NetworkDevice.getAll();
        res.json({ status: 'success', data: devices });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const getStatistics = (req, res) => {
    try {
        const stats = NetworkDevice.getStatistics();
        res.json({ status: 'success', data: stats });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const getChecks = (req, res) => {
    try {
        const hours = parseInt(req.query.hours) || 24;
        const checks = NetworkDevice.getChecks(req.params.id, hours);
        const uptime = NetworkDevice.getUptimeStats(req.params.id, hours);
        res.json({ status: 'success', data: { checks, uptime } });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const createDevice = (req, res) => {
    try {
        const data = sanitize(req.body);
        const device = NetworkDevice.create(data);
        scheduleDevice(device);
        broadcast('device_update', device);
        res.status(201).json({ status: 'success', data: device });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const updateDevice = (req, res) => {
    try {
        const data = sanitize(req.body);
        const device = NetworkDevice.update(req.params.id, data);
        if (!device) return res.status(404).json({ status: 'error', message: 'Nicht gefunden' });
        if (device.enabled) scheduleDevice(device);
        else cancelDevice(device.id);
        broadcast('device_update', device);
        res.json({ status: 'success', data: device });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const deleteDevice = (req, res) => {
    try {
        cancelDevice(parseInt(req.params.id));
        NetworkDevice.delete(req.params.id);
        broadcast('device_deleted', { id: parseInt(req.params.id) });
        res.json({ status: 'success' });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const checkNow = async (req, res) => {
    try {
        const device = NetworkDevice.getById(req.params.id);
        if (!device) return res.status(404).json({ status: 'error', message: 'Nicht gefunden' });
        await runCheck(device);
        const updated = NetworkDevice.getById(req.params.id);
        res.json({ status: 'success', data: updated });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

const getAllUptimeStats = (req, res) => {
    try {
        const devices = NetworkDevice.getAll();
        const stats = {};
        devices.forEach(d => { stats[d.id] = NetworkDevice.getUptimeStats(d.id, 24); });
        res.json({ status: 'success', data: stats });
    } catch (err) {
        res.status(500).json({ status: 'error', message: err.message });
    }
};

// ─── Helpers ─────────────────────────────────────────────────────────────────

function sanitize(body) {
    return {
        name: body.name?.trim() || '',
        type: body.type || 'host',
        host: body.host?.trim() || '',
        check_type: body.check_type || 'icmp',
        port: body.port ? parseInt(body.port) : null,
        http_path: body.http_path || '/',
        http_keyword: body.http_keyword || null,
        snmp_community: body.snmp_community?.trim() || 'public',
        snmp_version: ['1', '2c'].includes(body.snmp_version) ? body.snmp_version : '2c',
        interval_sec: parseInt(body.interval_sec) || 60,
        timeout_sec: parseInt(body.timeout_sec) || 5,
        enabled: body.enabled === false || body.enabled === 0 ? 0 : 1,
        notify_email: body.notify_email || null,
        location: body.location || null,
    };
}

module.exports = { sseStream, getAll, getStatistics, getChecks, getAllUptimeStats, createDevice, updateDevice, deleteDevice, checkNow, discover };
