const WebSocket = require('ws');
const express = require('express');
const http = require('http');
const mysql = require('mysql2/promise');
const crypto = require('crypto');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// Database connection pool
const dbConfig = {
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'sistemkos_db',
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
};

const pool = mysql.createPool(dbConfig);

// Store active connections: Map<userType:userId, Array<{ws, connectionId, userInfo}>>
const activeConnections = new Map();

// Store typing status
const typingStatus = new Map();

// Generate unique connection ID
function generateConnectionId() {
    return crypto.randomBytes(16).toString('hex');
}

// Log with timestamp
function log(message, data = null) {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] ${message}`);
    if (data) {
        console.log(JSON.stringify(data, null, 2));
    }
}

// Broadcast to specific user (all their connections)
function broadcastToUser(userType, userId, message) {
    const userKey = `${userType}:${userId}`;
    const connections = activeConnections.get(userKey);
    
    if (connections && connections.length > 0) {
        log(`📤 Broadcasting to ${userKey}`, { 
            messageType: message.type,
            connectionsCount: connections.length 
        });
        
        connections.forEach(connection => {
            if (connection.ws.readyState === WebSocket.OPEN) {
                try {
                    connection.ws.send(JSON.stringify(message));
                    log(`✅ Sent to connection ${connection.connectionId}`);
                } catch (error) {
                    log(`❌ Error sending to ${connection.connectionId}:`, error.message);
                }
            } else {
                log(`⚠️ Connection ${connection.connectionId} not open (state: ${connection.ws.readyState})`);
            }
        });
    } else {
        log(`⚠️ No active connections found for ${userKey}`);
    }
}

// Broadcast typing status
function broadcastTypingStatus(from, to, isTyping) {
    const message = {
        type: 'typing',
        from: from,
        isTyping: isTyping,
        timestamp: new Date().toISOString()
    };
    
    broadcastToUser(to.type, to.id, message);
}

// Remove connection from active connections
function removeConnection(connectionId) {
    for (const [userKey, connections] of activeConnections.entries()) {
        const index = connections.findIndex(c => c.connectionId === connectionId);
        if (index > -1) {
            const removed = connections.splice(index, 1)[0];
            log(`🔌 Removed connection ${connectionId} from ${userKey}`);
            
            if (connections.length === 0) {
                activeConnections.delete(userKey);
                const [userType, userId] = userKey.split(':');
                notifyUserOffline(userType, parseInt(userId));
                log(`👤 User ${userKey} is now offline (no more connections)`);
            }
            
            return removed.userInfo;
        }
    }
    return null;
}

// Handle new WebSocket connection
wss.on('connection', (ws, req) => {
    const connectionId = generateConnectionId();
    log(`🔗 New connection established`, { connectionId, ip: req.socket.remoteAddress });
    
    // Heartbeat mechanism
    let isAlive = true;
    
    ws.on('pong', () => {
        isAlive = true;
    });
    
    const heartbeatInterval = setInterval(() => {
        if (!isAlive) {
            log(`💔 Heartbeat failed for ${connectionId}, terminating...`);
            return ws.terminate();
        }
        
        isAlive = false;
        ws.ping();
    }, 30000);
    
    // Handle incoming messages
    ws.on('message', async (data) => {
        try {
            const message = JSON.parse(data);
            log(`📨 Received message from ${connectionId}`, { type: message.type });
            await handleMessage(ws, connectionId, message);
        } catch (error) {
            log(`❌ Error processing message from ${connectionId}:`, error.message);
            ws.send(JSON.stringify({
                type: 'error',
                message: 'Invalid message format',
                error: error.message
            }));
        }
    });
    
    // Handle connection close
    ws.on('close', (code, reason) => {
        log(`🔌 Connection closed`, { 
            connectionId, 
            code, 
            reason: reason.toString() 
        });
        
        clearInterval(heartbeatInterval);
        removeConnection(connectionId);
    });
    
    // Handle errors
    ws.on('error', (error) => {
        log(`❌ WebSocket error for ${connectionId}:`, error.message);
    });
});

// Handle authentication
async function handleAuth(ws, connectionId, message) {
    const { userType, userId, userName, userAvatar } = message;
    
    log(`🔐 Authentication attempt`, { userType, userId, userName });
    
    if (!userType || !userId) {
        log(`❌ Auth failed: Missing user information`);
        ws.send(JSON.stringify({
            type: 'auth_error',
            message: 'Missing user information'
        }));
        return;
    }
    
    const userKey = `${userType}:${userId}`;
    
    // Add to active connections
    if (!activeConnections.has(userKey)) {
        activeConnections.set(userKey, []);
    }
    
    activeConnections.get(userKey).push({
        ws: ws,
        connectionId: connectionId,
        userInfo: { 
            type: userType, 
            id: parseInt(userId), 
            name: userName, 
            avatar: userAvatar 
        }
    });
    
    log(`✅ User authenticated successfully`, { 
        userKey, 
        totalConnections: activeConnections.get(userKey).length 
    });
    
    // Send auth success
    ws.send(JSON.stringify({
        type: 'auth_success',
        connectionId: connectionId,
        user: { 
            type: userType, 
            id: parseInt(userId), 
            name: userName 
        }
    }));
    
    // 🔥 FIX: Update database online status
    try {
        const conn = await pool.getConnection();
        try {
            await conn.execute(
                `UPDATE chat_sessions 
                SET is_online = 1, last_seen = NOW(), updated_at = NOW() 
                WHERE ${userType === 'admin' ? 'id_admin' : 'id_penghuni'} = ?`,
                [parseInt(userId)]
            );
            
            log(`✅ Database updated: User ${userKey} is now online`);
        } finally {
            conn.release();
        }
    } catch (error) {
        log(`❌ Error updating database online status:`, error.message);
    }
    
    // Notify others about online status
    await notifyUserOnline(userType, parseInt(userId), userName);
}

// 🔥 NEW: Handle get_online_status request
async function handleGetOnlineStatus(ws, message) {
    const { target } = message;
    
    if (!target || !target.type || !target.id) {
        ws.send(JSON.stringify({
            type: 'error',
            message: 'Invalid target for online status check'
        }));
        return;
    }
    
    const userKey = `${target.type}:${target.id}`;
    const isOnline = activeConnections.has(userKey) && 
                     activeConnections.get(userKey).length > 0;
    
    log(`👀 Online status check for ${userKey}: ${isOnline ? 'Online' : 'Offline'}`);
    
    // Send response
    ws.send(JSON.stringify({
        type: 'user_' + (isOnline ? 'online' : 'offline'),
        user: {
            type: target.type,
            id: target.id
        }
    }));
}

// Handle different message types
async function handleMessage(ws, connectionId, message) {
    try {
        switch (message.type) {
            case 'auth':
                await handleAuth(ws, connectionId, message);
                break;
                
            case 'message':
                await handleNewMessage(message);
                break;
                
            case 'typing':
                await handleTyping(message);
                break;
                
            case 'read':
                await handleReadReceipt(message);
                break;
                
            case 'get_online_status': // 🔥 NEW
                await handleGetOnlineStatus(ws, message);
                break;
                
            case 'ping':
                ws.send(JSON.stringify({ 
                    type: 'pong', 
                    timestamp: Date.now() 
                }));
                break;
                
            default:
                log(`⚠️ Unknown message type: ${message.type}`);
        }
    } catch (error) {
        log(`❌ Error handling message type ${message.type}:`, error.message);
        ws.send(JSON.stringify({
            type: 'error',
            message: `Error handling ${message.type}`,
            error: error.message
        }));
    }
}

// Handle new message
// Handle new message
async function handleNewMessage(message) {
    const { from, to, content, messageId, timestamp } = message;
    
    log(`💬 New message`, { 
        from: `${from.type}:${from.id}`, 
        to: `${to.type}:${to.id}`,
        contentLength: content.length 
    });
    
    let savedMessageId = messageId;
    
    // Save to database
    try {
        const conn = await pool.getConnection();
        try {
            const [result] = await conn.execute(
                `INSERT INTO chat_message 
                (id_admin, id_penghuni, message, sender, created_at, is_read_admin, is_read_penghuni) 
                VALUES (?, ?, ?, ?, ?, ?, ?)`,
                [
                    from.type === 'admin' ? from.id : to.id,
                    from.type === 'penghuni' ? from.id : to.id,
                    content,
                    from.type,
                    timestamp || new Date().toISOString(),
                    from.type === 'admin' ? 1 : 0,
                    from.type === 'penghuni' ? 1 : 0
                ]
            );
            
            savedMessageId = result.insertId;
            log(`✅ Message saved to database`, { id: savedMessageId });
            
            // Update chat session
            await conn.execute(
                `INSERT INTO chat_sessions 
                (id_admin, id_penghuni, last_message_at, is_active, updated_at) 
                VALUES (?, ?, ?, 1, NOW()) 
                ON DUPLICATE KEY UPDATE 
                last_message_at = VALUES(last_message_at), 
                updated_at = NOW()`,
                [
                    from.type === 'admin' ? from.id : to.id,
                    from.type === 'penghuni' ? from.id : to.id,
                    timestamp || new Date().toISOString()
                ]
            );
            
            log(`✅ Chat session updated`);
            
        } finally {
            conn.release();
        }
    } catch (error) {
        log(`❌ Database error:`, error.message);
    }
    
    // Prepare message to broadcast
    const messageToSend = {
        type: 'message',
        from: from,
        content: content,
        messageId: savedMessageId.toString(),
        timestamp: timestamp || new Date().toISOString(),
        isDelivered: true
    };
    
    // 🔥 FIX: HANYA kirim ke penerima, TIDAK ke pengirim
    // Frontend sudah menambahkan pesan secara optimistic
    log(`📤 Broadcasting message to recipient only`);
    broadcastToUser(to.type, to.id, messageToSend);
    
    // Send delivery confirmation to sender
    log(`📤 Sending delivery confirmation to sender`);
    broadcastToUser(from.type, from.id, {
        type: 'message_delivered',
        messageId: savedMessageId.toString(),
        timestamp: new Date().toISOString()
    });
    
    // 🔥 REMOVED: Don't echo message back to sender
    // The frontend already added it optimistically
    // This was causing the "echo" bug where your own messages
    // appeared as if they were from the other person
}

// Handle typing status
async function handleTyping(message) {
    const { from, to, isTyping } = message;
    
    log(`⌨️ Typing status`, { 
        from: `${from.type}:${from.id}`, 
        to: `${to.type}:${to.id}`,
        isTyping 
    });
    
    const typingKey = `${from.type}:${from.id}:${to.type}:${to.id}`;
    typingStatus.set(typingKey, {
        isTyping: isTyping,
        timestamp: Date.now()
    });
    
    broadcastTypingStatus(from, to, isTyping);
}

// Handle read receipts
async function handleReadReceipt(message) {
    const { reader, messageIds, chatPartner } = message;
    
    log(`👁️ Read receipt`, { 
        reader: `${reader.type}:${reader.id}`,
        messageCount: messageIds.length 
    });
    
    // Update database
    try {
        const conn = await pool.getConnection();
        try {
            const field = reader.type === 'admin' ? 'is_read_admin' : 'is_read_penghuni';
            const readAtField = reader.type === 'admin' ? 'read_at_admin' : 'read_at_penghuni';
            
            const placeholders = messageIds.map(() => '?').join(',');
            await conn.execute(
                `UPDATE chat_message 
                SET ${field} = 1, ${readAtField} = NOW() 
                WHERE id_chat IN (${placeholders})`,
                messageIds
            );
            
            log(`✅ Read receipts updated in database`);
        } finally {
            conn.release();
        }
    } catch (error) {
        log(`❌ Error updating read status:`, error.message);
    }
    
    // Notify sender
    broadcastToUser(chatPartner.type, chatPartner.id, {
        type: 'read_receipt',
        messageIds: messageIds,
        reader: reader,
        timestamp: new Date().toISOString()
    });
}

// Notify user online
async function notifyUserOnline(userType, userId, userName) {
    log(`🟢 User online notification`, { userType, userId, userName });
    
    try {
        const conn = await pool.getConnection();
        try {
            if (userType === 'admin') {
                // Notify all penghuni of this admin
                const [penghuni] = await conn.execute(
                    'SELECT id_penghuni FROM penghuni WHERE id_admin = ?',
                    [userId]
                );
                
                log(`📢 Notifying ${penghuni.length} penghuni about admin online`);
                
                penghuni.forEach(p => {
                    broadcastToUser('penghuni', p.id_penghuni, {
                        type: 'user_online',
                        user: { type: 'admin', id: userId, name: userName }
                    });
                });
                
            } else if (userType === 'penghuni') {
                // Notify admin of this penghuni
                const [result] = await conn.execute(
                    'SELECT id_admin, nama_penghuni FROM penghuni WHERE id_penghuni = ?',
                    [userId]
                );
                
                if (result.length > 0) {
                    log(`📢 Notifying admin ${result[0].id_admin} about penghuni online`);
                    
                    broadcastToUser('admin', result[0].id_admin, {
                        type: 'user_online',
                        user: { 
                            type: 'penghuni', 
                            id: userId, 
                            name: userName || result[0].nama_penghuni 
                        }
                    });
                }
            }
        } finally {
            conn.release();
        }
    } catch (error) {
        log(`❌ Error notifying user online:`, error.message);
    }
}

// Notify user offline
async function notifyUserOffline(userType, userId) {
    log(`🔴 User offline notification`, { userType, userId });
    
    // 🔥 FIX: Update database
    try {
        const conn = await pool.getConnection();
        try {
            await conn.execute(
                `UPDATE chat_sessions 
                SET is_online = 0, last_seen = NOW(), updated_at = NOW() 
                WHERE ${userType === 'admin' ? 'id_admin' : 'id_penghuni'} = ?`,
                [userId]
            );
            
            log(`✅ Database updated: User ${userType}:${userId} is now offline`);
            
            if (userType === 'admin') {
                // Notify all penghuni of this admin
                const [penghuni] = await conn.execute(
                    'SELECT id_penghuni FROM penghuni WHERE id_admin = ?',
                    [userId]
                );
                
                log(`📢 Notifying ${penghuni.length} penghuni about admin offline`);
                
                penghuni.forEach(p => {
                    broadcastToUser('penghuni', p.id_penghuni, {
                        type: 'user_offline',
                        user: { type: 'admin', id: userId }
                    });
                });
                
            } else if (userType === 'penghuni') {
                // Notify admin of this penghuni
                const [result] = await conn.execute(
                    'SELECT id_admin FROM penghuni WHERE id_penghuni = ?',
                    [userId]
                );
                
                if (result.length > 0) {
                    log(`📢 Notifying admin ${result[0].id_admin} about penghuni offline`);
                    
                    broadcastToUser('admin', result[0].id_admin, {
                        type: 'user_offline',
                        user: { type: 'penghuni', id: userId }
                    });
                }
            }
        } finally {
            conn.release();
        }
    } catch (error) {
        log(`❌ Error notifying user offline:`, error.message);
    }
}

// Clean up old typing status
setInterval(() => {
    const now = Date.now();
    for (const [key, status] of typingStatus.entries()) {
        if (now - status.timestamp > 3000) {
            const [fromType, fromId, toType, toId] = key.split(':');
            if (status.isTyping) {
                broadcastTypingStatus(
                    { type: fromType, id: parseInt(fromId) },
                    { type: toType, id: parseInt(toId) },
                    false
                );
            }
            typingStatus.delete(key);
        }
    }
}, 1000);

// Status endpoint
app.get('/status', (req, res) => {
    const connections = Array.from(activeConnections.entries()).map(([key, conns]) => ({
        user: key,
        connections: conns.length
    }));
    
    res.json({
        status: 'online',
        activeUsers: activeConnections.size,
        connections: connections,
        totalConnections: Array.from(activeConnections.values())
            .reduce((sum, conns) => sum + conns.length, 0)
    });
});

// Start server
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
    log(`🚀 WebSocket server running on port ${PORT}`);
    log(`📊 Status endpoint: http://localhost:${PORT}/status`);
});

// Graceful shutdown
process.on('SIGTERM', () => {
    log('🛑 SIGTERM received, closing server...');
    server.close(() => {
        log('✅ Server closed');
        process.exit(0);
    });
});

module.exports = { wss, server };