const path = require("path");
const fs = require("fs");
const { spawn } = require("child_process");
const { httpPost } = require("./http");
const { computeHash, projectRoot, readMasterRuntime, loadComphubConfig, colors, apiUrl } = require("./utils");
const { cyan, green, white, yellow, red } = colors;
const { ADMIN_API, POLL_INTERVAL, MAX_RETRIES, DEFAULT_PORT } = require("./constants");
const getPort = require("get-port");

// ---- Config ----

function mergeConfig(opts, cwd) {
  // 加载项目配置文件，作为命令行参数的 fallback
  const fileConfig = loadComphubConfig(cwd);
  // 优先级：命令行 > 配置文件 > 默认值
  return {
    dir: opts.dir || fileConfig.dir || "./",
    api: opts.api || fileConfig.api,
    allowDebug: opts.allowDebug != null ? opts.allowDebug : fileConfig.allowDebug != null ? fileConfig.allowDebug : false,
    proxy: opts.proxy || fileConfig.proxy,
    port: opts.port
  };
}

// ---- Master lifecycle ----

async function waitForMasterRuntime() {
  for (let i = 0; i < MAX_RETRIES; i++) {
    const runtime = await readMasterRuntime();
    if (runtime) return runtime;
    await new Promise(r => setTimeout(r, POLL_INTERVAL));
  }
  return null;
}

async function spawnMaster(publicPort, pkg, cwd) {
  const masterEntry = path.join(projectRoot, "dist/master.js");

  if (!fs.existsSync(masterEntry)) {
    throw new Error("Master entry file not found, startup failed");
  }

  const masterArgs = ["--port", String(publicPort), "--public-port", String(publicPort)];
  const child = spawn(process.execPath, [masterEntry, ...masterArgs], {
    stdio: "ignore",
    detached: true,
    windowsHide: true,
    cwd,
    env: {
      ...process.env,
      COMP_HUB_VERSION: pkg.version
    }
  });

  child.unref();

  const runtime = await waitForMasterRuntime();
  if (!runtime) {
    child.kill();
    throw new Error("Master startup timeout");
  }

  return runtime;
}

/**
 * 确保 master 在运行，返回运行时信息。
 * 如果已在运行则复用，否则 spawn 新的 master 进程。
 */
async function ensureMasterRunning(opts, pkg) {
  let portMismatch = false;
  let runningPublicPort = 0;

  const existingRuntime = await readMasterRuntime();

  if (existingRuntime) {
    // Master 已在运行，检查用户指定的端口是否匹配
    const expectedPort = opts.port != null ? opts.port : existingRuntime.publicPort;
    if (opts.port != null && existingRuntime.publicPort !== expectedPort) {
      portMismatch = true;
      runningPublicPort = existingRuntime.publicPort;
    }
    return { runtime: existingRuntime, portMismatch, runningPublicPort };
  }

  // 启动新 master
  const portHint = opts.port != null ? opts.port : DEFAULT_PORT;
  const publicPort = await getPort({ port: getPort.makeRange(portHint, portHint + 100) });

  const runtime = await spawnMaster(publicPort, pkg, process.cwd());
  return { runtime, portMismatch, runningPublicPort };
}

// ---- Registration ----

async function registerProject(masterPort, hash, cwd, opts) {
  const body = {
    hash,
    cwd,
    dir: opts.dir || "./"
  };
  if (opts.api) body.api = opts.api;
  if (opts.allowDebug) body.allowDebug = true;
  if (opts.proxy) body.proxy = opts.proxy;

  const res = await httpPost(apiUrl(masterPort, ADMIN_API.REGISTER), body);
  if (!res.ok) {
    throw new Error(res.error || "Registration failed");
  }
  console.log(`${green("✓")} Project registered (hash: ${hash})`);
  return res;
}

// ---- Banner ----

function printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort }) {
  const url = `http://localhost:${displayPort}/${hash}/main/`;

  const lines = [
    "",
    cyan("   ██████╗  ██████╗  ███╗   ███╗ ██████╗       ██╗  ██╗ ██╗   ██╗ ██████╗ "),
    cyan("  ██╔════╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗      ██║  ██║ ██║   ██║ ██╔══██╗"),
    cyan("  ██║      ██║   ██║ ██╔████╔██║ ██████╔╝      ███████║ ██║   ██║ ██████╔╝"),
    cyan("  ██║      ██║   ██║ ██║╚██╔╝██║ ██╔═══╝       ██╔══██║ ██║   ██║ ██╔══██╗"),
    cyan("  ╚██████╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║           ██║  ██║ ╚██████╔╝ ██████╔╝"),
    cyan("   ╚═════╝  ╚═════╝  ╚═╝     ╚═╝ ╚═╝           ╚═╝  ╚═╝  ╚═════╝  ╚═════╝ "),
    "",
    "",
    `  ${green("Version")}  ${pkg.version}`,
    `  ${green("Project")}  ${cwd}`,
    `  ${green("Hash")}     ${hash}`,
    `  ${green("Port")}     ${displayPort}`,
    "",
    `  ${yellow("URL")}      ${cyan(url)}`,
    "",
    white("  ─────────────────────────────────────────────"),
    "",
    `  ${"comphub status".padEnd(30)}View all projects`,
    `  ${`comphub stop    ${hash}`.padEnd(30)}Stop current project`,
    `  ${"comphub stop    --all".padEnd(30)}Stop all projects`,
    `  ${"comphub kill".padEnd(30)}Shutdown master`,
    ""
  ];

  if (portMismatch) {
    lines.push(
      white("  ─────────────────────────────────────────────"),
      "",
      red(`  ⚠ Master is already running on port ${runningPublicPort}, ignoring -p ${opts.port}`),
      red(`    To change port, please run comphub kill first`),
      ""
    );
  }

  console.log(lines.join("\n"));
}

// ---- Main ----

async function main(pkg, opts) {
  if (!fs.existsSync(path.join(process.cwd(), "package.json"))) {
    console.error(`${red("✗")} Current directory is not a Node.js project`);
    process.exit(1);
  }

  const cwd = process.cwd();
  const hash = computeHash(cwd);

  opts = mergeConfig(opts, cwd);

  console.log(`${green("✓")} ${pkg.name} v${pkg.version}`);

  const { runtime, portMismatch, runningPublicPort } = await ensureMasterRunning(opts, pkg);

  const masterPort = runtime.managementPort;
  const displayPort = runtime.publicPort;

  await registerProject(masterPort, hash, cwd, opts);

  printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort });
}

module.exports = { main };
