// 在模块加载前重定向标准错误以抑制弃用警告
const originalStderrWrite = process.stderr.write;
process.stderr.write = function (chunk, encoding, callback) {
  // 检查是否是弃用警告
  if (typeof chunk === 'string' && chunk.includes('DeprecationWarning')) {
    if (callback) callback();
    return true;
  }
  return originalStderrWrite.apply(this, arguments);
};

require("./lib/logger");
logger.info("RaspiCar程序启动中...");
const { WebSocketServer } = require("ws");
const package = require("./package.json");
const md5 = require("md5");
const { spawn, execSync } = require("child_process");
const CameraServer = require("./lib/CameraServer");
const AudioServer = require("./lib/AudioServer");
const audioPlayer = require("./lib/audioPlayer");
const status = require("./lib/status");
const MicrophoneServer = require("./lib/MicrophoneServer");
const { sleep, localGet, localSave } = require("./lib/unit");
const { createServer } = require(`http`);
const sessionManager = require("./lib/session");
const {
  changePwmPin,
  closeChannel,
  changeSwitchPin,
  channelStatus,
  shutdownGpioProcess,
} = require("./lib/channel");
const WebRTC = require("./lib/WebRTC");
const ad = require("./lib/ads1115");
const fs = require("fs");
const path = require("path");

const argv = require("yargs")
  .usage("Usage: $0 [options]")
  .example("$0 -f -o 9058", "开启网络穿透")
  .options({
    p: {
      alias: "password",
      describe: "密码",
      type: "string",
    },
    lp: {
      alias: "localPort",
      default: 8080,
      describe: "local server port",
      type: "number",
    },
  })
  .env("NETWORK_RC")
  .help().argv;

const { localPort, password } = argv;

// 自动停止 PulseAudio 和 PipeWire 以释放音频设备
try {
  execSync("pulseaudio --kill 2>/dev/null || true", { stdio: 'ignore' });
  logger.info("已尝试停止 PulseAudio 以释放音频设备");
} catch (e) {
  logger.warn("停止 PulseAudio 失败: " + e.message);
}

// 停止 PipeWire
try {
  execSync("systemctl --user stop pipewire 2>/dev/null || true", { stdio: 'ignore' });
  execSync("systemctl --user stop pipewire-pulse 2>/dev/null || true", { stdio: 'ignore' });
  execSync("systemctl --user stop wireplumber 2>/dev/null || true", { stdio: 'ignore' });
  try { execSync("pkill -f pipewire", { stdio: 'ignore' }); } catch(e) {}
  try { execSync("pkill -f wireplumber", { stdio: 'ignore' }); } catch(e) {}
  logger.info("已尝试停止 PipeWire 以释放音频设备");
} catch (e) {
  logger.warn("停止 PipeWire 失败: " + e.message);
}
const resolvedLocalPort = Array.isArray(localPort)
  ? localPort[localPort.length - 1]
  : localPort;
const clients = new Set();
let cameraList = [];

const broadcast = (action, payload) => {
  // 对于麦克风控制消息，转发给 MicrophoneServer
  if (action === 'start mic' || action === 'stop mic') {
    microphoneServer && microphoneServer.handleAction(action, payload);
  }
  
  // 将消息广播给所有客户端
  clients.forEach(
    (socket) => socket.isLogin && socket.sendData(action, payload)
  );
};

const broadcastConfig = () => {
  const { channelList, uiComponentList, ...other } = status.config;
  broadcast("config", other);
};

const isOurProcess = (pid) => {
  if (!pid || pid === process.pid) return false;
  try {
    const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf8");
    return cmdline.includes("node") && cmdline.includes("index.js") && cmdline.includes("RaspiCar");
  } catch (e) {
    return false;
  }
};

const tryReleasePort = (port) => {
  let output = "";
  try {
    output = execSync("ss -ltnp", { stdio: ["ignore", "pipe", "ignore"] }).toString();
  } catch (e) {
    logger.warn("无法执行 ss，无法自动释放端口");
    return false;
  }

  const lines = output.split("\n").filter((line) => line.includes(`:${port}`));
  const pidSet = new Set();
  lines.forEach((line) => {
    const matches = line.match(/pid=(\d+)/g) || [];
    matches.forEach((item) => {
      const pid = parseInt(item.replace("pid=", ""), 10);
      if (pid) pidSet.add(pid);
    });
  });

  let killed = 0;
  pidSet.forEach((pid) => {
    if (isOurProcess(pid)) {
      try {
        process.kill(pid, "SIGTERM");
        killed++;
      } catch (e) {}
    }
  });

  if (killed > 0) {
    setTimeout(() => {
      pidSet.forEach((pid) => {
        if (isOurProcess(pid)) {
          try {
            process.kill(pid, "SIGKILL");
          } catch (e) {}
        }
      });
    }, 1000);
  }

  return killed > 0;
};

status.argv = argv;



exports.broadcast = broadcast;
const app = require("./lib/app");
const server = createServer({}, app);

sessionManager.clearTimeoutSession();

const controllerMessageHandle = (socket, action, payload, type) => {
  switch (action) {
    case "heartbeat":
      makeHeartbeatTimer(socket);
      break;
    case "ping":
      receivePing(socket, { ...payload, type });
      break;
    case "login":
      login(socket, payload);
      if (!check(socket)) break;
      if (socket.isLogin) {
        if (socket.isLogin) {
          socket.sendData(
            "camera list",
            cameraList.map(({ name, size, label }, index) => ({
              name,
              size,
              label,
              index,
            }))
          );
          broadcastConfig();
          socket.sendData("channel status", channelStatus);
        }
      }
      break;
    case "pi reboot":
      if (!check(socket)) break;
      piReboot();
      break;
    case "save config":
      if (!check(socket)) break;
      status.saveConfig(payload);
      
      // 检查是否有编码器配置更新
      const encoderKeys = [
        'encoderEnabled', 'encoderBaudRate', 'encoderPPR', 
        'encoderWheelDiameterMm', 'encoderIntervalMs', 'encoderSpeedUnit', 
        'encoderInvert'
      ];
      const hasEncoderConfig = encoderKeys.some(key => key in payload);
      
      if (hasEncoderConfig) {
        const encoderModule = require('./encoder-module');
        const encoderConfig = {};
        
        // 映射前端配置到编码器配置格式（Arduino Nano Mini）
        if ('encoderEnabled' in payload) encoderConfig.enabled = payload.encoderEnabled;
        if ('encoderBaudRate' in payload) encoderConfig.baudRate = payload.encoderBaudRate;
        if ('encoderPPR' in payload) encoderConfig.ppr = payload.encoderPPR;
        if ('encoderWheelDiameterMm' in payload) encoderConfig.wheelDiameterMm = payload.encoderWheelDiameterMm;
        if ('encoderIntervalMs' in payload) encoderConfig.intervalMs = payload.encoderIntervalMs;
        if ('encoderSpeedUnit' in payload) encoderConfig.speedUnit = payload.encoderSpeedUnit;
        if ('encoderInvert' in payload) encoderConfig.invert = payload.encoderInvert;
        
        // 更新编码器配置
        encoderModule.encoderModule.updateConfig(encoderConfig);
      }
      
      socket.sendData("success", { message: "设置已保存！" });

      broadcastConfig();
      break;

    case "play audio":
      if (!check(socket)) break;
      const { path, stop } = payload;
      audioPlayer.playFile(path, stop);
      break;
    case "change channel":
      if (!check(socket)) break;
      const channel = status.config.channelList.find(
        (i) => i.pin === payload.pin
      );
      if (channel && channel.enabled) {
        const { pin, value: inputValue } = payload;
        broadcast("channel status", { [pin]: inputValue });
        if (channel.type === "switch") {
          changeSwitchPin(pin, inputValue > 0 ? true : false);
          break;
        }
        const { valueReset, valuePostive, valueNegative } = channel;
        const value =
          inputValue > 0
            ? inputValue * (valuePostive - valueReset) + valueReset
            : inputValue == 0
            ? valueReset
            : inputValue * (valueReset - valueNegative) + valueReset;
        changePwmPin(pin, value);
      }
      break;
    case "start mic":
      if (!check(socket)) break;
      // 广播启动麦克风指令
      broadcast("start mic", {});
      logger.info("已广播启动麦克风指令");
      break;
    case "stop mic":
      if (!check(socket)) break;
      // 广播停止麦克风指令
      broadcast("stop mic", {});
      logger.info("已广播停止麦克风指令");
      break;
    case "reset channel":
      status.resetChannelAndUI();
      broadcastConfig();
      broadcast("success", { message: "通道已重置！！！！！" });
      break;


    // case "download cert":
    //   downloadCert()
    //   break;

    default:
      logger.info("怎么了？");
  }
};

const getVoltageLabel = (index) => `电压${index + 1}`;
const formatVoltage = (v) => `${Number(v).toFixed(1)}v`;





const broadcastVoltages = (voltages = []) => {
  const maxCount = 4;
  for (let i = 0; i < maxCount; i++) {
    const v = voltages[i] || 0;
    if (v > 0) {
      broadcast("status info", {
        label: getVoltageLabel(i),
        value: formatVoltage(v),
        type: "tag",
      });
    } else {
      broadcast("status info", {
        label: getVoltageLabel(i),
        remove: true,
      });
    }
  }
};



const afterLogin = () => {
  if (Array.isArray(ad.voltages)) {
    broadcastVoltages(ad.voltages);
    return;
  }
  if (ad.voltage !== 0) {
    broadcast("status info", {
      label: getVoltageLabel(0),
      value: formatVoltage(ad.voltage),
      type: "tag",
    });
  }
};

const login = (socket, { sessionId, token }) => {
  logger.info("Login in");
  if (socket.islogin) {
    socket.sendData("login", { status: 1, message: "已登陆！" });
    afterLogin();
    return;
  }

  if (!password) {
    socket.isLogin = true;
    socket.session = sessionManager.add({
      userType: "admin",
      noPassword: true,
    });
    socket.sendData("login", {
      session: socket.session,
      status: 0,
      message: "登陆成功",
    });
    afterLogin();
    return;
  } else {
    if (!token && !sessionId) {
      check(socket);
    }
  }

  if (token) {
    if (md5(password + "eson") == token) {
      socket.isLogin = true;
      const userType = "admin";
      const session = sessionManager.add({ userType });
      socket.session = session;

      socket.sendData("login", {
        session,
        status: 0,
        message: "登陆成功",
      });
      afterLogin();
      return;
    } else {
      socket.sendData("error", { status: 1, message: "密码错误" });
      return;
    }
  }

  if (sessionId) {
    logger.info("login with session", sessionId);
    const session = sessionManager.list.find((i) => i.id === sessionId);
    if (session) {
      const { noPassword } = session;
      if (password && noPassword) {
        socket.sendData("error", {
          status: 1,
          message: "登录过期！",
        });
        return;
      }

      socket.isLogin = true;
      socket.session = session;
      socket.sendData("login", {
        session,
        status: 0,
        message: "已登录！",
      });
      afterLogin();
      return;
    } else {
      socket.sendData("error", {
        status: 1,
        message: "登录过期！",
      });
    }
  }
};

/**
 * 接收到 ping 信号时执行
 * @param {WebSocket} socket
 * @param {object} param1
 */
const receivePing = (socket, { sendTime }) => {
  socket.sendData("pong", { sendTime });
};

/** 清除、创建心跳超时计时器 */
const makeHeartbeatTimer = (socket) => {
  socket.heartbeatTimeoutId && clearTimeout(socket.heartbeatTimeoutId);
  if (socket.autoLocking) {
    /** 刹车锁定后 正常心跳统计， 大于 10 就解锁 */
    socket.unlockHearbertCount++;
    if (socket.unlockHearbertCount > 10) {
      socket.autoLocking = false;
      socket.unlockHearbertCount = 0;
      logger.info("网络恢复");
      socket.sendData("locked", false);
    }
  }
  socket.heartbeatTimeoutId = setTimeout(async () => {
    socket.unlockHearbertCount = 0;
    if (socket.autoLocking === true) return;
    socket.autoLocking = true;
    logger.warn("网络连接不稳定，自动刹车");
    socket.sendData("locked", true);
    const { channelList = [], specialChannel } = status.config;
    const speedChannel = channelList.find(
      ({ id }) => id === specialChannel.speed
    );
    if (speedChannel) {
      const { pin, valueReset } = speedChannel;
      if (status.config.autoLockTime) {
        changePwmPin(pin, -(channelStatus[pin] || valueReset));
        await sleep(status.config.autoLockTime);
      }
      changePwmPin(pin, valueReset);
    }
  }, status.config.autoLockTime * 2);
};

const check = (socket) => {
  if (socket.isLogin) {
    return true;
  } else {
    logger.error("未登录！");
    socket.sendData("error", {
      status: 1,
      type: "auth error",
      message: "未登录！",
    });
    return false;
  }
};

const disconnect = (socket) => {
  logger.info("客户端断开连接！");
  if (socket.webrtc) socket.webrtc.close();
  clearTimeout(socket.timeout);
  clients.delete(socket);
  let num = 0;
  clients.forEach(({ isLogin }) => {
    if (isLogin) num++;
  });
  logger.info("已连接客户端", num);
  if (num < 1) {
    closeChannel();
  }
};


let restarting = false;
const piReboot = async () => {
  if (restarting) return;
  restarting = true;

  clients.forEach((socket) => {
    try {
      socket.close();
    } catch (e) {}
  });

  try {
    wss.close();
  } catch (e) {}

  await shutdownGpioProcess();

  const closeServer = () =>
    new Promise((resolve) => {
      try {
        if (server.listening) {
          server.close(resolve);
        } else {
          resolve();
        }
      } catch (e) {
        resolve();
      }
    });

  await closeServer();

  logger.info("正在重启树莓派...");
  const child = spawn("sudo", ["reboot"], {
    stdio: "inherit",
    cwd: process.cwd(),
    env: process.env,
  });
  child.on("error", (err) => {
    logger.error(`重启失败: ${err.message}`);
    restarting = false;
  });
  setTimeout(() => process.exit(0), 1000);
};

//获取本机ip地址
function getIPAdress() {
  var interfaces = require("os").networkInterfaces();
  for (var devName in interfaces) {
    var iface = interfaces[devName];
    for (var i = 0; i < iface.length; i++) {
      var alias = iface[i];
      if (
        alias.family === "IPv4" &&
        alias.address !== "127.0.0.1" &&
        !alias.internal
      ) {
        return alias.address;
      }
    }
  }
}

const wss = new WebSocketServer(
  {
    noServer: true,
    path: "/control",
  },
  () => {
    logger.info("控制 websocket 服务已启动");
  }
);

wss.on("error", (err) => {
  logger.error(`Websocket 服务器错误${err.message}`);
});

server.on("upgrade", (request, socket, head) => {
  if (request.url === "/control")
    wss.handleUpgrade(request, socket, head, (ws) => {
      wss.emit("connection", ws, request);
    });
});

ad.on("voltages-change", (voltages) => {
  broadcastVoltages(voltages);
});

ad.on("voltage-change", (v) => {
  if (!Array.isArray(ad.voltages)) {
    broadcast("status info", {
      label: getVoltageLabel(0),
      value: formatVoltage(v),
      type: "tag",
    });
  }
});



const microphoneServer = new MicrophoneServer({ server });
new AudioServer({ server });
const EncoderServer = require("./lib/EncoderServer");
new EncoderServer({ server, broadcast });

status.on("update", () => {
  broadcast("config update");
});


wss.on("connection", async function (socket) {
  logger.info("客户端连接！");
  logger.info("已经设置密码", password ? "是" : "否");

  clients.add(socket);

  socket.sendData = function (action, payload) {
    if (
      socket.webrtcChannel &&
      socket.webrtcChannel.controller &&
      socket.webrtcChannel.controller.readyState === "open"
    )
      socket.webrtcChannel.controller.send(JSON.stringify({ action, payload }));
    else this.send(JSON.stringify({ action, payload }));
  };

  socket.on("close", () => {
    disconnect(socket);
  });

  socket.on("error", (err) => {
    logger.info("Received error: ", err);
  });

  socket.on("message", (m) => {
    const { action, payload } = JSON.parse(m);

    // logger.info("Websocket recived message", action, payload);

    if (action.indexOf("webrtc") !== -1) {
      if (!check(socket)) return;
      const type = action.split(" ")[1];
      switch (type) {
        case "connect":
          const enableRtcVideo = !["0", "false", "off"].includes(
            String(process.env.NRC_RTC_VIDEO ?? "1").toLowerCase()
          );

          socket.webrtc = new WebRTC({
            socket,
            onClose() {},
            onDataChannelOpen(channel) {
              if (socket.webrtcChannel) {
                socket.webrtcChannel[channel.label] = channel;
              } else {
                socket.webrtcChannel = {
                  [channel.label]: channel,
                };
              }
              socket.sendData("connect type", "webrtc");
              if (!enableRtcVideo) return;
              const camServer = cameraList.find(
                (i) => i.label == channel.label
              );
              if (camServer) {
                camServer.server.pushRTCDataChannel(channel);
              }
            },
            onDataChannelClose(channel) {
              if (enableRtcVideo) {
                const camServer = cameraList.find(
                  (i) => i.label == channel.label
                );
                if (camServer) {
                  camServer.server.removeRTCDataChannel(channel);
                }
              }
              if (socket.webrtcChannel && socket.webrtcChannel[channel.label]) {
                delete socket.webrtcChannel[channel.label];
              }
            },
            rtcDataChannelList: [
              {
                label: "controller",
                onMessage(data) {
                  const { action, payload } = JSON.parse(data);
                  // if (action !== "heartbeat") {
                  //   logger.info("RTC message", action, payload);
                  // }
                  controllerMessageHandle(socket, action, payload, "rtc");
                },
              },
              ...(enableRtcVideo ? cameraList.map(({ label }) => ({ label })) : []),
            ],
            onOffer(offer) {
              socket.sendData("webrtc offer", offer);
            },
            sendCandidate(candidate) {
              socket.sendData("webrtc candidate", candidate);
            },
            onSuccess() {
            },
            onClose() {
              socket.sendData("webrtc close");
              delete socket.webrtc;
              // broadcast("stream_active", false);
              socket.sendData("connect type", "ws");
            },
            onError({ message }) {
              socket.sendData("switch", { protocol: "websocket" });
            },
            onWarnning({ message }) {
              socket.sendData("warn", { status: 1, message });
            },
          });
          break;
        case "answer":
          socket.webrtc.onAnswer(payload);
          break;
        case "candidate":
          socket.webrtc.addCandidate(payload);
          break;
        case "close":
          socket.webrtc && socket.webrtc.close();
          break;
        default:
          logger.info("怎么了？ webrtc", type);
          break;
      }
      return;
    }

    controllerMessageHandle(socket, action, payload, "ws");
  });
});

let serverStarting = false;
let serverListening = false;
let portRetrying = false;

const startServer = () => {
  if (serverStarting || serverListening) return;
  serverStarting = true;
  logger.info(`Web服务器启动中...端口：${resolvedLocalPort}`);
  server.listen(resolvedLocalPort, async (e) => {
    logger.info(`Web服务器启动成功，本地访问地址：http://${getIPAdress()}:${resolvedLocalPort}`);
    
    // 测试音频设备并解除占用
    logger.info("检查音频设备状态...");
    try {
      const { execSync } = require('child_process');
      // 测试录音设备
      const arecordResult = execSync("arecord -l 2>/dev/null | grep -E 'card [0-9]+:' | head -n1 || echo '未检测到录音设备'", { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
      if (arecordResult.includes('card')) {
        logger.info(`【音频】录音设备: ${arecordResult.trim()}`);
      } else {
        logger.warn("【音频】未检测到录音设备");
      }
      
      // 检查是否有进程占用音频设备
      const fuserResult = execSync("fuser -v /dev/snd/* 2>/dev/null || echo '无进程占用'", { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
      if (fuserResult.includes('PID') || fuserResult.includes('pipewire') || fuserResult.includes('pulseaudio')) {
        logger.warn("【音频】检测到音频设备被占用，正在解除...");
        // 停止占用音频的进程
        try {
          execSync("systemctl --user stop pipewire 2>/dev/null || true", { stdio: 'ignore' });
          execSync("systemctl --user stop pipewire-pulse 2>/dev/null || true", { stdio: 'ignore' });
          execSync("systemctl --user stop wireplumber 2>/dev/null || true", { stdio: 'ignore' });
          try { execSync("pkill -f pipewire", { stdio: 'ignore' }); } catch(e) {}
          try { execSync("pkill -f wireplumber", { stdio: 'ignore' }); } catch(e) {}
          try { execSync("pulseaudio --kill", { stdio: 'ignore' }); } catch(e) {}
          await sleep(1000);
          logger.info("【音频】已解除音频设备占用");
        } catch (killError) {
          logger.warn("【音频】解除占用失败: " + killError.message);
        }
      } else {
        logger.info("【音频】音频设备未被占用");
      }
    } catch (e) {
      logger.warn("【音频】音频设备检查失败: " + e.message);
    }
    
    // 输出模块运行状态
    logger.info("=== 系统启动完成 ===");
    logger.info("【运行】Web服务器");
    logger.info("【运行】摄像头服务");
    logger.info("【运行】麦克风服务");
    logger.info("【运行】音频服务");
    try {
      const encoderModule = require('./encoder-module');
      if(encoderModule && encoderModule.getStatus && encoderModule.getStatus().isRunning) {
        logger.info("【运行】编码器服务");
      } else {
        logger.info("【禁用】编码器服务");
      }
    } catch (e) {
      logger.info("【禁用】编码器服务");
    }
    logger.info("==================");
  });
};

server.on("listening", () => {
  serverStarting = false;
  serverListening = true;
});

server.on("error", (e) => {
  logger.error(`Server error: ${e.message}`);
  if (e.code === "EADDRINUSE") {
    serverStarting = false;
    logger.info(` ${resolvedLocalPort} 端口被其他程序使用了...`);
    if (!portRetrying && tryReleasePort(resolvedLocalPort)) {
      portRetrying = true;
      logger.warn("检测到旧进程占用，已尝试释放端口，稍后重试监听...");
      setTimeout(() => {
        portRetrying = false;
        if (!serverListening) startServer();
      }, 1200);
      return;
    }
    logger.warn("未能自动释放端口，请手动清理占用进程");
    exitHandler({ cleanup: true, exit: true });
  }
});

(async () => {
  cameraList = await CameraServer.getCameraList();
  cameraList.forEach((item, index) => {
    const { dev, size, name, cardType, label } = item;
    item.server = new CameraServer({
      server,
      devPath: dev,
      name,
      label,
      cardType,
      deviceSize: size,
      cameraIndex: index,
    });
  });

  startServer();
})();

// process.on("SIGHUP", async function () {
//   process.exit();
// });

let exiting = false;
async function exitHandler(options, exitCode) {
  if (exiting) return;
  exiting = true;

  if (options.cleanup) {
      logger.info("正在关闭程序...");
      try {
        clients.forEach((socket) => {
          try {
            socket.close();
          } catch (e) {}
        });
        wss.close();
        server.close();
        cameraList.forEach((item) => {
          try {
            item.server && item.server.close();
          } catch (e) {}
        });
      } catch (e) {}

      logger.info("正在关闭GPIO服务...");
      await shutdownGpioProcess();
      audioPlayer.destroy();
      
      logger.info("正在停止音频服务(pipewire/wireplumber)...");
      try {
        execSync("systemctl --user stop pipewire 2>/dev/null || true", { stdio: 'ignore' });
        execSync("systemctl --user stop pipewire-pulse 2>/dev/null || true", { stdio: 'ignore' });
        execSync("systemctl --user stop wireplumber 2>/dev/null || true", { stdio: 'ignore' });
        try { execSync("pkill -f pipewire", { stdio: 'ignore' }); } catch(e) {}
        try { execSync("pkill -f wireplumber", { stdio: 'ignore' }); } catch(e) {}
        logger.info("音频服务已停止");
      } catch (e) {
        // 忽略错误
      }
      
      await sleep(1000);
    }
  if (options.exit) {
    logger.error("RaspiCar程序已退出");
    setTimeout(() => {
      process.exit();
    }, 100);
  }
}

//do something when app is closing
process.on("exit", exitHandler.bind(null, { cleanup: true }));

//catches ctrl+c event
process.on("SIGINT", exitHandler.bind(null, { exit: true }));

// catches "kill pid" (for example: nodemon restart)
process.on("SIGUSR1", exitHandler.bind(null, { exit: true }));
process.on("SIGUSR2", exitHandler.bind(null, { exit: true }));

//catches uncaught exceptions
process.on("uncaughtException", exitHandler.bind(null, { exit: true }));
