const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const { Client } = require('ssh2');
const bodyParser = require('body-parser');
const { v4: uuidv4 } = require('uuid');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

app.use(express.static('public'));
app.use(bodyParser.json());

// -------- Privacy-safe broadcast store --------
const publicNotices = [];
const sessionPublicCodes = new Map();

function getPublicCode(sessionId){
  if(!sessionPublicCodes.has(sessionId)){
    const n=(Math.floor(Math.random()*10000)).toString().padStart(4,'0');
    sessionPublicCodes.set(sessionId,n);
  }
  return sessionPublicCodes.get(sessionId);
}

function fmtTs(ts){
  const d=new Date(ts), p=x=>String(x).padStart(2,'0');
  return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}

// -------- Progress helpers --------
function stageStart(sessionId, id, title){
  io.to(sessionId).emit('progress', { id, title, percent: 0, state: 'running' });
}

function stageSet(sessionId, id, percent){
  io.to(sessionId).emit('progress', { id, percent, state: 'running' });
}

function stageDone(sessionId, id){
  io.to(sessionId).emit('progress', { id, percent: 100, state: 'done' });
}

function stageFail(sessionId, id, msg){
  io.to(sessionId).emit('progress', { id, state: 'failed', error: msg || 'Failed' });
}

function statusSet(sessionId, status, activity){
  io.to(sessionId).emit('status', { status, activity });
}

function logOutput(sessionId, data, type = 'stdout') {
  io.to(sessionId).emit('log', { data: data.toString(), type });
}

// -------- SSH helpers --------
function sshConnect(opts){
  return new Promise((resolve, reject)=>{
    const conn = new Client();
    conn.on('ready', ()=>resolve(conn))
        .on('error', reject)
        .connect({
          host: opts.vps_ip,
          port: opts.vps_port || 22,
          username: opts.vps_user,
          password: opts.vps_pass,
          tryKeyboard: true,
          readyTimeout: 20000
        });
    conn.on('keyboard-interactive', (_name,_inst,_lang,prompts,finish)=>{
      finish(prompts.map(()=>opts.vps_pass));
    });
  });
}

// Execute command with proper sudo handling
function executeCommand(conn, sessionId, command, logPrefix = '') {
  return new Promise((resolve, reject) => {
    logOutput(sessionId, `${logPrefix}Running: ${command}\n`, 'info');
    
    conn.exec(command, (err, stream) => {
      if (err) {
        logOutput(sessionId, `${logPrefix}Error: ${err.message}\n`, 'stderr');
        reject(err);
        return;
      }

      let stdout = '';
      let stderr = '';

      stream.on('data', (data) => {
        stdout += data.toString();
        logOutput(sessionId, `${logPrefix}${data.toString()}`, 'stdout');
      });

      stream.stderr.on('data', (data) => {
        stderr += data.toString();
        logOutput(sessionId, `${logPrefix}${data.toString()}`, 'stderr');
      });

      stream.on('close', (code, signal) => {
        if (code === 0) {
          logOutput(sessionId, `${logPrefix}Command completed successfully\n`, 'success');
          resolve({ stdout, stderr, code });
        } else {
          logOutput(sessionId, `${logPrefix}Command failed with code ${code}\n`, 'stderr');
          reject(new Error(`Command failed with code ${code}: ${stderr}`));
        }
      });

      stream.on('error', (err) => {
        logOutput(sessionId, `${logPrefix}Stream error: ${err.message}\n`, 'stderr');
        reject(err);
      });
    });
  });
}

// Run commands in interactive shell with proper sudo handling
function runCommandsInShell(sessionId, conn, commands, password) {
  return new Promise((resolve, reject) => {
    conn.shell((err, stream) => {
      if (err) {
        reject(err);
        return;
      }

      let output = '';
      let commandIndex = 0;
      let expectingPassword = false;

      stream.on('data', (data) => {
        const dataStr = data.toString();
        output += dataStr;
        logOutput(sessionId, dataStr, 'stdout');

        // Check for password prompts
        if ((dataStr.toLowerCase().includes('password') || dataStr.includes('[sudo]')) && !expectingPassword) {
          expectingPassword = true;
          setTimeout(() => {
            stream.write(password + '\n');
            expectingPassword = false;
          }, 1000);
          return;
        }

        // Check for command completion (prompt appears)
        if ((dataStr.includes('$ ') || dataStr.includes('# ') || dataStr.includes('> ') || 
             dataStr.includes(commands[commandIndex-1])) && !expectingPassword) {
          if (commandIndex < commands.length) {
            setTimeout(() => {
              logOutput(sessionId, `\n🚀 Running: ${commands[commandIndex]}\n`, 'info');
              stream.write(commands[commandIndex] + '\n');
              commandIndex++;
            }, 2000);
          } else {
            // All commands completed
            setTimeout(() => {
              stream.write('exit\n');
              setTimeout(() => {
                stream.end();
                resolve(output);
              }, 2000);
            }, 3000);
          }
        }
      });

      stream.stderr.on('data', (data) => {
        logOutput(sessionId, data.toString(), 'stderr');
      });

      stream.on('close', () => {
        resolve(output);
      });

      stream.on('error', (err) => {
        reject(err);
      });

      // Start with first command
      setTimeout(() => {
        logOutput(sessionId, `\n🚀 Running: ${commands[0]}\n`, 'info');
        stream.write(commands[0] + '\n');
        commandIndex++;
      }, 2000);
    });
  });
}

// Run installer with interactive prompts
function runInstallerInteractive(sessionId, conn, password, proxyUser, proxyPass) {
  return new Promise((resolve, reject) => {
    conn.shell((err, stream) => {
      if (err) {
        reject(err);
        return;
      }

      let output = '';
      let step = 0;
      let inSudo = false;
      let inInstaller = false;
      let usernameSent = false;
      let passwordSent = false;
      let confirmSent = false;

      stream.on('data', (data) => {
        const dataStr = data.toString();
        output += dataStr;
        logOutput(sessionId, dataStr, 'stdout');

        // Handle sudo password prompt
        if ((dataStr.toLowerCase().includes('password') || dataStr.includes('[sudo]')) && !inSudo && !inInstaller) {
          setTimeout(() => {
            stream.write(password + '\n');
            inSudo = true;
          }, 1000);
          return;
        }

        // After sudo success, run cd
        if ((dataStr.includes('# ') || dataStr.includes('root@')) && inSudo && step === 0) {
          setTimeout(() => {
            logOutput(sessionId, '\n🚀 Running: cd\n', 'info');
            stream.write('cd\n');
            step = 1;
          }, 1000);
          return;
        }

        // After cd, run installer
        if ((dataStr.includes('# ') || dataStr.includes('root@')) && step === 1) {
          setTimeout(() => {
            logOutput(sessionId, '\n🚀 Running: curl installer\n', 'info');
            stream.write('curl -sL 157.20.240.21/panel-9050/echoproxy | bash\n');
            step = 2;
            inInstaller = true;
          }, 1000);
          return;
        }

        // Handle installer username prompt
        if (inInstaller && !usernameSent && 
            (dataStr.toLowerCase().includes('username') || 
             dataStr.toLowerCase().includes('user'))) {
          setTimeout(() => {
            logOutput(sessionId, `\n📝 Providing username: ${proxyUser}\n`, 'info');
            stream.write(proxyUser + '\n');
            usernameSent = true;
          }, 1500);
          return;
        }

        // Handle installer password prompt (first time)
        if (inInstaller && usernameSent && !passwordSent && 
            dataStr.toLowerCase().includes('password')) {
          setTimeout(() => {
            logOutput(sessionId, `\n🔐 Providing password\n`, 'info');
            stream.write(proxyPass + '\n');
            passwordSent = true;
          }, 1500);
          return;
        }

        // Handle password confirmation
        if (inInstaller && passwordSent && !confirmSent && 
            (dataStr.toLowerCase().includes('password') || 
             dataStr.toLowerCase().includes('confirm') ||
             dataStr.toLowerCase().includes('retype') ||
             dataStr.toLowerCase().includes('again'))) {
          setTimeout(() => {
            logOutput(sessionId, `\n🔐 Confirming password\n`, 'info');
            stream.write(proxyPass + '\n');
            confirmSent = true;
            
            // Wait for installation to complete
            setTimeout(() => {
              logOutput(sessionId, '\n✅ Installation completed\n', 'success');
              stream.write('exit\n');
              setTimeout(() => {
                stream.end();
                resolve(output);
              }, 2000);
            }, 8000);
          }, 1500);
          return;
        }

        // Start the process with sudo su
        if ((dataStr.includes('$ ') || dataStr.includes('# ') || dataStr.includes('> ')) && step === 0) {
          setTimeout(() => {
            logOutput(sessionId, '\n🚀 Running: sudo su\n', 'info');
            stream.write('sudo su\n');
          }, 1000);
        }
      });

      stream.stderr.on('data', (data) => {
        logOutput(sessionId, data.toString(), 'stderr');
      });

      stream.on('close', () => {
        resolve(output);
      });

      stream.on('error', (err) => {
        reject(err);
      });
    });
  });
}

async function stepUpdateAndReboot(sessionId, creds){
  const id = 'step1';
  stageStart(sessionId, id, 'Step 1 — System Update & Upgrade');
  statusSet(sessionId, 'Running', 'Step 1: Running system updates');

  const conn = await sshConnect(creds);
  
  try {
    logOutput(sessionId, '🔧 Starting system update process...\n', 'info');
    
    // Commands for step 1
    const commands = [
      'sudo su',
      'cd',
      'apt update -y',
      'apt upgrade -y', 
      'apt autoremove -y',
      'reboot now'
    ];

    await runCommandsInShell(sessionId, conn, commands, creds.vps_pass);
    
    logOutput(sessionId, '✅ Step 1 completed - System reboot initiated\n', 'success');
    stageSet(sessionId, id, 100);
    stageDone(sessionId, id);
    
  } catch (error) {
    logOutput(sessionId, `❌ Step 1 failed: ${error.message}\n`, 'error');
    stageFail(sessionId, id, error.message);
    throw error;
  } finally {
    try { 
      // Give some time for reboot command to be sent
      setTimeout(() => {
        try { conn.end(); } catch (e) {}
      }, 3000);
    } catch (e) {}
  }
}

async function waitRebootAndReconnect(sessionId, creds){
  const id = 'reconnect';
  stageStart(sessionId, id, 'Waiting for Reboot');
  statusSet(sessionId, 'Rebooting', 'Waiting for server to reboot');

  let connected = false;
  for (let attempt = 1; attempt <= 30; attempt++) {
    stageSet(sessionId, id, Math.min(90, attempt * 3));
    logOutput(sessionId, `⏳ Reconnect attempt ${attempt}/30...\n`, 'info');
    
    try {
      const testConn = await sshConnect(creds);
      // Test with a simple command
      await executeCommand(testConn, sessionId, 'echo "System is ready"', '[test] ');
      testConn.end();
      connected = true;
      logOutput(sessionId, '✅ Server is back online and responsive!\n', 'success');
      break;
    } catch (error) {
      logOutput(sessionId, `Server not ready yet, waiting 10 seconds...\n`, 'info');
      await new Promise(resolve => setTimeout(resolve, 10000));
    }
  }

  if (!connected) {
    stageFail(sessionId, id, 'Could not reconnect after reboot');
    throw new Error('Could not reconnect after reboot');
  }

  stageDone(sessionId, id);
}

async function stepInstaller(sessionId, creds, proxyUser, proxyPass){
  const id = 'step2';
  stageStart(sessionId, id, 'Step 2 — Install Proxy Service');
  statusSet(sessionId, 'Running', 'Step 2: Installing proxy service');

  const conn = await sshConnect(creds);
  
  try {
    logOutput(sessionId, '🔧 Starting proxy installation process...\n', 'info');
    
    // Run the interactive installer
    stageSet(sessionId, id, 30);
    await runInstallerInteractive(sessionId, conn, creds.vps_pass, proxyUser, proxyPass);

    logOutput(sessionId, '✅ Proxy installation completed\n', 'success');
    stageSet(sessionId, id, 80);

    // Test if proxy port is open
    logOutput(sessionId, '🔍 Testing proxy service...\n', 'info');
    stageSet(sessionId, id, 90);
    
    try {
      // Test if proxy port is listening
      await executeCommand(conn, sessionId, 'netstat -tln | grep :5001 || ss -tln | grep :5001', '[port test] ');
      logOutput(sessionId, '✅ Proxy port 5001 is listening\n', 'success');
    } catch (error) {
      logOutput(sessionId, '⚠️ Proxy port not found, checking process...\n', 'warning');
      try {
        await executeCommand(conn, sessionId, 'ps aux | grep echoproxy', '[process test] ');
        logOutput(sessionId, '✅ Proxy process is running\n', 'success');
      } catch (e) {
        logOutput(sessionId, '⚠️ Cannot verify proxy directly, but installation completed\n', 'warning');
      }
    }

    stageSet(sessionId, id, 100);
    stageDone(sessionId, id);
    statusSet(sessionId, 'Completed', 'Proxy installation finished');

    // Final result
    const proxy = `${creds.vps_ip}:5001:${proxyUser}:${proxyPass}`;
    logOutput(sessionId, `🎉 Proxy setup completed!\n📍 Your proxy: ${proxy}\n`, 'success');
    io.to(sessionId).emit('result', { ok: true, proxy });

  } catch (error) {
    logOutput(sessionId, `❌ Step 2 failed: ${error.message}\n`, 'error');
    stageFail(sessionId, id, error.message);
    throw error;
  } finally {
    try { conn.end(); } catch (e) {}
  }
}

// -------- API --------
app.post('/api/build', (req, res)=>{
  const d = req.body || {};
  const sessionId = d.session_id || uuidv4();
  const broadcast = d.broadcast === true;

  for(const k of ['vps_ip','vps_user','vps_pass','proxy_user','proxy_pass']){
    if(!d[k]) return res.status(400).json({ ok:false, error:`missing ${k}` });
  }

  // Reset everything
  statusSet(sessionId, 'Started', 'Queued');
  io.to(sessionId).emit('timeline_reset', true);
  io.to(sessionId).emit('log_clear', true);

  (async ()=>{
    try{
      // Step 1
      await stepUpdateAndReboot(sessionId, {
        vps_ip: d.vps_ip, vps_port: d.vps_port || 22,
        vps_user: d.vps_user, vps_pass: d.vps_pass
      });

      // Reconnect
      await waitRebootAndReconnect(sessionId, {
        vps_ip: d.vps_ip, vps_port: d.vps_port || 22,
        vps_user: d.vps_user, vps_pass: d.vps_pass
      });

      // Step 2
      await stepInstaller(sessionId, {
        vps_ip: d.vps_ip, vps_port: d.vps_port || 22,
        vps_user: d.vps_user, vps_pass: d.vps_pass
      }, d.proxy_user, d.proxy_pass);

      // Broadcast privacy-safe notice
      if(broadcast){
        const code = getPublicCode(sessionId);
        const note = `user:${code} proxy made done at ${fmtTs(Date.now())}`;
        publicNotices.unshift({ id: uuidv4(), note, time: Date.now() });
        while(publicNotices.length>100) publicNotices.pop();
        io.emit('public_notice', { note, time: Date.now() });
      }
    }catch(e){
      statusSet(sessionId, 'Failed', e.message || 'Failed');
      logOutput(sessionId, `❌ Build failed: ${e.message}\n`, 'error');
      io.to(sessionId).emit('result', { ok:false, error: e.message || 'Unexpected error' });
    }
  })();

  res.json({ ok:true, session_id: sessionId, msg:'Build started' });
});

app.get('/api/public', (req,res)=> res.json({ ok:true, notices: publicNotices }));

io.on('connection', (socket)=>{
  socket.on('join', ({session_id})=>{
    if(!session_id) return;
    socket.join(session_id);
    socket.emit('joined', { ok:true, session_id, user_code: getPublicCode(session_id) });
    socket.emit('public_list', { notices: publicNotices });
    statusSet(session_id, 'Waiting', 'Idle');
  });
});

const PORT = process.env.PORT || 5000;
server.listen(PORT, ()=>console.log(`Server running on port ${PORT}`));