const WebSocket = require('ws');
const pty = require('node-pty');
const express = require('express');
const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// Read MySQL password from /root/.my.cnf (same as PHP config does)
const { execSync } = require('child_process');
let mysqlPass = '';
try {
    // Execute the same command as PHP: cat /root/.my.cnf |grep password |cut -f2- -d=
    mysqlPass = execSync('cat /root/.my.cnf | grep password | cut -f2- -d=', { encoding: 'utf8' }).trim();
} catch (error) {
    console.error('Failed to read MySQL password from /root/.my.cnf:', error.message);
    process.exit(1);
}

// Configuration
const CONFIG = {
    port: 8090,
    mysqlHost: '127.0.0.1', 
    mysqlUser: 'root',
    mysqlPassword: mysqlPass,
    mysqlDatabase: 'whp',
    sessionPath: '/var/lib/php/sessions', // PHP session directory
    authTokenHeader: 'X-Auth-Token'
};

// Create MySQL connection pool
const dbPool = mysql.createPool({
    host: CONFIG.mysqlHost,
    user: CONFIG.mysqlUser,
    password: CONFIG.mysqlPassword,
    database: CONFIG.mysqlDatabase,
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

// WebSocket server
const wss = new WebSocket.Server({ port: CONFIG.port });

console.log(`WHP Terminal Server running on port ${CONFIG.port}`);

// Active terminal sessions
const terminals = new Map();

// Verify user has access to container
async function verifyContainerAccess(username, containerId) {
    try {
        const isRoot = username === 'root';
        
        let query;
        let params;
        
        if (isRoot) {
            query = `
                SELECT s.username, s.site_name, sc.container_id
                FROM site_containers sc
                JOIN sites s ON sc.site_id = s.id
                WHERE sc.container_id = ? AND sc.status = 'running'
            `;
            params = [containerId];
        } else {
            query = `
                SELECT s.username, s.site_name, sc.container_id
                FROM site_containers sc
                JOIN sites s ON sc.site_id = s.id
                WHERE sc.container_id = ? AND s.username = ? AND sc.status = 'running'
            `;
            params = [containerId, username];
        }
        
        const [rows] = await dbPool.execute(query, params);
        return rows.length > 0 ? rows[0] : null;
    } catch (error) {
        console.error('Database error:', error);
        return null;
    }
}

// Parse PHP session data
function parsePhpSession(sessionData) {
    // Basic PHP session parser - handles common cases
    const session = {};
    const parts = sessionData.split('|');
    
    for (let i = 0; i < parts.length; i++) {
        const part = parts[i];
        const colonIndex = part.lastIndexOf(':');
        if (colonIndex > 0) {
            const key = part.substring(0, colonIndex);
            const value = part.substring(colonIndex + 1);
            
            // Handle serialized strings
            if (value.startsWith('s:')) {
                const match = value.match(/s:\d+:"([^"]*)"/);
                if (match) {
                    session[key] = match[1];
                }
            }
        }
    }
    
    return session;
}

// Authenticate WebSocket connection
async function authenticate(token) {
    try {
        // First try to validate as a session token from our API
        const [rows] = await dbPool.execute(
            'SELECT username FROM api_tokens WHERE token = ? AND expires_at > NOW()',
            [token]
        );
        
        if (rows.length > 0) {
            return { username: rows[0].username, authenticated: true };
        }
        
        // Try PHP session file as fallback
        const sessionFile = path.join(CONFIG.sessionPath, `sess_${token}`);
        if (fs.existsSync(sessionFile)) {
            const sessionData = fs.readFileSync(sessionFile, 'utf8');
            const session = parsePhpSession(sessionData);
            
            if (session.username) {
                return { username: session.username, authenticated: true };
            }
        }
        
        return { authenticated: false };
    } catch (error) {
        console.error('Authentication error:', error);
        return { authenticated: false };
    }
}

// Log terminal access
async function logTerminalAccess(username, containerId, containerUser, shell) {
    try {
        await dbPool.execute(
            `INSERT INTO terminal_access_log 
             (username, container_id, container_user, shell) 
             VALUES (?, ?, ?, ?)`,
            [username, containerId, containerUser, shell]
        );
    } catch (error) {
        console.error('Failed to log terminal access:', error);
    }
}

// Update command count
async function updateCommandCount(username, containerId) {
    try {
        await dbPool.execute(
            `UPDATE terminal_access_log 
             SET commands_executed = commands_executed + 1 
             WHERE username = ? AND container_id = ? 
             AND disconnected_at IS NULL
             ORDER BY connected_at DESC LIMIT 1`,
            [username, containerId]
        );
    } catch (error) {
        console.error('Failed to update command count:', error);
    }
}

// Handle WebSocket connections
wss.on('connection', async (ws, req) => {
    console.log('New WebSocket connection');
    
    let term = null;
    let authenticated = false;
    let username = null;
    let containerId = null;
    
    // Authentication timeout
    const authTimeout = setTimeout(() => {
        if (!authenticated) {
            ws.send(JSON.stringify({ type: 'error', message: 'Authentication timeout' }));
            ws.close();
        }
    }, 5000);
    
    ws.on('message', async (data) => {
        try {
            const message = JSON.parse(data);
            
            switch (message.type) {
                case 'auth':
                    const authResult = await authenticate(message.token);
                    if (authResult.authenticated) {
                        authenticated = true;
                        username = authResult.username;
                        clearTimeout(authTimeout);
                        ws.send(JSON.stringify({ type: 'authenticated', username }));
                    } else {
                        ws.send(JSON.stringify({ type: 'error', message: 'Authentication failed' }));
                        ws.close();
                    }
                    break;
                    
                case 'connect':
                    if (!authenticated) {
                        ws.send(JSON.stringify({ type: 'error', message: 'Not authenticated' }));
                        return;
                    }
                    
                    containerId = message.containerId;
                    const shell = message.shell || 'bash';
                    
                    // Verify access
                    const containerInfo = await verifyContainerAccess(username, containerId);
                    if (!containerInfo) {
                        ws.send(JSON.stringify({ type: 'error', message: 'Access denied' }));
                        return;
                    }
                    
                    // Default to container owner if no user specified
                    const containerUser = message.user || containerInfo.username;
                    
                    // Create PTY process
                    const args = ['exec', '-it'];
                    if (containerUser !== 'root') {
                        args.push('--user', containerUser);
                    }
                    args.push(containerId, shell);
                    
                    term = pty.spawn('docker', args, {
                        name: 'xterm-256color',
                        cols: message.cols || 80,
                        rows: message.rows || 24,
                        cwd: process.env.HOME,
                        env: process.env
                    });
                    
                    // Store terminal reference
                    terminals.set(ws, term);
                    
                    // Log access
                    await logTerminalAccess(username, containerId, containerUser, shell);
                    
                    console.log(`Terminal created for ${username} on container ${containerId}`);
                    
                    // Handle PTY data
                    term.onData((data) => {
                        ws.send(JSON.stringify({ type: 'data', data }));
                    });
                    
                    term.onExit((code) => {
                        console.log(`Terminal exited with code ${code}`);
                        ws.send(JSON.stringify({ type: 'exit', code }));
                        terminals.delete(ws);
                    });
                    
                    ws.send(JSON.stringify({ type: 'connected' }));
                    break;
                    
                case 'data':
                    if (term) {
                        term.write(message.data);
                        // Count Enter key presses as commands
                        if (message.data.includes('\r') || message.data.includes('\n')) {
                            updateCommandCount(username, containerId);
                        }
                    }
                    break;
                    
                case 'resize':
                    if (term && message.cols && message.rows) {
                        term.resize(message.cols, message.rows);
                    }
                    break;
            }
        } catch (error) {
            console.error('WebSocket message error:', error);
            ws.send(JSON.stringify({ type: 'error', message: 'Internal error' }));
        }
    });
    
    ws.on('close', async () => {
        console.log('WebSocket connection closed');
        clearTimeout(authTimeout);
        
        // Clean up terminal
        if (terminals.has(ws)) {
            const term = terminals.get(ws);
            term.kill();
            terminals.delete(ws);
            
            // Update disconnect time
            if (username && containerId) {
                try {
                    await dbPool.execute(
                        `UPDATE terminal_access_log 
                         SET disconnected_at = NOW() 
                         WHERE username = ? AND container_id = ? 
                         AND disconnected_at IS NULL
                         ORDER BY connected_at DESC LIMIT 1`,
                        [username, containerId]
                    );
                } catch (error) {
                    console.error('Failed to update disconnect time:', error);
                }
            }
        }
    });
    
    ws.on('error', (error) => {
        console.error('WebSocket error:', error);
    });
});

// Graceful shutdown
process.on('SIGTERM', () => {
    console.log('SIGTERM received, closing server...');
    wss.close(() => {
        dbPool.end();
        process.exit(0);
    });
});