import { app, ipcMain, Notification } from "electron";
import { getConfig } from "../storage/config";
import { listProxy } from "../storage/proxy";
import { getVersionById } from "../storage/version";
import treeKill from "tree-kill";
import { logInfo, logError, LogModule, logDebug, logWarn } from "../utils/log";

const fs = require("fs");
const path = require("path");
const { exec, spawn } = require("child_process");

export let frpcProcess = null;
const runningCmd = {
  commandPath: null,
  configPath: null
};
let frpcStatusListener = null;

const getFrpcVersionWorkerPath = (
  versionId: number,
  callback: (workerPath: string) => void
) => {
  getVersionById(versionId, (err2, version) => {
    if (!err2) {
      if (version) {
        callback(version["frpcVersionPath"]);
      }
    }
  });
};

const isRangePort = (m: Proxy) => {
  return (
    (m.type === "tcp" || m.type === "udp") &&
    (String(m.localPort).indexOf("-") !== -1 ||
      String(m.localPort).indexOf(",") !== -1)
  );
};

/**
 * 生成toml配置文件
 * @param config
 * @param proxys
 */
export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => {
  const proxyToml = proxys.map(m => {
    const rangePort = isRangePort(m);
    config.tlsConfigKeyFile = config.tlsConfigKeyFile.replace(/\\/g, "\\\\");
    config.tlsConfigCertFile = config.tlsConfigCertFile.replace(/\\/g, "\\\\");
    config.tlsConfigTrustedCaFile = config.tlsConfigTrustedCaFile.replace(
      /\\/g,
      "\\\\"
    );
    let toml = `${
      rangePort
        ? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}`
        : ""
    }
[[${
      (m.type === "stcp" || m.type === "xtcp" || m.type === "sudp") &&
      m.stcpModel === "visitors"
        ? "visitors"
        : "proxies"
    }]]
${rangePort ? "" : `name = "${m.name}"`}
type = "${m.type}"\n`;

    switch (m.type) {
      case "tcp":
      case "udp":
        if (rangePort) {
          toml += `name = "${m.name}-{{ $v.First }}"
localPort = {{ $v.First }}
remotePort = {{ $v.Second }}\n`;
        } else {
          toml += `localIP = "${m.localIp}"
localPort = ${m.localPort}
remotePort = ${m.remotePort}\n`;
        }
        break;
      case "http":
      case "https":
        const customDomains = m.customDomains.filter(f1 => f1 !== "");
        if (customDomains && customDomains.length > 0) {
          toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]\n`;
        }
        if (m.subdomain) {
          toml += `subdomain="${m.subdomain}"\n`;
        }
        if (m.basicAuth) {
          toml += `httpUser = "${m.httpUser}"
httpPassword = "${m.httpPassword}"\n`;
        }
        if (m.https2http) {
          toml += `[proxies.plugin]
type = "https2http"
localAddr =  "${m.localIp}:${m.localPort}"

crtPath =  "${m.https2httpCaFile}"
keyPath = "${m.https2httpKeyFile}"\n`;
        } else {
          toml += `localIP = "${m.localIp}"
localPort = ${m.localPort}\n`;
        }

        break;
      case "xtcp":
        if (m.stcpModel === "visitors") {
          toml += `keepTunnelOpen = ${m.keepTunnelOpen}\n`;
        }
      case "stcp":
      case "sudp":
        if (m.stcpModel === "visitors") {
          // 访问者
          toml += `serverName = "${m.serverName}"
bindAddr = "${m.bindAddr}"
bindPort = ${m.bindPort}\n`;
          if (m.fallbackTo) {
            toml += `fallbackTo = "${m.fallbackTo}"
fallbackTimeoutMs = ${m.fallbackTimeoutMs || 500}\n`;
          }
        } else if (m.stcpModel === "visited") {
          // 被访问者
          toml += `localIP = "${m.localIp}"
localPort = ${m.localPort}\n`;
        }

        toml += `secretKey="${m.secretKey}"\n`;
        break;
      default:
        break;
    }

    if (rangePort) {
      toml += `{{- end }}`;
    }
    return toml;
  });
  const toml = `serverAddr = "${config.serverAddr}"
serverPort = ${config.serverPort}
${
  config.authMethod === "token"
    ? `auth.method = "token"
auth.token = "${config.authToken}"`
    : ""
}
${
  config.authMethod === "multiuser"
    ? `user = "${config.user}"
metadatas.token = "${config.metaToken}"`
    : ""
}
log.to = "frpc.log"
log.level = "${config.logLevel}"
log.maxDays = ${config.logMaxDays}
webServer.addr = "127.0.0.1"
webServer.port = ${config.webPort}
${
  config.transportProtocol
    ? `transport.protocol = "${config.transportProtocol}"`
    : ""
}
${
  config.transportPoolCount
    ? `transport.poolCount = ${config.transportPoolCount}`
    : ""
}
${
  config.transportDialServerTimeout
    ? `transport.dialServerTimeout = ${config.transportDialServerTimeout}`
    : ""
}
${
  config.transportDialServerKeepalive
    ? `transport.dialServerKeepalive = ${config.transportDialServerKeepalive}`
    : ""
}
${
  config.transportHeartbeatInterval
    ? `transport.heartbeatInterval = ${config.transportHeartbeatInterval}`
    : ""
}
${
  config.transportHeartbeatTimeout
    ? `transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}`
    : ""
}
${config.transportTcpMux ? `transport.tcpMux = ${config.transportTcpMux}` : ""}
${
  config.transportTcpMux && config.transportTcpMuxKeepaliveInterval
    ? `transport.tcpMuxKeepaliveInterval = ${config.transportTcpMuxKeepaliveInterval}`
    : ""
}
transport.tls.enable = ${config.tlsConfigEnable}
${
  config.tlsConfigEnable && config.tlsConfigCertFile
    ? `transport.tls.certFile = "${config.tlsConfigCertFile}"`
    : ""
}
${
  config.tlsConfigEnable && config.tlsConfigKeyFile
    ? `transport.tls.keyFile = "${config.tlsConfigKeyFile}"`
    : ""
}
  ${
    config.tlsConfigEnable && config.tlsConfigTrustedCaFile
      ? `transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"`
      : ""
  }
  ${
    config.tlsConfigEnable && config.tlsConfigServerName
      ? `transport.tls.serverName = "${config.tlsConfigServerName}"`
      : ""
  }
${
  config.proxyConfigEnable
    ? `transport.proxyURL = "${config.proxyConfigProxyUrl}"`
    : ""
}
${proxyToml.join("")}`;
  return toml;
};

/**
 * 生成ini配置
 * @param config
 * @param proxys
 */
export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => {
  const proxyIni = proxys.map(m => {
    const rangePort = isRangePort(m);
    let ini = `[${rangePort ? "range:" : ""}${m.name}]
type = "${m.type}"
`;
    switch (m.type) {
      case "tcp":
      case "udp":
        ini += `
local_ip = "${m.localIp}"
local_port = ${m.localPort}
remote_port = ${m.remotePort}
`;
        break;
      case "http":
        ini += `
        local_ip = "${m.localIp}"
        local_port = ${m.localPort}
        custom_domains=[${m.customDomains.map(m => `${m}`)}]
        subdomain="${m.subdomain}"
        `;
        if (m.basicAuth) {
          ini += `
        httpUser = "${m.httpUser}"
        httpPassword = "${m.httpPassword}"
        `;
        }
        break;
      case "https":
        ini += `
custom_domains=[${m.customDomains.map(m => `${m}`)}]
subdomain="${m.subdomain}"
        `;
        if (m.basicAuth) {
          ini += `
httpUser = "${m.httpUser}"
httpPassword = "${m.httpPassword}"
        `;
        }
        if (m.https2http) {
          ini += `
plugin = https2http
plugin_local_addr = ${m.localIp}:${m.localPort}
plugin_crt_path = ${m.https2httpCaFile}
plugin_key_path = ${m.https2httpKeyFile}
        `;
        } else {
          ini += `
local_ip = "${m.localIp}"
local_port = ${m.localPort}
                  `;
        }
        break;
      case "stcp":
      case "xtcp":
      case "sudp":
        if (m.stcpModel === "visitors") {
          // 访问者
          ini += `
role = visitor
server_name = "${m.serverName}"
bind_addr = "${m.bindAddr}"
bind_port = ${m.bindPort}
`;
          if (m.fallbackTo) {
            ini += `
fallback_to = ${m.fallbackTo}
fallback_timeout_ms = ${m.fallbackTimeoutMs || 500}
            `;
          }
        } else if (m.stcpModel === "visited") {
          // 被访问者
          ini += `
local_ip = "${m.localIp}"
local_port = ${m.localPort}`;
        }
        ini += `
sk="${m.secretKey}"
`;
        break;
      default:
        break;
    }

    return ini;
  });
  const ini = `
[common]
server_addr = ${config.serverAddr}
server_port = ${config.serverPort}
${
  config.authMethod === "token"
    ? `
authentication_method = ${config.authMethod}
token = ${config.authToken}
`
    : ""
}
${
  config.authMethod === "multiuser"
    ? `
user = ${config.user}
meta_token = ${config.metaToken}
`
    : ""
}

${
  config.transportHeartbeatInterval
    ? `
heartbeat_interval = ${config.transportHeartbeatInterval}
`
    : ""
}
${
  config.transportHeartbeatTimeout
    ? `
heartbeat_timeout = ${config.transportHeartbeatTimeout}
`
    : ""
}

log_file = "frpc.log"
log_level = ${config.logLevel}
log_max_days = ${config.logMaxDays}
admin_addr = 127.0.0.1
admin_port = ${config.webPort}
tls_enable = ${config.tlsConfigEnable}

${
  config.tlsConfigEnable && config.tlsConfigCertFile
    ? `
tls_cert_file = ${config.tlsConfigCertFile}
`
    : ""
}
  ${
    config.tlsConfigEnable && config.tlsConfigKeyFile
      ? `
tls_key_file = ${config.tlsConfigKeyFile}
`
      : ""
  }
  ${
    config.tlsConfigEnable && config.tlsConfigTrustedCaFile
      ? `
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
`
      : ""
  }
  ${
    config.tlsConfigEnable && config.tlsConfigServerName
      ? `
tls_server_name = ${config.tlsConfigServerName}
`
      : ""
  }

${
  config.proxyConfigEnable
    ? `
http_proxy = "${config.proxyConfigProxyUrl}"
`
    : ""
}

${proxyIni.join("")}
    `;
  return ini;
};

/**
 * 生成配置文件
 */
export const generateConfig = (
  config: FrpConfig,
  callback: (configPath: string) => void
) => {
  listProxy((err3, proxys) => {
    if (err3) {
      logError(LogModule.FRP_CLIENT, `Failed to list proxies: ${err3.message}`);
      return;
    }

    const { currentVersion } = config;
    let filename = null;
    let configContent = "";
    const filtered = proxys
      .map(m => {
        if (m.status == null || m.status == undefined) {
          m.status = true;
        }
        return m;
      })
      .filter(f => f.status);

    if (currentVersion < 124395282) {
      // 版本小于v0.52.0
      filename = "frp.ini";
      configContent = genIniConfig(config, filtered);
      logInfo(
        LogModule.FRP_CLIENT,
        `Using INI format for configuration: ${filename}`
      );
    } else {
      filename = "frp.toml";
      configContent = genTomlConfig(config, filtered);
      logInfo(
        LogModule.FRP_CLIENT,
        `Using TOML format for configuration: ${filename}`
      );
    }

    const configPath = path.join(app.getPath("userData"), filename);
    logInfo(
      LogModule.FRP_CLIENT,
      `Writing configuration to file: ${configPath}`
    );

    fs.writeFile(
      configPath, // 配置文件目录
      configContent, // 配置文件内容
      { flag: "w" },
      err => {
        if (err) {
          logError(
            LogModule.FRP_CLIENT,
            `Failed to write configuration file: ${err.message}`
          );
        } else {
          logInfo(
            LogModule.FRP_CLIENT,
            `Configuration file written successfully: ${filename}`
          );
          callback(filename);
        }
      }
    );
  });
};

/**
 * 启动frpc子进程
 * @param cwd
 * @param commandPath
 * @param configPath
 */
const startFrpcProcess = (commandPath: string, configPath: string) => {
  logInfo(
    LogModule.FRP_CLIENT,
    `Starting frpc process. Directory: ${app.getPath(
      "userData"
    )}, Command: ${commandPath}`
  );

  const command = `${commandPath} -c ${configPath}`;
  frpcProcess = spawn(command, {
    cwd: app.getPath("userData"),
    shell: true
  });
  runningCmd.commandPath = commandPath;
  runningCmd.configPath = configPath;

  frpcProcess.stdout.on("data", data => {
    logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`);
  });

  frpcProcess.stdout.on("error", data => {
    logError(LogModule.FRP_CLIENT, `Frpc process error: ${data}`);
    stopFrpcProcess(() => {});
  });

  frpcStatusListener = setInterval(() => {
    const status = frpcProcessStatus();
    logDebug(
      LogModule.FRP_CLIENT,
      `Monitoring frpc process status: ${status}, Listener ID: ${frpcStatusListener}`
    );
    if (!status) {
      new Notification({
        title: "Frpc Desktop",
        body: "Connection lost, please check the logs for details."
      }).show();
      logError(
        LogModule.FRP_CLIENT,
        "Frpc process status check failed. Connection lost."
      );
      clearInterval(frpcStatusListener);
    }
  }, 3000);
};

/**
 *  重载frpc配置
 */
export const reloadFrpcProcess = () => {
  if (frpcProcess && !frpcProcess.killed) {
    logDebug(
      LogModule.FRP_CLIENT,
      "Attempting to reload frpc process configuration."
    );
    getConfig((err1, config) => {
      if (!err1) {
        if (config) {
          generateConfig(config, configPath => {
            const command = `${runningCmd.commandPath} reload -c ${configPath}`;
            logInfo(
              LogModule.FRP_CLIENT,
              `Reloading configuration: ${command}`
            );
            exec(
              command,
              {
                cwd: app.getPath("userData"),
                shell: true
              },
              (error, stdout, stderr) => {
                if (error) {
                  logError(
                    LogModule.FRP_CLIENT,
                    `Error reloading configuration: ${error.message}`
                  );
                  return;
                }
                logDebug(
                  LogModule.FRP_CLIENT,
                  `Configuration reload output: ${stdout}`
                );
                if (stderr) {
                  logWarn(
                    LogModule.FRP_CLIENT,
                    `Configuration reload warnings: ${stderr}`
                  );
                }
              }
            );
          });
        } else {
          logWarn(LogModule.FRP_CLIENT, "No configuration found to reload.");
        }
      } else {
        logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`);
      }
    });
  } else {
    logDebug(
      LogModule.FRP_CLIENT,
      "frpc process is not running or has been killed."
    );
  }
};

/**
 * 停止frpc子进程
 */
export const stopFrpcProcess = (callback?: () => void) => {
  if (frpcProcess) {
    treeKill(frpcProcess.pid, (error: Error) => {
      if (error) {
        logError(
          LogModule.FRP_CLIENT,
          `Failed to stop frpc process with pid: ${frpcProcess.pid}. Error: ${error.message}`
        );
        callback();
      } else {
        logInfo(
          LogModule.FRP_CLIENT,
          `Successfully stopped frpc process with pid: ${frpcProcess.pid}.`
        );
        frpcProcess = null;
        clearInterval(frpcStatusListener);
        callback();
      }
    });
  } else {
    logWarn(
      LogModule.FRP_CLIENT,
      "Attempted to stop frpc process, but no process is running."
    );
    logWarn(LogModule.FRP_CLIENT, "No frpc process to stop.");
    callback();
  }
};

/**
 * 获取frpc子进程状态
 */
export const frpcProcessStatus = () => {
  if (!frpcProcess) {
    logDebug(LogModule.FRP_CLIENT, "frpc process is not running.");
    return false;
  }
  try {
    // 发送信号给进程，如果进程存在，会正常返回
    process.kill(frpcProcess.pid, 0);
    logDebug(
      LogModule.FRP_CLIENT,
      `frpc process is running with pid: ${frpcProcess.pid}`
    );
    return true;
  } catch (error) {
    // 进程不存在，抛出异常
    logError(
      LogModule.FRP_CLIENT,
      `frpc process not found. Error: ${error.message}`
    );
    return false;
  }
};

/**
 * 启动frpc流程
 * @param config
 */
export const startFrpWorkerProcess = async (config: FrpConfig) => {
  logInfo(LogModule.FRP_CLIENT, "Starting frpc worker process...");
  getFrpcVersionWorkerPath(config.currentVersion, (frpcVersionPath: string) => {
    if (frpcVersionPath) {
      logInfo(
        LogModule.FRP_CLIENT,
        `Found frpc version path: ${frpcVersionPath}`
      );
      generateConfig(config, configPath => {
        const platform = process.platform;
        if (platform === "win32") {
          logInfo(LogModule.FRP_CLIENT, "Starting frpc on Windows.");
          startFrpcProcess(path.join(frpcVersionPath, "frpc.exe"), configPath);
        } else {
          logInfo(
            LogModule.FRP_CLIENT,
            "Starting frpc on non-Windows platform."
          );
          startFrpcProcess(path.join(frpcVersionPath, "frpc"), configPath);
        }
      });
    } else {
      logError(LogModule.FRP_CLIENT, "frpc version path not found.");
    }
  });
};

export const initFrpcApi = () => {
  ipcMain.handle("frpc.running", async (event, args) => {
    logDebug(LogModule.FRP_CLIENT, "Checking if frpc process is running...");
    return frpcProcessStatus();
  });

  ipcMain.on("frpc.start", async (event, args) => {
    logInfo(LogModule.FRP_CLIENT, "Received request to start frpc process.");
    getConfig((err1, config) => {
      if (!err1) {
        if (!config) {
          logWarn(
            LogModule.FRP_CLIENT,
            "Configuration not found. Prompting user to set configuration."
          );
          event.reply(
            "Home.frpc.start.error.hook",
            "请先前往设置页面，修改配置后再启动"
          );
          return;
        }
        if (!config.currentVersion) {
          logWarn(
            LogModule.FRP_CLIENT,
            "Current version not set in configuration. Prompting user."
          );
          event.reply(
            "Home.frpc.start.error.hook",
            "请先前往设置页面，修改配置后再启动"
          );
          return;
        }
        startFrpWorkerProcess(config);
      } else {
        logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`);
      }
    });
  });

  ipcMain.on("frpc.stop", () => {
    logInfo(LogModule.FRP_CLIENT, "Received request to stop frpc process.");
    if (frpcProcess && !frpcProcess.killed) {
      stopFrpcProcess(() => {});
    } else {
      logWarn(LogModule.FRP_CLIENT, "No frpc process to stop.");
    }
  });
};
