require("./lib/logger");
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,
  startEncoder,
  stopEncoder,
} = 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;
const resolvedLocalPort = Array.isArray(localPort)
  ? localPort[localPort.length - 1]
  : localPort;
const clients = new Set();
let cameraList = [];
let sharedEndTimerId;
const broadcast = (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("network-rc");
  } 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;

const encoderConfigPath = path.resolve(__dirname, "back/encoder.config.json");
const defaultEncoderConfig = {
  enabled: false,
  pinA: 5,
  pinB: 6,
  ppr: 100,
  mode: "x4",
  intervalMs: 1000,
  pollIntervalMs: 2,
  forcePolling: false,
  glitchUs: 0,
  invert: false,
  gpioBackend: "",
  gpiodChip: "gpiochip0",
  gpiodLineA: null,
  gpiodLineB: null,
  gpiodBias: "as-is",
  gpiodActiveLow: false,
  wheelDiameterMm: 65,
  wheelCircumferenceMm: 0,
  speedUnit: "kmh",
  speedLabel: "车速",
  rpmLabel: "转速",
  directionLabel: "方向",
  showRpm: true,
  showDirection: false,
};

const loadEncoderConfig = () => {
  const fileConfig = localGet(encoderConfigPath);
  const merged = { ...defaultEncoderConfig, ...fileConfig };
  if (!fs.existsSync(encoderConfigPath)) {
    localSave(encoderConfigPath, merged);
  }
  return merged;
};

let encoderConfig = loadEncoderConfig();

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

sessionManager.clearTimeoutSession();
if (
  status.config.sharedEndTime &&
  status.config.sharedEndTime < new Date().getTime()
) {
  status.saveConfig({ sharedEndTime: undefined });
}

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);
      socket.sendData("success", { message: "设置已保存！" });
      if (!payload.sharedCode) {
        clients.forEach((socket) => {
          if (socket.session && socket.session.sharedCode) {
            socket.close();
            clients.delete(socket);
          }
        });
        status.saveConfig({ sharedEndTime: undefined });
        sessionManager.clearSharedCodeSession();
      }
      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 "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 normalizeSpeedUnit = (unit) => {
  const key = String(unit || "").toLowerCase();
  if (["mps", "m/s", "ms"].includes(key)) return "m/s";
  if (["kmh", "km/h", "kph"].includes(key)) return "km/h";
  if (["mph"].includes(key)) return "mph";
  return "km/h";
};

const calcSpeedFromRpm = (rpm, config) => {
  const diameterMm = Number(config.wheelDiameterMm) || 0;
  const circumferenceMm = Number(config.wheelCircumferenceMm) || 0;
  const circumferenceM =
    (circumferenceMm > 0 ? circumferenceMm : Math.PI * diameterMm) / 1000;
  if (!circumferenceM || !Number.isFinite(circumferenceM)) {
    return { speed: 0, unit: normalizeSpeedUnit(config.speedUnit) };
  }

  const speedMps = (rpm * circumferenceM) / 60;
  const unit = normalizeSpeedUnit(config.speedUnit);
  if (unit === "m/s") return { speed: speedMps, unit };
  if (unit === "mph") return { speed: speedMps * 2.236936, unit };
  return { speed: speedMps * 3.6, unit };
};

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 startEncoderService = () => {
  encoderConfig = loadEncoderConfig();
  if (!encoderConfig.enabled) {
    logger.info("Encoder 未启用，跳过启动");
    return;
  }

  startEncoder(encoderConfig, (payload) => {
    const rpm = Number(payload.rpm) || 0;
    const speedInfo = calcSpeedFromRpm(rpm, encoderConfig);
    const speedValue = speedInfo.speed;
    const speedLabel = encoderConfig.speedLabel || "车速";
    const speedText = `${speedValue.toFixed(2)}${speedInfo.unit}`;

    broadcast("status info", {
      label: speedLabel,
      value: speedText,
      type: "tag",
    });

    if (encoderConfig.showRpm) {
      broadcast("status info", {
        label: encoderConfig.rpmLabel || "转速",
        value: `${rpm.toFixed(2)}rpm`,
        type: "tag",
      });
    }

    if (encoderConfig.showDirection) {
      const dirText = payload.dir === "reverse" ? "反转" : "正转";
      broadcast("status info", {
        label: encoderConfig.directionLabel || "方向",
        value: dirText,
        type: "tag",
      });
    }
  });
};

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, sharedCode }) => {
  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 && !sharedCode && !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 (status.config.sharedCode && sharedCode) {
    logger.info("login shared code", sharedCode);
    if (status.config.sharedCode === sharedCode) {
      socket.isLogin = true;
      const userType = "guest";
      const nowTime = new Date().getTime();
      if (!status.config.sharedEndTime) {
        status.saveConfig({
          sharedEndTime: nowTime + status.config.sharedDuration,
        });
        broadcastConfig();
      }
      const endTime = status.config.sharedEndTime;
      const session = sessionManager.add({ userType, sharedCode, endTime });
      socket.session = session;
      socket.sendData("login", {
        session,
        status: 0,
        message: "🏎️ 分享链接登陆成功 ！",
      });
      afterLogin();

      if (!sharedEndTimerId) {
        sharedEndTimerId = setTimeout(() => {
          broadcast("info", { message: "分享时间结束。" });
          status.saveConfig({
            sharedCode: undefined,
            sharedEndTime: undefined,
          });
          broadcast("config", status.config);
          clients.forEach((socket) => {
            if (socket.session.sharedCode) {
              socket.close();
              clients.delete(socket);
            }
          });
          sharedEndTimerId = undefined;
          sessionManager.clearSharedCodeSession();
        }, endTime - nowTime);
      }

      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",
    });
  }
});

startEncoderService();

new MicrophoneServer({ server });
new AudioServer({ server });

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(`开始启动服务，端口：${resolvedLocalPort}`);
  server.listen(resolvedLocalPort, async (e) => {
    logger.info("server", server.address());
    logger.info(`本地访问地址 http://${getIPAdress()}:${resolvedLocalPort}`);
  });
};

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("Exit Clean");
    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) {}

    stopEncoder();
    await shutdownGpioProcess();
    audioPlayer.destroy();
    await sleep(1000);
  }
  if (exitCode || exitCode === 0) console.log(exitCode);
  if (options.exit) process.exit();
}

//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 }));
