#!/usr/bin/env node
const WebSocket = require('ws');
const http = require('http');
const { URL } = require('url');

const args = process.argv.slice(2);
function getArg(name, def=null){
  const idx = args.indexOf(name);
  if (idx!==-1 && args[idx+1]) return args[idx+1];
  const pref = args.find(a=>a.startsWith(name+'='));
  if (pref) return pref.split('=')[1];
  return def;
}
const config = {
  apiKey: getArg('--api-key') || process.env.BHIM_API_KEY || 'bhim_live_sk_tunnel_2024_static_key',
  subdomain: (getArg('--subdomain')||process.env.BHIM_SUBDOMAIN||'myapp').toLowerCase(),
  localPort: parseInt(getArg('--port')||getArg('--local-port')||process.env.BHIM_PORT||'3000'),
  localHost: getArg('--host')||'127.0.0.1',
  serverHost: getArg('--server')||process.env.BHIM_SERVER||'tunnel.bhim.si',
  wsPort: String(getArg('--ws-port')||process.env.BHIM_WS_PORT||'443'),
  wsPath: getArg('--ws-path')||process.env.BHIM_WS_PATH||'/tunnel',
  protocol: getArg('--protocol')|| 'wss',
  baseDomain: getArg('--base-domain')|| 'tunnel.bhim.si',
  customDomain: getArg('--custom-domain')||null,
};
function buildWsUrl(hostOverride){
  const host = hostOverride || config.serverHost;
  const isStandard = (config.protocol==='wss' && config.wsPort==='443') || (config.protocol==='ws' && config.wsPort==='80');
  const portPart = isStandard ? '' : `:${config.wsPort}`;
  const pathPart = config.wsPath.startsWith('/')? config.wsPath : `/${config.wsPath}`;
  return `${config.protocol}://${host}${portPart}${pathPart}?apiKey=${encodeURIComponent(config.apiKey)}&subdomain=${encodeURIComponent(config.subdomain)}&port=${config.localPort}${config.customDomain?`&customDomain=${encodeURIComponent(config.customDomain)}`:''}`;
}
let WS_URL = buildWsUrl();
console.log(`\n🚇 Bhim Tunnel Client v1.3.0\nSubdomain: ${config.subdomain}\nLocal: http://${config.localHost}:${config.localPort}\nServer: ${WS_URL}\nPublic: https://${config.subdomain}.tunnel.bhim.si\nPath: https://${config.baseDomain}/t/${config.subdomain}/\n`);

let ws, reconnectAttempts=0; const MAX=200; let ipFallback=false;
function connect(url, hostHeader){
  console.log(`[Tunnel] Connecting ${url} Host:${hostHeader||'auto'}`);
  const opts = { handshakeTimeout:10000, rejectUnauthorized:false };
  if(hostHeader) opts.headers={ Host: hostHeader };
  ws=new WebSocket(url, opts);
  ws.on('open', ()=>{
    reconnectAttempts=0; ipFallback=false;
    console.log(`[Tunnel] ✅ Connected https://${config.subdomain}.tunnel.bhim.si -> localhost:${config.localPort}`);
    setInterval(()=>{ if(ws.readyState===WebSocket.OPEN) try{ws.send(JSON.stringify({type:'ping'}))}catch{} },25000);
  });
  ws.on('message', (data)=>{
    let msg; try{msg=JSON.parse(data.toString())}catch{return}
    if(msg.type==='connected') console.log(`[Server] ${msg.pathUrl || msg.url}`);
    else if(msg.type==='error') console.error(`[Server Error] ${msg.message}`);
    else if(msg.type==='http_request'){
      const {requestId, method, url: reqUrl, headers, body} = msg;
      const localUrl=`http://${config.localHost}:${config.localPort}${reqUrl}`;
      const bodyBuf = body? Buffer.from(body,'base64'): null;
      const parsed=new URL(localUrl);
      const opts={ hostname: parsed.hostname, port: parsed.port, path: parsed.pathname+parsed.search, method, headers:{...headers, host:`${config.localHost}:${config.localPort}`, 'x-forwarded-host':headers.host, 'x-bhim-tunnel':'true'}, timeout:25000 };
      delete opts.headers['connection']; delete opts.headers['content-length'];
      const proxyReq=http.request(opts, (proxyRes)=>{
        const chunks=[]; proxyRes.on('data',d=>chunks.push(d)); proxyRes.on('end',()=>{
          const respBody=Buffer.concat(chunks);
          ws.readyState===WebSocket.OPEN && ws.send(JSON.stringify({ type:'http_response', requestId, statusCode:proxyRes.statusCode, headers:proxyRes.headers, body:respBody.toString('base64') }));
        });
      });
      proxyReq.on('error', err=>{
        ws.readyState===WebSocket.OPEN && ws.send(JSON.stringify({ type:'http_response', requestId, error:`Local unreachable ${config.localHost}:${config.localPort} ${err.message}` }));
      });
      if(bodyBuf&&bodyBuf.length) proxyReq.write(bodyBuf);
      proxyReq.end();
    }
  });
  ws.on('close', (code)=>{
    console.log(`[Tunnel] Closed ${code}`);
    if(reconnectAttempts<MAX){
      const delay=Math.min(1000*Math.pow(1.5,reconnectAttempts),15000); reconnectAttempts++;
      if(code===1006 && !ipFallback){
        ipFallback=true; const ipUrl=buildWsUrl('88.222.241.153'); console.log('[Tunnel] IP fallback...');
        setTimeout(()=>connect(ipUrl, config.baseDomain),1000); return;
      }
      setTimeout(()=>connect(WS_URL,null),delay);
    } else { process.exit(1); }
  });
  ws.on('error', err=>{
    console.error(`[Tunnel] WS error ${err.message}`);
    if(err.message.includes('ENOTFOUND') && !ipFallback){
      ipFallback=true; const ipUrl=buildWsUrl('88.222.241.153');
      setTimeout(()=>connect(ipUrl, config.baseDomain),1000);
    }
  });
}
connect(WS_URL,null);
process.on('SIGINT', ()=>{ ws&&ws.close(); process.exit(0); });
process.on('SIGTERM', ()=>{ ws&&ws.close(); process.exit(0); });
