const { WebSocketServer } = require('ws');
const { decode } = require('@msgpack/msgpack');
const { consumeConnectionToken, appendAsConnection, query, hset, del } = require('../store/redis');
const { writeGenericData, writeForecastBatch } = require('../store/influx');
const db = require('../store/db');

// In-memory registries
const sensorWatchers = new Map();   // sensorName → Set<WebSocket> (watchers)
const connectedSensors = new Map(); // sensorName → WebSocket (sensor clients)

function generateSessionId() {
    const num = Math.floor(1000 + Math.random() * 9000);
    return `s${num}`;
}

function setup(server) {
    const wss = new WebSocketServer({ noServer: true });

    server.on('upgrade', async (req, socket, head) => {
        const url = new URL(req.url, `http://${req.headers.host}`);
        const path = url.pathname;

        if (path === '/' || path === '') {
            const token = url.searchParams.get('token');
            if (!token) {
                socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
                socket.destroy();
                return;
            }

            const sensor = await consumeConnectionToken(token);
            if (!sensor) {
                socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
                socket.destroy();
                return;
            }

            wss.handleUpgrade(req, socket, head, (ws) => {
                ws.sensorName = sensor;
                ws.sessionId = generateSessionId();
                ws.connectedAt = new Date().toISOString();
                handleSensorConnection(ws);
            });

        } else if (path === '/live') {
            wss.handleUpgrade(req, socket, head, (ws) => {
                handleWatcherConnection(ws);
            });

        } else {
            socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
            socket.destroy();
        }
    });
}

async function handleSensorConnection(ws) {
    const { sensorName, sessionId, connectedAt } = ws;
    console.log(`Sensor connected: ${sensorName} (session: ${sessionId})`);

    // Register in global registry
    connectedSensors.set(sensorName, ws);

    appendAsConnection(sensorName, 'connected', connectedAt);
    hset(`sensors:${sensorName}`, 'session', sessionId, 'connectedAt', connectedAt);

    // Crea riga in sessiondataref su PostgreSQL (nome di default = sessionId)
    try {
        await db.query('sensors',
            `INSERT INTO sessiondataref (session_id, sensor_name, name, created_at)
             VALUES ($1, $2, $3, NOW())
             ON CONFLICT (session_id) DO NOTHING`,
            [sessionId, sensorName, sessionId]
        );
        console.log(`[${sensorName}] Session ${sessionId} registrata in sessiondataref`);
    } catch (err) {
        console.error(`[${sensorName}] Errore creazione sessiondataref:`, err.message);
    }

    const pingInterval = setInterval(() => {
        if (ws.readyState === ws.OPEN) ws.ping();
    }, 30000);

    ws.on('message', (data) => {
        try {
            const packet = decode(data);

            // Messaggio di inizializzazione
            if (packet._t === 'init') {
                ws.sensorUptime = packet.uptime || null;
                console.log(`[${sensorName}] Init — uptime:`, ws.sensorUptime);
                if (ws.sensorUptime != null) {
                    hset(`sensors:${sensorName}`, 'uptime', String(ws.sensorUptime));
                }
                return;
            }

            const { ts, _m, ...fields } = packet;

            // InfluxDB: usa SEMPRE sessionId come tag (non cambia mai)
            if (_m === 'forecast_batch') {
                if (Array.isArray(fields.points)) {
                    writeForecastBatch(fields.points, sensorName, sessionId);
                }
            } else {
                const measurement = _m || 'sensor_data';
                writeGenericData(measurement, fields, sensorName, sessionId, ts);
            }

            // Broadcast ai watchers
            const watchers = sensorWatchers.get(sensorName);
            if (watchers && watchers.size > 0) {
                const msg = JSON.stringify({
                    timestamp: ts,
                    measurement: _m || 'sensor_data',
                    fields: fields
                });
                for (const watcher of watchers) {
                    if (watcher.readyState === watcher.OPEN) {
                        watcher.send(msg);
                    }
                }
            }
        } catch (err) {
            console.error(`Error processing sensor data from ${sensorName}:`, err.message);
        }
    });

    ws.on('close', async () => {
        console.log(`Sensor disconnected: ${sensorName}`);
        clearInterval(pingInterval);
        connectedSensors.delete(sensorName);
        appendAsConnection(sensorName, 'disconnected', new Date().toISOString());

        // Aggiorna disconnected_at in sessiondataref
        try {
            await db.query('sensors',
                `UPDATE sessiondataref SET disconnected_at = NOW() WHERE session_id = $1`,
                [sessionId]
            );
        } catch (err) {
            console.error(`[${sensorName}] Errore update disconnected_at:`, err.message);
        }

        del(`sensors:${sensorName}`);
    });

    ws.on('error', (err) => {
        console.error(`WebSocket error for sensor ${sensorName}:`, err.message);
    });
}

function handleWatcherConnection(ws) {
    console.log('Watcher connected, waiting for watch action...');

    ws.on('message', async (data) => {
        try {
            const msg = JSON.parse(data.toString());

            if (msg.action === 'watch' && msg.sensorId) {
                if (ws.sensorName) {
                    sensorWatchers.get(ws.sensorName)?.delete(ws);
                    if (sensorWatchers.get(ws.sensorName)?.size === 0) {
                        sensorWatchers.delete(ws.sensorName);
                    }
                }

                ws.sensorName = msg.sensorId;

                if (!sensorWatchers.has(msg.sensorId)) {
                    sensorWatchers.set(msg.sensorId, new Set());
                }
                sensorWatchers.get(msg.sensorId).add(ws);

                console.log(`Watcher now watching sensor: ${msg.sensorId}`);

            } else if (msg.action === 'unwatch') {
                if (ws.sensorName) {
                    sensorWatchers.get(ws.sensorName)?.delete(ws);
                    if (sensorWatchers.get(ws.sensorName)?.size === 0) {
                        sensorWatchers.delete(ws.sensorName);
                    }
                    ws.sensorName = null;
                }
            }
        } catch (err) {
            // Ignore non-JSON messages
        }
    });

    ws.on('close', () => {
        if (ws.sensorName) {
            sensorWatchers.get(ws.sensorName)?.delete(ws);
            if (sensorWatchers.get(ws.sensorName)?.size === 0) {
                sensorWatchers.delete(ws.sensorName);
            }
        }
        console.log('Watcher disconnected');
    });

    ws.on('error', (err) => {
        console.error('WebSocket error for watcher:', err.message);
    });
}

module.exports = { setup, connectedSensors };
