#!/usr/bin/env node
/* ═══════════════════════════════════════
   光湖驱动引擎 v2.1 · Guanghu Drive Engine
   HLDP万能语言接口 + 集群串联 — 一条指令，全网执行
   ═══════════════════════════════════════ */

const http=require('http'),fs=require('fs'),path=require('path'),crypto=require('crypto'),{exec}=require('child_process'),os=require('os');

const PORT=parseInt(process.env.ENGINE_PORT||process.env.GATEKEEPER_PORT||process.argv[2]||'3910',10);
const DATA_DIR=path.join(os.homedir(),'.gatekeeper');
const CMD_TIMEOUT=30000,MAX_OUTPUT=100000,RATE_LIMIT=60;

if(!fs.existsSync(DATA_DIR))fs.mkdirSync(DATA_DIR,{recursive:true,mode:0o700});

/* ═══ 密钥 — 兼容所有历史数据目录 ═══ */
const SECRET_PATHS=[
  path.join(os.homedir(),'.gatekeeper','secret'),
  path.join(os.homedir(),'.gk','secret'),
  path.join(os.homedir(),'.guanghu-engine','secret'),
];
let API_SECRET;
for(const sp of SECRET_PATHS){
  if(fs.existsSync(sp)){const k=fs.readFileSync(sp,'utf-8').trim();if(k){API_SECRET=k;break;}}
}
if(!API_SECRET){
  API_SECRET='zy_gtw_'+crypto.randomBytes(24).toString('hex');
  const dir=path.dirname(SECRET_PATHS[0]);
  if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true,mode:0o700});
  fs.writeFileSync(SECRET_PATHS[0],API_SECRET,{mode:0o600});
  console.log('');
  console.log('══════════════════════════════════════════════');
  console.log('  🔐 光湖驱动引擎 · 首次启动');
  console.log('');
  console.log('  ╭─ API 密钥（请复制保存）');
  console.log('  ├─ '+API_SECRET);
  console.log('  ╰─ 此密钥仅在此处显示一次');
  console.log('');
  console.log('  已保存至: '+SECRET_PATHS[0]);
  console.log('  监听端口: '+PORT);
  console.log('══════════════════════════════════════════════');
  console.log('');
}

function log(level,action,detail){
  const ts=new Date().toISOString();
  const line='['+ts+'] ['+level+'] '+action+(detail?' | '+detail:'');
  console.log(line);
  try{const lf=path.join(DATA_DIR,'engine.log');fs.appendFileSync(lf,line+'\n');}catch(e){}
}

const RC={};
function checkRate(ip){
  const n=Date.now(),w=60000;
  if(!RC[ip]){RC[ip]={c:1,r:n+w};return true;}
  if(n>RC[ip].r){RC[ip]={c:1,r:n+w};return true;}
  RC[ip].c++;return RC[ip].c<=RATE_LIMIT;
}

function auth(headers){
  const t=(headers['authorization']||headers['Authorization']||'').replace(/^Bearer\s+/i,'').trim();
  if(!t)return{ok:false,reason:'缺少 Authorization header'};
  if(t.length!==API_SECRET.length)return{ok:false,reason:'密钥无效'};
  for(let i=0;i<t.length;i++){if(t.charCodeAt(i)!==API_SECRET.charCodeAt(i))return{ok:false,reason:'密钥无效'};}
  return{ok:true};
}

function parseBody(req){
  return new Promise(r=>{let b='';req.on('data',c=>b+=c);req.on('end',()=>{try{r(JSON.parse(b))}catch(e){r(null)}});req.on('error',()=>r(null));});
}

function jr(res,status,data){
  res.writeHead(status,{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'});
  res.end(JSON.stringify(data));
}

async function execCmd(cmd,timeout){
  return new Promise(r=>{exec(cmd,{timeout:timeout||CMD_TIMEOUT,maxBuffer:MAX_OUTPUT,shell:'/bin/bash'},(e,o,se)=>{r({stdout:(o||'').slice(0,MAX_OUTPUT),stderr:(se||'').slice(0,MAX_OUTPUT),code:e?(e.code||e.signal||1):0,err:e?e.message:null});});});
}

function fmtUptime(s){const d=Math.floor(s/86400),h=Math.floor((s%86400)/3600),m=Math.floor((s%3600)/60);return d+'天'+h+'小时'+m+'分'+(s%60)+'秒';}
function fmtBytes(b){if(b===0)return'0 B';const k=1024,sizes=['B','KB','MB','GB','TB'];const i=Math.floor(Math.log(b)/Math.log(k));return parseFloat((b/Math.pow(k,i)).toFixed(1))+' '+sizes[i];}


/* ═══════════════════════════════════════
   HLDP 翻译层 v2.0 — 光湖驱动引擎 v2.1
   新增 @串联 @广播 — 引擎之间互相通话
   ═══════════════════════════════════════ */

const NODES={
  'BS-GZ-006':{url:'http://43.139.217.141:3910',key:'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23'},
  'BS-SG-001':{url:'http://43.156.237.110:3911',key:'zy_gtw_c38be341d65043eee0ba51a214a267832a9ae46dd73e423c'},
  'BS-SG-002':{url:'http://43.134.16.246:3910',key:'zy_gtw_ceb06d8a70d0cd38678239b3a1a9b2ba5cba77a3fbe5ebbf'},
  'BS-SG-003':{url:'http://43.153.193.169:3910',key:'zy_gtw_d7c033d8ae992ffc9dd3e0dd5fe332dace0b644c0ae7c847'},
  'ZY-SG-006':{url:'http://43.153.203.105:3910',key:'zy_gtw_fff861a2fe40cbdc366ee21f218023be8b1c182f66c852d3'},
  'BS-SH-005':{url:'http://124.223.10.33:3910',key:'zy_gtw_b1045de5ddfd7832e3c53349d9c896f054b85a4a9ece22f9'},
};

function parseOpLine(t){
  if(t.startsWith('@执行:')){
    const cmd=t.replace('@执行:','').trim();
    const parts=cmd.split(/\s+/);
    if(parts[0]==='shell')return{type:'shell',cmd:parts.slice(1).join(' ')};
    if(parts[0]==='python')return{type:'python',cmd:parts.slice(1).join(' ')};
    return{type:'shell',cmd:cmd};
  }
  if(t.startsWith('@部署:')){
    const action=t.replace('@部署:','').trim();
    const parts=action.split(/\s+/);
    if(parts[0]==='写入')return{type:'write',path:parts[1]};
    if(parts[0]==='命令')return{type:'shell',cmd:parts.slice(1).join(' ')};
  }
  return null;
}

function parseHLDP(text){
  const lines=text.split('\n');
  const groups=[];
  let relay=null;
  for(const line of lines){
    const t=line.trim();
    if(!t)continue;
    if(t.startsWith('@串联:')){relay=t.replace('@串联:','').trim();continue;}
    if(t==='@广播'){relay='BROADCAST';continue;}
    const op=parseOpLine(t);
    if(!op)continue;
    if(!groups.length||groups[groups.length-1].relay!==relay){
      groups.push({relay:relay,ops:[]});
    }
    groups[groups.length-1].ops.push(op);
  }
  return groups;
}

async function relayHLDP(target,hldpText){
  const node=NODES[target];
  if(!node)return{target:target,ok:false,error:'未知节点: '+target};
  return new Promise(r=>{
    const body=JSON.stringify({hlpd:hldpText});
    const opts={hostname:node.url.replace(/https?:\/\//,'').split(':')[0],
      port:parseInt(node.url.split(':').pop()||'3910',10),
      path:'/hlpd',method:'POST',
      headers:{'Content-Type':'application/json','Authorization':'Bearer '+node.key,'Content-Length':Buffer.byteLength(body)}};
    const req=http.request(opts,res=>{let d='';res.on('data',c=>d+=c);res.on('end',()=>{try{r({target:target,ok:true,result:JSON.parse(d)})}catch(e){r({target:target,ok:false,error:d.slice(0,200)})}})});
    req.on('error',e=>r({target:target,ok:false,error:e.message}));
    req.write(body);req.end();
  });
}

function hldpTextFromOps(ops){
  return ops.map(o=>{
    if(o.type==='shell')return '@执行: shell '+o.cmd;
    if(o.type==='python')return '@执行: python '+o.cmd;
    if(o.type==='write')return '@部署: 写入 '+o.path;
    return '';
  }).filter(l=>l).join('\n');
}

async function runHLDP(hlpdText){
  const groups=parseHLDP(hlpdText);
  const allResults=[];
  for(const g of groups){
    if(!g.relay){
      for(const op of g.ops){
        if(op.type==='shell'){
          const r=await execCmd(op.cmd);
          allResults.push({type:'shell',cmd:op.cmd,...r});
        }else if(op.type==='write'){
          try{
            const dir=path.dirname(op.path);
            if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true});
            allResults.push({type:'write',path:op.path,ok:true});
          }catch(e){allResults.push({type:'write',path:op.path,ok:false,error:e.message});}
        }else if(op.type==='python'){
          const r=await execCmd('python3 -c "'+op.cmd.replace(/"/g,'\\"')+'"');
          allResults.push({type:'python',cmd:op.cmd,...r});
        }
      }
    }else if(g.relay==='BROADCAST'){
      const hldp=hldpTextFromOps(g.ops);
      const targets=Object.keys(NODES);
      const promises=targets.map(t=>relayHLDP(t,hldp));
      const results=await Promise.all(promises);
      allResults.push({type:'broadcast',targets:targets.length,results:results});
    }else{
      const hldp=hldpTextFromOps(g.ops);
      const r=await relayHLDP(g.relay,hldp);
      allResults.push({type:'relay',target:g.relay,...r});
    }
  }
  return{groups:groups.length,results:allResults};
}


/* ═══════════════════════════════════════
   HTTP 服务器 + 路由
   ═══════════════════════════════════════ */

const server=http.createServer(async(req,res)=>{
  if(req.method==='OPTIONS'){jr(res,204,{});return;}
  if(req.method!=='POST'){jr(res,405,{error:'只接受 POST 请求'});return;}

  const ip=req.socket.remoteAddress||'unknown';
  if(!checkRate(ip)){log('WARN','rate_limit',ip);jr(res,429,{error:'请求过于频繁'});return;}

  const a=auth(req.headers);
  if(!a.ok){log('WARN','auth_failed',a.reason);jr(res,401,{error:a.reason});return;}

  const url=new URL(req.url,'http://'+((req.headers.host)||'localhost'));
  const route=url.pathname;

  try{
    if(route==='/exec'){
      const b=await parseBody(req);
      if(!b||!b.cmd){jr(res,400,{error:'缺少 cmd 参数'});return;}
      log('INFO','exec',b.cmd.slice(0,200));
      const r=await execCmd(b.cmd,b.timeout);
      log('INFO','exec_result','exit='+r.code);
      jr(res,200,{ok:r.code===0,stdout:r.stdout,stderr:r.stderr,exitCode:r.code});
    }else if(route==='/health'){
      jr(res,200,{ok:true,service:'guanghu-engine',version:'2.1.0',uptime:process.uptime().toFixed(0)+'s',timestamp:new Date().toISOString()});
    }else if(route==='/status'){
      log('INFO','status','');
      const total=os.totalmem(),free=os.freemem();
      jr(res,200,{ok:true,hostname:os.hostname(),platform:os.platform(),arch:os.arch(),cpus:os.cpus().length,uptime:os.uptime(),uptime_str:fmtUptime(os.uptime()),memory:{total:fmtBytes(total),free:fmtBytes(free),used:fmtBytes(total-free),usage:((total-free)/total*100).toFixed(1)+'%'},load:os.loadavg().map(n=>+n.toFixed(2)),engine:{version:'2.1.0',port:PORT,uptime:process.uptime().toFixed(0)+'s'}});
    }else if(route==='/hlpd'){
      const b=await parseBody(req);
      if(!b||!b.hlpd){jr(res,400,{error:'缺少 hlpd 参数'});return;}
      log('INFO','hlpd','执行HLDP模块,长度='+b.hlpd.length);
      const r=await runHLDP(b.hlpd);
      jr(res,200,{ok:true,hlpd:r});
    }else if(route==='/file/read'){
      const b=await parseBody(req);
      if(!b||!b.path){jr(res,400,{error:'缺少 path 参数'});return;}
      log('INFO','file_read',b.path);
      try{
        const c=fs.readFileSync(b.path,'utf-8');
        const s=fs.statSync(b.path);
        jr(res,200,{ok:true,path:b.path,size:s.size,modified:s.mtime.toISOString(),content:c.slice(0,MAX_OUTPUT)});
      }catch(e){jr(res,404,{ok:false,error:e.message});}
    }else if(route==='/file/write'){
      const b=await parseBody(req);
      if(!b||!b.path||b.content===undefined){jr(res,400,{error:'缺少 path 或 content'});return;}
      log('INFO','file_write',b.path);
      try{
        const d=path.dirname(b.path);
        if(!fs.existsSync(d))fs.mkdirSync(d,{recursive:true});
        fs.writeFileSync(b.path,b.content,'utf-8');
        jr(res,200,{ok:true,path:b.path,size:Buffer.byteLength(b.content,'utf-8')});
      }catch(e){jr(res,500,{ok:false,error:e.message});}
    }else if(route==='/file/list'){
      const b=await parseBody(req);
      const p=(b&&b.path)||'.';
      log('INFO','file_list',p);
      try{
        const items=fs.readdirSync(p,{withFileTypes:true});
        const files=[];
        for(const x of items){files.push({name:x.name,type:x.isDirectory()?'dir':'file'});}
        jr(res,200,{ok:true,path:p,files:files});
      }catch(e){jr(res,404,{ok:false,error:e.message});}
    }else if(route==='/ping'){
      jr(res,200,{ok:true,pong:true});
    }else{
      jr(res,404,{error:'未知路径: '+route});
    }
  }catch(e){log('ERROR','unhandled',e.message);jr(res,500,{error:'内部错误: '+e.message});}
});

/* ═══ 启动 + 错误处理 ═══ */
server.on('error',(e)=>{
  if(e.code==='EADDRINUSE'){
    log('FATAL','eaddrinuse','端口 '+PORT+' 已被占用，3秒后退出');
    console.error('❌ 端口 '+PORT+' 已被占用，无法启动。请先释放端口。');
    setTimeout(()=>process.exit(1),3000);
  }else{
    log('FATAL','server_error',e.message);
    console.error('❌ 服务器错误: '+e.message);
    process.exit(1);
  }
});

server.listen(PORT,'0.0.0.0',()=>{
  const h=os.hostname();
  log('INFO','startup','host='+h+' port='+PORT+' v2.1');
  console.log('  ⚔️ 光湖驱动引擎 v2.1 · HLDP + 集群串联');
  console.log('  主机名: '+h+'  端口: '+PORT);
});

process.on('SIGTERM',()=>{log('INFO','shutdown','SIGTERM');server.close(()=>process.exit(0));});
process.on('SIGINT',()=>{log('INFO','shutdown','SIGINT');server.close(()=>process.exit(0));});
process.on('uncaughtException',(e)=>{log('ERROR','uncaught',e.message);});
process.on('unhandledRejection',(e)=>{log('ERROR','unhandled',e.message);});
