#!/usr/bin/env node
/**
 * 一键启动“生产模式”的前后端与相关服务（适用于 Cloudflare Tunnel / 公网访问）。
 *
 * 目标：
 * - 前后端与内部服务统一读取仓库根目录 `.env.all`（生产优先，全局唯一 env 文件）
 *
 * 可选环境变量：
 * - SKIP_FRONTEND_BUILD=1：跳过前端 build（仅 start）
 * - FRONTEND_PORT：默认 3000
 * - BACKEND_PORT：默认 8000
 * - RAG_GATEWAY_PORT：默认 8778
 * - INGEST_PORT：默认 8779
 * - PYTHON_BIN：默认 python
 * - CELERY_BIN：默认 celery
 * - SKIP_CELERY=1：跳过 Celery worker
 * - REDIS_URL：用于探测 Redis，默认 redis://127.0.0.1:6379/0
 */

const { spawn } = require("child_process");
const fs = require("fs");
const net = require("net");
const path = require("path");
const { URL } = require("url");

const rootDir = path.resolve(__dirname, "..");
const frontendDir = path.join(rootDir, "wolai-frontend");
const backendDir = path.join(rootDir, "wolai-backend");
const ingestDir = path.join(rootDir, "services", "ingest_service");
const ragGatewayDir = path.join(rootDir, "services", "rag_gateway");

function resolveBackendExecutable(envName, fallbackName) {
  const fromEnv = (process.env[envName] || "").trim();
  if (fromEnv) return fromEnv;

  const isWin = process.platform === "win32";
  const candidates = isWin
    ? [
        path.join(backendDir, ".venv", "Scripts", `${fallbackName}.exe`),
        path.join(backendDir, ".venv312", "Scripts", `${fallbackName}.exe`),
        path.join(backendDir, "venv", "Scripts", `${fallbackName}.exe`),
      ]
    : [
        path.join(backendDir, ".venv-linux", "bin", fallbackName),
        path.join(backendDir, ".venv", "bin", fallbackName),
        path.join(backendDir, "venv", "bin", fallbackName),
      ];

  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) {
      return candidate;
    }
  }

  return fallbackName;
}

const pythonBin = resolveBackendExecutable("PYTHON_BIN", "python");
const celeryBin = resolveBackendExecutable("CELERY_BIN", "celery");
const skipCelery =
  (process.env.SKIP_CELERY || "").toLowerCase() === "1" ||
  (process.env.SKIP_CELERY || "").toLowerCase() === "true";
const redisUrl = process.env.REDIS_URL || "redis://127.0.0.1:6379/0";

const frontendPort = Number(process.env.FRONTEND_PORT || 3000);
const backendPort = Number(process.env.BACKEND_PORT || 8000);
const ragGatewayPort = Number(process.env.RAG_GATEWAY_PORT || 8778);
const ingestPort = Number(process.env.INGEST_PORT || 8779);

const children = [];
let shuttingDown = false;

function logPrefix(name, message) {
  console.log(`[${name}] ${message}`);
}

function loadEnvFile(filePath) {
  if (!fs.existsSync(filePath)) return {};
  const content = fs.readFileSync(filePath, "utf8");
  return content
    .split(/\r?\n/)
    .filter((line) => line.trim() && !line.trim().startsWith("#"))
    .reduce((acc, line) => {
      const idx = line.indexOf("=");
      if (idx === -1) return acc;
      const key = line.slice(0, idx).trim();
      const value = line.slice(idx + 1).trim();
      acc[key] = value;
      return acc;
    }, {});
}

async function waitForExit(child, name) {
  return await new Promise((resolve) => {
    child.on("exit", (code, signal) => {
      const status = signal ? `信号 ${signal}` : `退出码 ${code ?? "null"}`;
      logPrefix(name, `进程结束（${status}）`);
      resolve(code ?? 0);
    });
    child.on("error", () => resolve(1));
  });
}

function spawnTask({ name, command, cwd, env }) {
  logPrefix(name, `启动命令：${command}`);
  const child = spawn(command, {
    cwd,
    stdio: "inherit",
    shell: true,
    env,
  });
  child.on("exit", (code, signal) => {
    if (shuttingDown) return;
    const status = signal !== null ? `因信号 ${signal} 退出` : `退出码 ${code ?? "null"}`;
    logPrefix(name, `进程结束（${status}），准备清理其它任务。`);
    shutdown(code ?? 0);
  });
  child.on("error", (err) => {
    logPrefix(name, `启动失败：${err.message}`);
    shutdown(1);
  });
  children.push(child);
  return child;
}

function shutdown(code) {
  if (shuttingDown) return;
  shuttingDown = true;
  logPrefix("system", "收到终止信号，正在关闭所有子进程…");
  for (const child of children) {
    if (!child.killed) {
      child.kill("SIGINT");
      setTimeout(() => {
        if (!child.killed) {
          child.kill("SIGTERM");
        }
      }, 5000);
    }
  }
  setTimeout(() => process.exit(code), 200);
}

process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));

async function checkPortOpen(host, port, timeoutMs = 1200) {
  return await new Promise((resolve) => {
    const socket = net.createConnection({ host, port });
    const timer = setTimeout(() => {
      socket.destroy();
      resolve(false);
    }, timeoutMs);
    socket.once("connect", () => {
      clearTimeout(timer);
      socket.end();
      resolve(true);
    });
    socket.once("error", () => {
      clearTimeout(timer);
      resolve(false);
    });
  });
}

async function checkRedisReachable(urlString, timeoutMs = 2000) {
  try {
    const url = new URL(urlString);
    const host = url.hostname || "127.0.0.1";
    const port = Number(url.port) || 6379;
    return await checkPortOpen(host, port, timeoutMs);
  } catch (error) {
    logPrefix("celery", `REDIS_URL (${urlString}) 解析失败：${error.message}，跳过连通性检查。`);
    return true;
  }
}

function requireFile(filePath, hint) {
  if (!fs.existsSync(filePath)) {
    console.error(`[system] 缺少文件：${filePath}`);
    if (hint) console.error(`[system] 提示：${hint}`);
    process.exit(1);
  }
}

function ensureStandaloneStatic(frontendDir) {
  // 说明：当 next.config.ts 配置了 output: "standalone" 时，推荐使用
  // `node .next/standalone/server.js` 启动。
  // 但 Next 不会自动把 `.next/static` 放入 `.next/standalone/.next/static`，
  // 若缺失会导致 `/_next/static/*` 404（远程访问尤其明显）。
  const standaloneServer = path.join(frontendDir, ".next", "standalone", "server.js");
  if (!fs.existsSync(standaloneServer)) return false;

  const src = path.join(frontendDir, ".next", "static");
  const dest = path.join(frontendDir, ".next", "standalone", ".next", "static");
  if (!fs.existsSync(src)) return true;

  try {
    fs.mkdirSync(path.dirname(dest), { recursive: true });
    // Node 16+：fs.cpSync
    fs.cpSync(src, dest, { recursive: true });
    return true;
  } catch (err) {
    logPrefix(
      "frontend",
      `复制 .next/static 到 standalone 失败：${err instanceof Error ? err.message : String(err)}`,
    );
    // 失败时不强制退出，仍尝试启动（方便你自行排查）
    return true;
  }
}

async function main() {
  // 约定：全局仅使用仓库根目录的 .env.all 作为配置来源（生产优先）。
  const envAllPath = path.join(rootDir, ".env.all");
  requireFile(envAllPath, "请先在仓库根目录创建 .env.all（全局唯一 env 文件）");
  const envAll = loadEnvFile(envAllPath);

  // 生产模式：前端必须使用 production env，避免本地开发配置污染公网用户
  const envFrontend = {
    ...process.env,
    ...envAll,
    NODE_ENV: "production",
    NEXT_TELEMETRY_DISABLED: "1",
  };

  // 后端/内部服务：尽量使用本机/内网配置（不会暴露给浏览器）
  const envBackend = {
    ...process.env,
    ...envAll,
    PYTHONUTF8: "1",
  };
  const envInternalServices = {
    ...process.env,
    ...envAll,
    PYTHONUTF8: "1",
  };

  const shouldSkipBuild =
    (process.env.SKIP_FRONTEND_BUILD || "").toLowerCase() === "1" ||
    (process.env.SKIP_FRONTEND_BUILD || "").toLowerCase() === "true";

  if (!shouldSkipBuild) {
    logPrefix("frontend", "开始 production build…");
    const build = spawn("pnpm -C wolai-frontend build", {
      cwd: rootDir,
      stdio: "inherit",
      shell: true,
      env: envFrontend,
    });
    const code = await waitForExit(build, "frontend");
    if (code !== 0) {
      process.exit(code);
    }
  } else {
    logPrefix("frontend", "已跳过 build（SKIP_FRONTEND_BUILD=1）");
  }

  const useStandalone = ensureStandaloneStatic(frontendDir);

  // 预检查：提醒必要端口是否已就绪（不强制退出，避免误伤已有进程）
  const portChecks = [
    { name: "frontend", port: frontendPort },
    { name: "backend", port: backendPort },
    { name: "rag_gateway", port: ragGatewayPort },
    { name: "ingest_service", port: ingestPort },
  ];
  for (const item of portChecks) {
    // 仅检查本机 127.0.0.1 是否已有服务在监听，方便你快速判断“是否重复启动”
    // 监听着不代表冲突：可能是你已经手动启动了。
    // eslint-disable-next-line no-await-in-loop
    const open = await checkPortOpen("127.0.0.1", item.port);
    if (open) {
      logPrefix("system", `检测到端口 ${item.port} 已有服务监听（${item.name}），若你准备全量启动，请确认不会端口冲突。`);
    }
  }

  // 启动前端（production）
  const envFrontendServer = {
    ...envFrontend,
    PORT: String(frontendPort),
    HOSTNAME: "0.0.0.0",
  };
  spawnTask({
    name: "frontend",
    // 说明：next.config.ts 配置了 output: "standalone" 时，应使用 standalone server 启动，避免 next start 行为不一致。
    command: useStandalone
      ? "node .next/standalone/server.js"
      : `pnpm -C wolai-frontend exec next start -p ${frontendPort} -H 0.0.0.0`,
    cwd: useStandalone ? frontendDir : rootDir,
    env: envFrontendServer,
  });

  // 启动 wolai-backend（FastAPI）
  spawnTask({
    name: "backend",
    command: `${pythonBin} -m uvicorn app.main:app --host 0.0.0.0 --port ${backendPort}`,
    cwd: backendDir,
    env: envBackend,
  });

  // Celery（可选）
  if (!skipCelery) {
    const ok = await checkRedisReachable(envBackend.REDIS_URL || redisUrl);
    if (ok) {
      spawnTask({
        name: "celery",
        command: `${celeryBin} -A app.workers.celery_app worker --loglevel=info`,
        cwd: backendDir,
        env: envBackend,
      });
    } else {
      logPrefix("celery", `检测到 Redis 不可达（${envBackend.REDIS_URL || redisUrl}），跳过 Celery。`);
    }
  } else {
    logPrefix("celery", "已跳过 Celery（SKIP_CELERY=1）");
  }

  // 启动 ingest_service（自动入库、OCR 触发、延迟删除清理）
  spawnTask({
    name: "ingest",
    command: `${pythonBin} -m uvicorn app.main:app --host 0.0.0.0 --port ${ingestPort}`,
    cwd: ingestDir,
    env: envInternalServices,
  });

  // 启动 rag_gateway（/rag 查询网关）
  spawnTask({
    name: "rag_gateway",
    command: `${pythonBin} -m uvicorn app.main:app --host 0.0.0.0 --port ${ragGatewayPort}`,
    cwd: ragGatewayDir,
    env: envInternalServices,
  });

  logPrefix(
    "system",
    `已启动：frontend:${frontendPort} backend:${backendPort} ingest:${ingestPort} rag_gateway:${ragGatewayPort}（如需公网访问，请确保 cloudflared tunnel 正在运行）`,
  );
}

main().catch((error) => {
  console.error(`[system] 启动失败：${error instanceof Error ? error.message : String(error)}`);
  process.exit(1);
});
