/*
 * ════════════════════════════════════════════════════════════════
 * 光湖 · Git 同步服务 (Git Sync)
 * 监听 Gitea Webhook，自动拉取最新代码到服务器
 * ════════════════════════════════════════════════════════════════
 *
 * 工作原理：
 * 1. Gitea 配置 Webhook → push 事件 → 通知此服务
 * 2. 本服务收到通知后执行 git pull 同步代码
 * 3. 同步成功后重启相关 PM2 服务
 *
 * 安全设计：
 * - 验证 Webhook Secret（防止伪造请求）
 * - 仅响应 push 到 main 分支的事件
 * - 同步操作以 root 权限运行（确保 git 权限无问题）
 * - 同步锁防止并发执行
 * - 同步失败自动重试（最多3次）
 *
 * 部署位置: /data/guanghulab/server/git-sync/
 * PM2 进程名: guanghulab-git-sync
 * ════════════════════════════════════════════════════════════════
 */
"use strict";

const express = require("express");
const crypto = require("crypto");
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");

const app = express();

// 捕获 raw body 用于 Webhook 签名验证
app.use(express.json({
  limit: "1mb",
  verify: (req, _res, buf) => {
    req.rawBody = buf.toString("utf8");
  }
}));

// ─── 配置 ─────────────────────────────────────────────────────
const CONFIG = {
  port: process.env.GIT_SYNC_PORT || 8082,
  host: process.env.GIT_SYNC_HOST || "127.0.0.1",

  // 仓库路径
  repoDir: process.env.REPO_DIR || "/data/guanghulab/repo",

  // Webhook Secret（和 Gitea 里配置的一致）
  webhookSecret: process.env.GIT_SYNC_SECRET || "",

  // Git 认证信息（内嵌在 remote URL 中）
  gitUser: process.env.GIT_USER || "bingshuo",
  gitToken: process.env.ZY_REPO_TOKEN || "",
  gitHost: process.env.GIT_HOST || "127.0.0.1:3001",
  gitRepo: process.env.GIT_REPO || "bingshuo/guanghulab.git",

  // 同步后要重启的 PM2 进程（逗号分隔）
  restartServices: (process.env.RESTART_SERVICES || "").split(",").filter(Boolean),

  // 重试配置
  maxRetries: 3,
  retryDelay: 5000, // 5秒

  // 同步日志
  syncLogPath: process.env.SYNC_LOG_PATH || "/data/guanghulab/.runtime/git-sync-log.json"
};

// ─── 同步锁 ─────────────────────────────────────────────────────
let isSyncing = false;

// ─── 同步日志 ──────────────────────────────────────────────────
let syncLog = [];

function loadSyncLog() {
  try {
    if (fs.existsSync(CONFIG.syncLogPath)) {
      syncLog = JSON.parse(fs.readFileSync(CONFIG.syncLogPath, "utf8"));
    }
  } catch { syncLog = []; }
}

function saveSyncLog() {
  try {
    const dir = path.dirname(CONFIG.syncLogPath);
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
    // 只保留最近50条
    syncLog = syncLog.slice(-50);
    fs.writeFileSync(CONFIG.syncLogPath, JSON.stringify(syncLog, null, 2));
  } catch (e) {
    console.error("[git-sync] 日志写入失败:", e.message);
  }
}

function addLog(entry) {
  syncLog.push({
    ...entry,
    timestamp: new Date().toISOString()
  });
  saveSyncLog();
}

// ─── 验证 Webhook Secret ──────────────────────────────────────
function verifyWebhook(payload, signature) {
  if (!CONFIG.webhookSecret) {
    // 未配置 Secret 时跳过验证（仅限内网使用）
    console.warn("[git-sync] ⚠️  Webhook Secret 未配置，跳过验证");
    return true;
  }

  if (!signature) return false;

  const expected = crypto
    .createHmac("sha256", CONFIG.webhookSecret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature)
  );
}

// ─── 执行 Git 同步 ────────────────────────────────────────────
function doSync(commitSha, committer, message) {
  if (isSyncing) {
    console.log("[git-sync] 同步进行中，跳过");
    return { skipped: true };
  }

  isSyncing = true;

  try {
    // 确保 remote URL 包含认证信息
    const authUrl = `http://${CONFIG.gitUser}:${CONFIG.gitToken}@${CONFIG.gitHost}/${CONFIG.gitRepo}`;

    execSync(`cd ${CONFIG.repoDir} && git remote set-url origin ${authUrl}`, {
      encoding: "utf8",
      timeout: 10000
    });

    // 执行 git pull
    const output = execSync(
      `cd ${CONFIG.repoDir} && git fetch origin && git reset --hard origin/main && git log --oneline -1`,
      { encoding: "utf8", timeout: 30000 }
    );

    console.log(`[git-sync] ✅ 同步成功: ${output.trim()}`);

    // 同步后重启相关服务
    if (CONFIG.restartServices.length > 0) {
      for (const svc of CONFIG.restartServices) {
        try {
          execSync(`pm2 restart ${svc}`, { encoding: "utf8", timeout: 15000 });
          console.log(`[git-sync] 🔄 已重启: ${svc}`);
        } catch (e) {
          console.error(`[git-sync] ⚠️  重启失败 ${svc}: ${e.message}`);
        }
      }
    }

    addLog({
      status: "success",
      commit: commitSha || output.trim().split(" ")[0],
      committer: committer || "unknown",
      message: message || output.trim(),
      output: output.trim()
    });

    return { success: true, output: output.trim() };

  } catch (e) {
    console.error(`[git-sync] ❌ 同步失败: ${e.message}`);

    addLog({
      status: "failed",
      commit: commitSha,
      committer: committer || "unknown",
      message: message || "",
      error: e.message
    });

    return { success: false, error: e.message };

  } finally {
    isSyncing = false;
  }
}

// ─── 带重试的同步 ──────────────────────────────────────────────
async function syncWithRetry(commitSha, committer, message) {
  for (let attempt = 1; attempt <= CONFIG.maxRetries; attempt++) {
    const result = doSync(commitSha, committer, message);

    if (result.success || result.skipped) return result;

    if (attempt < CONFIG.maxRetries) {
      console.log(`[git-sync] 重试 ${attempt}/${CONFIG.maxRetries}...`);
      await new Promise(r => setTimeout(r, CONFIG.retryDelay));
    }
  }

  return { success: false, error: "重试次数已用完" };
}

// ─── API 路由 ──────────────────────────────────────────────────

// Webhook 接收端点
app.post("/webhook", async (req, res) => {
  const signature = req.headers["x-gitea-signature"] || req.headers["x-hub-signature-256"] || "";
  // 使用 raw body 验证签名（Express JSON 解析后 stringify 会和原始 body 不一致）
  const payload = req.rawBody || JSON.stringify(req.body);

  // 验证签名
  if (!verifyWebhook(payload, signature)) {
    console.warn("[git-sync] ❌ Webhook 签名验证失败");
    return res.status(403).json({ error: "签名验证失败" });
  }

  const body = req.body;
  const ref = body.ref || "";
  const commitSha = body.after || body.head_commit?.id || "";
  const committer = body.pusher?.login || body.sender?.login || "unknown";
  const message = body.head_commit?.message || body.commits?.[0]?.message || "";

  // 仅响应 push 到 main 分支
  if (!ref.endsWith("/main")) {
    console.log(`[git-sync] 忽略非 main 分支推送: ${ref}`);
    return res.json({ ignored: true, ref });
  }

  console.log(`[git-sync] 📥 收到 push 事件: ${commitSha.substring(0, 8)} by ${committer}`);

  // 异步执行同步（不阻塞 Webhook 响应）
  syncWithRetry(commitSha, committer, message)
    .then(result => {
      if (result.success) {
        console.log(`[git-sync] ✅ 同步完成: ${commitSha.substring(0, 8)}`);
      } else {
        console.error(`[git-sync] ❌ 同步最终失败: ${result.error}`);
      }
    });

  // 立即返回 200，Gitea 不需要等待同步完成
  res.json({ received: true, syncing: true, commit: commitSha.substring(0, 8) });
});

// 手动触发同步
app.post("/sync", async (req, res) => {
  if (isSyncing) {
    return res.json({ syncing: true, message: "同步进行中" });
  }

  const result = await syncWithRetry("", "manual", "手动触发");
  res.json(result);
});

// 同步状态查询
app.get("/status", (req, res) => {
  const lastSync = syncLog.length > 0 ? syncLog[syncLog.length - 1] : null;

  // 读取当前仓库状态
  let repoStatus = null;
  try {
    const log = execSync(`cd ${CONFIG.repoDir} && git log --oneline -1 && git status --short`, {
      encoding: "utf8", timeout: 10000
    });
    repoStatus = log.trim();
  } catch {
    repoStatus = "无法读取";
  }

  res.json({
    service: "git-sync",
    status: isSyncing ? "syncing" : "idle",
    lastSync,
    repoStatus,
    totalSyncs: syncLog.length,
    failedSyncs: syncLog.filter(l => l.status === "failed").length
  });
});

// 健康检查
app.get("/health", (req, res) => {
  res.json({ status: "ok", uptime: process.uptime() });
});

// ─── 启动 ─────────────────────────────────────────────────────
loadSyncLog();

app.listen(CONFIG.port, CONFIG.host, () => {
  console.log(`[git-sync] Git 同步服务启动: http://${CONFIG.host}:${CONFIG.port}`);
  console.log(`[git-sync] 仓库路径: ${CONFIG.repoDir}`);
  console.log(`[git-sync] Webhook 端点: POST /webhook`);
  console.log(`[git-sync] 铸渊 · ICE-GL-ZY001 · TCS-0002∞`);
});
