import express from 'express';
import cors from 'cors';
import si from 'systeminformation';
import Docker from 'dockerode';
import { createServer } from 'http';
import { Server } from 'socket.io';
import fs from 'fs';
import path from 'path';
import webpush from 'web-push';
import crypto from 'crypto';

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"]
    }
});
const PORT = 8001;

// Docker Connection
const docker = new Docker({ socketPath: '/var/run/docker.sock' });

app.use(cors());
app.use(express.json());

// Helper: Read Synology Info from Host
const getSynologyInfo = () => {
    try {
        const versionPath = '/host/etc/VERSION';
        const synoInfoPath = '/host/etc/synoinfo.conf';

        let dsmVersion = 'Unknown';
        let model = 'Synology NAS';

        if (fs.existsSync(versionPath)) {
            const versionContent = fs.readFileSync(versionPath, 'utf8');
            const productVersion = versionContent.match(/productversion="([^"]+)"/);
            const buildNumber = versionContent.match(/buildnumber="([^"]+)"/);
            if (productVersion && buildNumber) {
                dsmVersion = `DSM ${productVersion[1]}-${buildNumber[1]}`;
            }
        }

        if (fs.existsSync(synoInfoPath)) {
            // Try to read model from synoinfo (unique="synology_geminilake_920+") or similar
            // Or usually /proc/sys/kernel/syno_hw_version if available
            // Simpler fallback: check hostname or use generic if deep parsing fails.
            // Actually synoinfo.conf has 'upnpmodelname="DS920+"' usually.
            const infoContent = fs.readFileSync(synoInfoPath, 'utf8');
            const modelMatch = infoContent.match(/upnpmodelname="([^"]+)"/);
            if (modelMatch) {
                model = modelMatch[1];
            }
        }

        return { model, dsmVersion };
    } catch (e) {
        console.error('Failed to read host files:', e);
        return { model: 'Docker Container', dsmVersion: 'Unknown' };
    }
};

// --- Docker Config & Security ---
const DATA_DIR = path.join(process.cwd(), 'data');
const CONFIG_FILE = path.join(DATA_DIR, 'config.json');

// Ensure data directory exists
if (!fs.existsSync(DATA_DIR)) {
    try { fs.mkdirSync(DATA_DIR, { recursive: true }); }
    catch (e) { console.error('Failed to create data directory:', e); }
}

let dockerConfig = {
    enabled: false,
    passwordHash: null,
    salt: null,
    retentionHours: 24,
    alertThresholds: { cpu: 80, memory: 80, disk: 90 },
    containerAlertEnabled: false,
    alertCooldownSeconds: 300  // 5 minutes default
};

const saveDockerConfig = () => {
    try { fs.writeFileSync(CONFIG_FILE, JSON.stringify(dockerConfig, null, 2)); }
    catch (e) { console.error('Failed to save docker config:', e); }
};

const loadDockerConfig = () => {
    try {
        if (fs.existsSync(CONFIG_FILE)) {
            const data = fs.readFileSync(CONFIG_FILE, 'utf8');
            dockerConfig = JSON.parse(data);
            // Merge defaults
            if (!dockerConfig.alertThresholds) dockerConfig.alertThresholds = { cpu: 90, mem: 90, disk: 90 };
            // Restore history limit from saved retention hours
            // Note: historyRetentionHours and HISTORY_LIMIT are defined later in the file
            // This function is called AFTER those variables are initialized (line 438)
            if (dockerConfig.retentionHours) {
                historyRetentionHours = dockerConfig.retentionHours;
                HISTORY_LIMIT = historyRetentionHours * 60;
            }
        } else {
            saveDockerConfig();
        }
    } catch (e) { console.error('Failed to load docker config:', e); }
};
// I will Stub loadDockerConfig here and call the real logic later? 
// No, I should move History Vars up too.

// --- Helper: Secure Hash ---
const hashPassword = (password, salt = null) => {
    if (!password) return null;
    try {
        if (!salt) salt = crypto.randomBytes(16).toString('hex');
        const hash = crypto.scryptSync(password, salt, 64).toString('hex');
        return { hash, salt };
    } catch (e) { console.error("Hashing error:", e); return null; }
};

// --- Push Notification System Variables ---
const SUBSCRIPTIONS_FILE = path.join(process.cwd(), 'data', 'subscriptions.json');
let subscriptions = [];
let vapidKeys = { publicKey: '', privateKey: '' };
let alertState = { cpu: 0, memory: 0, disk: 0 };

const initPushNotifications = () => {
    // Ensure data directory exists
    if (!fs.existsSync(process.cwd() + '/data')) {
        fs.mkdirSync(process.cwd() + '/data', { recursive: true });
    }

    // Initialize VAPID keys
    if (!fs.existsSync(process.cwd() + '/data/vapid.json')) {
        vapidKeys = webpush.generateVAPIDKeys();
        fs.writeFileSync(process.cwd() + '/data/vapid.json', JSON.stringify(vapidKeys));
    } else {
        vapidKeys = JSON.parse(fs.readFileSync(process.cwd() + '/data/vapid.json', 'utf8'));
    }
    webpush.setVapidDetails('mailto:admin@example.com', vapidKeys.publicKey, vapidKeys.privateKey);

    // Initialize subscriptions - CREATE FILE IF MISSING
    if (fs.existsSync(SUBSCRIPTIONS_FILE)) {
        try {
            subscriptions = JSON.parse(fs.readFileSync(SUBSCRIPTIONS_FILE, 'utf8'));
            console.log('[PUSH] Loaded', subscriptions.length, 'existing subscriptions');
        } catch (e) {
            console.error('[PUSH] Error loading subscriptions:', e);
            subscriptions = [];
        }
    } else {
        console.log('[PUSH] Creating new subscriptions file');
        subscriptions = [];
        fs.writeFileSync(SUBSCRIPTIONS_FILE, JSON.stringify(subscriptions));
    }
};

const saveSubscriptions = () => {
    try { fs.writeFileSync(SUBSCRIPTIONS_FILE, JSON.stringify(subscriptions)); }
    catch (e) { console.error('Failed to save subscriptions:', e); }
};

const sendNotification = (payload) => {
    const notificationPayload = JSON.stringify(payload);
    console.log('[PUSH] Sending notification to', subscriptions.length, 'subscribers');
    console.log('[PUSH] Payload:', payload);

    if (subscriptions.length === 0) {
        console.warn('[PUSH] No subscriptions available!');
        return;
    }

    subscriptions.forEach((subscription, index) => {
        console.log(`[PUSH] Sending to subscription ${index + 1}/${subscriptions.length}`);
        webpush.sendNotification(subscription, notificationPayload)
            .then(() => {
                console.log(`[PUSH] Successfully sent to subscription ${index + 1}`);
            })
            .catch(error => {
                console.error(`[PUSH] Error sending to subscription ${index + 1}:`, error);
                if (error.statusCode === 410 || error.statusCode === 404) {
                    console.log('[PUSH] Removing expired subscription');
                    subscriptions.splice(index, 1);
                    saveSubscriptions();
                }
            });
    });
};

const checkAlerts = (stats) => {
    const now = Date.now();
    const thresholds = dockerConfig.alertThresholds || { cpu: 90, memory: 90, disk: 90 };
    const COOLDOWN = (dockerConfig.alertCooldownSeconds || 300) * 1000; // Convert seconds to ms

    if (stats.cpu > thresholds.cpu) {
        if (now - alertState.cpu > COOLDOWN) {
            console.log('[ALERT] CPU alert sent:', stats.cpu + '%');
            sendNotification({ title: 'snStatus 알림', body: `CPU 사용률 높음: ${stats.cpu}%` });
            alertState.cpu = now;
        }
    } else { alertState.cpu = 0; }

    if (stats.memory > thresholds.memory) {
        if (now - alertState.memory > COOLDOWN) {
            console.log('[ALERT] Memory alert sent:', stats.memory + '%');
            sendNotification({ title: 'snStatus 알림', body: `메모리 사용률 높음: ${stats.memory}%` });
            alertState.memory = now;
        }
    } else { alertState.memory = 0; }
};

// VAPID Public Key Endpoint
app.get('/api/notifications/vapidPublicKey', (req, res) => {
    try {
        if (!vapidKeys || !vapidKeys.publicKey) {
            return res.status(500).json({ error: 'VAPID keys not initialized' });
        }
        res.json({ publicKey: vapidKeys.publicKey });
    } catch (error) {
        console.error('Error getting VAPID key:', error);
        res.status(500).json({ error: 'Failed to get VAPID key' });
    }
});

// Subscribe to push notifications
app.post('/api/notifications/subscribe', async (req, res) => {
    const subscription = req.body;
    if (!subscription || !subscription.endpoint) {
        return res.status(400).json({ error: 'Invalid subscription' });
    }
    // Remove existing subscription with same endpoint
    subscriptions = subscriptions.filter(sub => sub.endpoint !== subscription.endpoint);
    subscriptions.push(subscription);
    saveSubscriptions();
    console.log('Push subscription added:', subscription.endpoint);
    res.status(201).json({ message: 'Subscription added' });
});

// Unsubscribe from push notifications
app.post('/api/notifications/unsubscribe', async (req, res) => {
    const { endpoint } = req.body;
    subscriptions = subscriptions.filter(sub => sub.endpoint !== endpoint);
    saveSubscriptions();
    console.log('Push subscription removed:', endpoint);
    res.json({ message: 'Unsubscribed' });
});

// --- System Info APIs ---
// Global state for network speed calculation
let previousNetStats = {};
let previousTime = Date.now();

// Function to read and parse /host/proc/net/dev
const getHostNetworkStats = () => {
    try {
        // Use /sys/class/net mounted from host to bypass container network namespace isolation.
        // This is the correct way to see host interfaces (eth0, eth1, etc.) from a bridge container.
        const netDir = '/host/sys/class/net';

        let interfaces = [];
        try {
            if (fs.existsSync(netDir)) {
                interfaces = fs.readdirSync(netDir);
            } else {
                console.warn("Host sysfs not found at /host/sys/class/net");
                return [];
            }
        } catch (e) { return []; }

        const timestamp = Date.now();
        const currentStats = {};
        const timeDiff = (timestamp - previousTime) / 1000;
        let result = [];

        for (const iface of interfaces) {
            // Filter unwanted interfaces
            if (iface === 'lo' ||
                iface.startsWith('veth') ||
                iface.startsWith('docker') ||
                iface.startsWith('br-') ||
                iface.startsWith('cali') ||
                iface.startsWith('flannel') ||
                iface.startsWith('cni') ||
                iface.startsWith('sit') ||
                iface.startsWith('tun') ||
                iface.startsWith('tap')) continue;

            try {
                const rxPath = path.join(netDir, iface, 'statistics', 'rx_bytes');
                const txPath = path.join(netDir, iface, 'statistics', 'tx_bytes');

                if (!fs.existsSync(rxPath) || !fs.existsSync(txPath)) continue;

                const rxBytes = parseInt(fs.readFileSync(rxPath, 'utf8').trim());
                const txBytes = parseInt(fs.readFileSync(txPath, 'utf8').trim());

                currentStats[iface] = { rx: rxBytes, tx: txBytes };

                let rx_sec = 0;
                let tx_sec = 0;

                if (previousNetStats[iface] && timeDiff > 0) {
                    const prevRx = previousNetStats[iface].rx;
                    const prevTx = previousNetStats[iface].tx;

                    // Simple difference (handle counter wrap if needed, but unlikely for 64bit counters in JS for short periods)
                    if (rxBytes >= prevRx) rx_sec = (rxBytes - prevRx) / timeDiff;
                    if (txBytes >= prevTx) tx_sec = (txBytes - prevTx) / timeDiff;
                }

                result.push({
                    iface: iface,
                    rx_sec: rx_sec,
                    tx_sec: tx_sec
                });

            } catch (err) {
                continue;
            }
        }

        previousNetStats = currentStats;
        previousTime = timestamp;

        return result;

    } catch (error) {
        console.error('Failed to read host network stats from /sys:', error.message);
        return [];
    }
};

app.get('/api/stats', async (req, res) => {
    try {
        const [cpu, mem, fsData, osInfo, system] = await Promise.all([
            si.currentLoad(),
            si.mem(),
            si.fsSize(),
            si.osInfo(),
            si.system()
        ]);

        // Get Host Network Stats
        const network = getHostNetworkStats();

        // Get Host Info
        const hostInfo = getSynologyInfo();

        // Disk Filtering Logic
        const filteredStorage = fsData.filter(disk => {
            let mountPoint = disk.mount;
            if (mountPoint.startsWith('/host')) mountPoint = mountPoint.substring(5);
            if (mountPoint.includes('@') || mountPoint.includes('docker') || mountPoint.includes('container') ||
                mountPoint.includes('appdata') || mountPoint.includes('tmp') ||
                disk.fs.startsWith('overlay') || disk.fs.startsWith('tmpfs') || disk.fs.startsWith('shm')) {
                return false;
            }
            return /^\/volume\d+$/.test(mountPoint);
        });

        const stats = {
            cpu: {
                load: Math.round(cpu.currentLoad),
                cores: cpu.cpus.map(c => Math.round(c.load)),
            },
            memory: {
                total: mem.total,
                used: mem.used,
                percentage: Math.round((mem.active / mem.total) * 100),
            },
            storage: filteredStorage.map(disk => ({
                fs: disk.fs,
                type: disk.type,
                size: disk.size,
                used: disk.used,
                use: Math.round(disk.use),
                mount: disk.mount,
            })),
            network: network, // Use custom stats
            system: {
                model: hostInfo.model !== 'Docker Container' ? hostInfo.model : system.model, // Prefer host info
                manufacturer: system.manufacturer,
                os: 'DSM', // Hardcode or derive
                release: hostInfo.dsmVersion,
                kernel: osInfo.kernel,
                hostname: osInfo.hostname,
                uptime: osInfo.uptime
            }
        };

        res.json(stats);

    } catch (error) {
        console.error('Error fetching system stats:', error);
        res.status(500).json({ error: 'Failed to retrieve system statistics' });
    }
});

// --- History Data ---
const HISTORY_FILE = path.join(process.cwd(), 'data', 'history.json');
let historyRetentionHours = 24; // Default 24 hours
let HISTORY_LIMIT = historyRetentionHours * 60; // points (1 min interval)
let historyData = [];

const saveHistory = () => {
    try {
        fs.writeFileSync(HISTORY_FILE, JSON.stringify(historyData));
    } catch (e) { console.error('Failed to save history:', e); }
};

const loadHistory = () => {
    try {
        if (fs.existsSync(HISTORY_FILE)) {
            const data = fs.readFileSync(HISTORY_FILE, 'utf8');
            historyData = JSON.parse(data);
            // Ensure data respects current limit (if limit changed while offline? Unlikely but good practice)
            if (historyData.length > HISTORY_LIMIT) {
                historyData = historyData.slice(historyData.length - HISTORY_LIMIT);
            }
        }
    } catch (e) { console.error('Failed to load history:', e); }
};
loadHistory();

// Retention Settings API
app.get('/api/settings/retention', (req, res) => {
    res.json({ hours: historyRetentionHours });
});

app.post('/api/settings/retention', (req, res) => {
    const { hours } = req.body;
    if (!hours || hours < 1) {
        return res.status(400).json({ error: 'Invalid retention period' });
    }
    historyRetentionHours = parseInt(hours);
    HISTORY_LIMIT = historyRetentionHours * 60;

    // Trim existing data if needed
    if (historyData.length > HISTORY_LIMIT) {
        historyData = historyData.slice(historyData.length - HISTORY_LIMIT);
    }
    saveHistory(); // Save truncated data

    // Save preference to config
    dockerConfig.retentionHours = historyRetentionHours;
    saveDockerConfig();

    console.log(`History retention updated to ${historyRetentionHours} hours (${HISTORY_LIMIT} points)`);
    res.json({ message: 'Retention updated', hours: historyRetentionHours });
});

const updateHistory = async () => {
    try {
        const [cpu, mem] = await Promise.all([
            si.currentLoad(),
            si.mem()
        ]);

        // Capture Network Stats for History
        // Note: This relies on the shared getHostNetworkStats state. 
        // It captures the "current rate" since the last poll (whether by UI or this loop).
        const netStats = getHostNetworkStats();
        let totalRx = 0;
        let totalTx = 0;
        netStats.forEach(net => {
            // Include all interfaces or filter? Let's include all physical-ish ones found by our filter
            totalRx += net.rx_sec;
            totalTx += net.tx_sec;
        });

        const point = {
            timestamp: Date.now(),
            cpu: Math.round(cpu.currentLoad),
            memory: Math.round((mem.active / mem.total) * 100),
            network: {
                rx: totalRx,
                tx: totalTx
            }
        };

        historyData.push(point);
        if (historyData.length > HISTORY_LIMIT) {
            historyData.shift(); // Remove oldest
        }

        saveHistory();
        checkAlerts(point);
    } catch (e) {
        console.error("Failed to update history:", e);
    }
};

// Update history every 1 minute
setInterval(updateHistory, 60 * 1000);
// Initial run
updateHistory();

app.get('/api/history', (req, res) => {
    res.json(historyData);
});


// Config saved via helper
// NOTE: loadDockerConfig normally called here.
// But check scope: HISTORY_LIMIT is defined above at 220.
// Our new loadDockerConfig at top CANNOT see HISTORY_LIMIT.
// So we must fix loadDockerConfig logic.
// I'll handle that separately. For now, deleting this block.

// Helper: Secure Hash (Scrypt + Salt)


loadDockerConfig(); // Initial Load

// Docker Config API
app.get('/api/docker/config', (req, res) => {
    res.json({
        enabled: dockerConfig.enabled,
        alertThresholds: dockerConfig.alertThresholds || { cpu: 90, memory: 90, disk: 90 },
        containerAlertEnabled: dockerConfig.containerAlertEnabled !== undefined ? dockerConfig.containerAlertEnabled : true,
        alertCooldownSeconds: dockerConfig.alertCooldownSeconds || 300
    });
});

app.post('/api/docker/config', (req, res) => {
    const { enabled, password } = req.body;
    const isEnabling = !!enabled;

    // Case 1: Disabling (Require Verification)
    if (!isEnabling && dockerConfig.enabled) {
        if (!password) return res.status(400).json({ error: 'Password is required to disable Docker features.' });

        if (dockerConfig.passwordHash) {
            let match = false;

            if (!dockerConfig.salt) {
                // Legacy SHA256 Check
                const legacyHash = crypto.createHash('sha256').update(password).digest('hex');
                match = (legacyHash === dockerConfig.passwordHash);
            } else {
                // Modern Scrypt Check
                try {
                    const { hash } = hashPassword(password, dockerConfig.salt);
                    match = crypto.timingSafeEqual(
                        Buffer.from(hash, 'hex'),
                        Buffer.from(dockerConfig.passwordHash, 'hex')
                    );
                } catch (e) {
                    return res.status(500).json({ error: 'Verification error.' });
                }
            }

            if (!match) return res.status(401).json({ error: '패스워드가 올바르지 않습니다.' });
        }
    }

    // Case 2: Enabling (Require Password if no existing hash)
    if (isEnabling && !password && !dockerConfig.passwordHash) {
        return res.status(400).json({ error: 'Password is required to enable Docker features.' });
    }

    dockerConfig.enabled = isEnabling;

    // If Enabling and password provided, update/set password
    if (isEnabling && password) {
        const result = hashPassword(password);
        if (result) {
            dockerConfig.passwordHash = result.hash;
            dockerConfig.salt = result.salt;
        }
    }

    saveDockerConfig();
    console.log(`Docker feature ${dockerConfig.enabled ? 'Enabled' : 'Disabled'}`);
    res.json({ message: 'Configuration saved', enabled: dockerConfig.enabled });
});

// Verify Password API
app.post('/api/docker/verify', (req, res) => {
    const { password } = req.body;
    if (!password) return res.status(400).json({ success: false });

    // Validate if config exists
    if (!dockerConfig.passwordHash || !dockerConfig.salt) {
        return res.status(500).json({ success: false, error: 'Security configuration not found. Please reset in Settings.' });
    }

    const { hash } = hashPassword(password, dockerConfig.salt);

    // Constant-time comparison to prevent timing attacks
    try {
        const match = crypto.timingSafeEqual(
            Buffer.from(hash, 'hex'),
            Buffer.from(dockerConfig.passwordHash, 'hex')
        );

        if (match) {
            res.json({ success: true });
        } else {
            res.status(401).json({ success: false, error: '패스워드가 올바르지 않습니다.' });
        }
    } catch (e) {
        res.status(401).json({ success: false, error: 'Verification failed' });
    }
});

// --- Docker APIs ---
// Helper: Calculate CPU Percent from Docker Stats
const calculateCPUPercent = (stats) => {
    if (!stats || !stats.cpu_stats || !stats.precpu_stats) return 0;

    const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
    const systemCpuDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
    const numberCpus = stats.cpu_stats.online_cpus || (stats.cpu_stats.cpu_usage.percpu_usage ? stats.cpu_stats.cpu_usage.percpu_usage.length : 1);

    if (systemCpuDelta > 0 && cpuDelta > 0) {
        return (cpuDelta / systemCpuDelta) * numberCpus * 100.0;
    }
    return 0;
};

// --- Docker APIs ---
app.get('/api/docker/containers', async (req, res) => {
    // Check if enabled
    if (!dockerConfig.enabled) {
        return res.status(403).json({ error: 'Docker features are disabled.' });
    }

    try {
        const containers = await docker.listContainers({ all: true });

        // Fetch stats for running containers
        const containersWithStats = await Promise.all(containers.map(async (containerInfo) => {
            if (containerInfo.State !== 'running') {
                return { ...containerInfo, stats: null };
            }

            try {
                const container = docker.getContainer(containerInfo.Id);
                const stats = await container.stats({ stream: false });

                const cpuPercent = calculateCPUPercent(stats);
                const memoryUsage = stats.memory_stats.usage || 0;
                const memoryLimit = stats.memory_stats.limit || 0;
                const memoryPercent = memoryLimit > 0 ? (memoryUsage / memoryLimit) * 100 : 0;

                return {
                    ...containerInfo,
                    stats: {
                        cpu: cpuPercent,
                        memory: memoryUsage,
                        memoryLimit: memoryLimit,
                        memoryPercent: memoryPercent
                    }
                };
            } catch (e) {
                console.error(`Failed to fetch stats for ${containerInfo.Names[0]}:`, e.message);
                return { ...containerInfo, stats: null };
            }
        }));

        res.json(containersWithStats);
    } catch (error) {
        console.error('Error fetching containers:', error);
        res.status(500).json({ error: 'Failed to fetch containers. Is Docker Sock mounted?' });
    }
});

// Container Control APIs
app.post('/api/docker/containers/:id/:action', async (req, res) => {
    const { id, action } = req.params;
    try {
        const container = docker.getContainer(id);
        let data;
        switch (action) {
            case 'start': data = await container.start(); break;
            case 'stop': data = await container.stop(); break;
            case 'restart': data = await container.restart(); break;
            default: return res.status(400).json({ error: 'Invalid action' });
        }
        res.json({ message: `Container ${action} successful`, data });
    } catch (error) {
        console.error(`Error performing ${action} on container ${id}:`, error);
        res.status(500).json({ error: `Failed to ${action} container: ${error.message}` });
    }
});

// Save Alert Thresholds
app.post('/api/settings/thresholds', (req, res) => {
    const { thresholds, containerAlertEnabled, alertCooldownSeconds } = req.body;
    if (thresholds) {
        dockerConfig.alertThresholds = thresholds;
        if (containerAlertEnabled !== undefined) {
            dockerConfig.containerAlertEnabled = containerAlertEnabled;
        }
        if (alertCooldownSeconds !== undefined) {
            dockerConfig.alertCooldownSeconds = Math.max(10, Math.min(3600, alertCooldownSeconds));
        }
        saveDockerConfig();
        res.json({ success: true });
    } else {
        res.status(400).json({ error: 'Invalid thresholds' });
    }
});

// Container Logs API
app.get('/api/docker/containers/:id/logs', async (req, res) => {
    const { id } = req.params;
    try {
        const container = docker.getContainer(id);
        const logs = await container.logs({ stdout: true, stderr: true, tail: 100, timestamps: true });
        res.send(logs.toString('utf8'));
    } catch (error) {
        console.error('Error fetching logs:', error);
        res.status(500).json({ error: 'Failed to fetch logs' });
    }
});


const monitorDockerEvents = () => {
    docker.getEvents((err, stream) => {
        if (err) return console.error('Error getting docker events:', err);

        stream.on('data', chunk => {
            if (!dockerConfig.enabled) return; // Ignore if disabled
            if (!dockerConfig.containerAlertEnabled) return; // Ignore if container alerts disabled

            try {
                const event = JSON.parse(chunk.toString());
                // Filter for container stop/die
                if (event.Type === 'container' && (event.Action === 'die' || event.Action === 'stop')) {
                    // Check if it's "unexpected"?
                    // Usually we alert on any stop if the monitoring is strictly keeping uptime.
                    // But if user stops it manually via UI, we might get an event too.
                    // However, UI stop calls API -> API stops container.
                    // It is hard to distinguish "crash" from "manual stop" without state tracking.
                    // For now, user asked "컨테이너가 까지게 되면", effectively "stops".
                    // We will alert.
                    const containerName = event.Actor?.Attributes?.name || event.id.substring(0, 12);
                    sendNotification({
                        title: 'snStatus 알림',
                        body: `컨테이너 종료: ${containerName}`
                    });
                }
            } catch (e) {
                console.error('Error parsing docker event:', e);
            }
        });
    });
};

initPushNotifications();
if (dockerConfig.enabled) monitorDockerEvents(); // Start if enabled, or always start?
// Better always start listening, but filter inside.
// Actually monitorDockerEvents is a stream. If I call it multiple times it duplicates.
// Just call it once.
monitorDockerEvents();


// --- WebSocket for Terminal & Logs ---
io.on('connection', (socket) => {
    let stream = null;
    socket.on('attach-terminal', async (containerId) => {
        try {
            const container = docker.getContainer(containerId);
            const exec = await container.exec({
                AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: ['/bin/sh']
            });
            stream = await exec.start({ hijack: true, stdin: true });
            stream.on('data', (chunk) => socket.emit('terminal-output', chunk.toString('utf8')));
            socket.on('terminal-input', (data) => { if (stream) stream.write(data); });
            socket.on('resize-terminal', ({ cols, rows }) => { if (exec) exec.resize({ w: cols, h: rows }).catch(() => { }); });
        } catch (err) {
            console.error('Error attaching to container:', err);
            socket.emit('terminal-output', '\r\nError: Failed to attach to container. ' + err.message + '\r\n');
        }
    });

    socket.on('disconnect', () => { if (stream) stream.end(); });
});

httpServer.listen(PORT, '0.0.0.0', () => {
    console.log(`Server running on port ${PORT}`);
});
