/**
 * GitHub Webhook 监听器
 * 工单编号: GH-GMP-004
 * 开发者: 培园A04 (5TH-LE-HK-A04)
 * 职责: 接收GitHub push/PR事件 · 验证签名 · 触发模块自动部署
 */

const crypto = require('crypto');
const express = require('express');
const { createLogger } = require('./lib/logger');

const logger = createLogger('webhook');

// ─── 速率限制 (纯Map实现, 不引入新依赖) ──────────────────
// 策略: 同一IP在 60秒窗口内最多 30 次请求, 超出返回 429
const RATE_LIMIT_WINDOW_MS = 60 * 1000;
const RATE_LIMIT_MAX = 30;
// IP -> 时间戳数组 (滑动窗口)
const rateLimitStore = new Map();

/**
 * 提取客户端IP (兼容直连 / 反向代理后的 X-Forwarded-For)
 */
function getClientIp(req) {
  const xff = req.headers['x-forwarded-for'];
  if (typeof xff === 'string' && xff.length > 0) {
    return xff.split(',')[0].trim();
  }
  return req.ip || req.connection?.remoteAddress || 'unknown';
}

/**
 * 速率限制中间件
 */
function rateLimiter(req, res, next) {
  const ip = getClientIp(req);
  const now = Date.now();
  const windowStart = now - RATE_LIMIT_WINDOW_MS;

  let timestamps = rateLimitStore.get(ip) || [];
  // 丢弃窗口外的旧请求
  timestamps = timestamps.filter(t => t > windowStart);

  if (timestamps.length >= RATE_LIMIT_MAX) {
    const retryAfter = Math.ceil((timestamps[0] + RATE_LIMIT_WINDOW_MS - now) / 1000);
    res.setHeader('Retry-After', String(Math.max(retryAfter, 1)));
    logger.warn('[Webhook] 速率限制触发 ip=' + ip + ' count=' + timestamps.length);
    return res.status(429).json({
      error: 'Too Many Requests',
      message: '请求过于频繁, 请稍后再试',
      retryAfter: retryAfter
    });
  }

  timestamps.push(now);
  rateLimitStore.set(ip, timestamps);

  // 周期性清理冷IP (避免Map无限增长)
  if (rateLimitStore.size > 10000) {
    for (const [k, v] of rateLimitStore) {
      const fresh = v.filter(t => t > windowStart);
      if (fresh.length === 0) {
        rateLimitStore.delete(k);
      } else {
        rateLimitStore.set(k, fresh);
      }
    }
  }

  next();
}

/**
 * 验证GitHub Webhook签名 (HMAC SHA-256)
 */
function verifySignature(secret, payload, signature) {
  if (!secret || !signature) return false;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  try {
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );
  } catch (err) {
    return false;
  }
}

/**
 * 解析push事件中变更的模块目录
 * 约定: guanghu-self-hosted/<module-name>/ 下的变更触发该模块重新部署
 */
function extractChangedModules(commits) {
  const modules = new Set();
  for (const commit of commits || []) {
    const allFiles = []
      .concat(commit.added || [])
      .concat(commit.modified || [])
      .concat(commit.removed || []);

    for (const file of allFiles) {
      // 匹配 guanghu-self-hosted/<module-name>/... 路径
      const match = file.match(/^guanghu-self-hosted\/([^\/]+)\//);
      if (match) {
        modules.add(match[1]);
      }
    }
  }
  return Array.from(modules);
}

/**
 * 创建Webhook路由
 * @param {GMPAgent} agent - GMP-Agent实例引用
 */
function createWebhookRouter(agent) {
  const router = express.Router();

  // 速率限制 (router-level, 应用到所有 webhook 路由)
  // 防止恶意请求消耗服务器资源
  router.use(rateLimiter);

  // 需要原始body来验证签名
  router.use(express.raw({ type: 'application/json', limit: '10mb' }));

  /**
   * POST /webhook/github
   * 接收GitHub Webhook事件
   */
  router.post('/github', async (req, res) => {
    const event = req.headers['x-github-event'];
    const delivery = req.headers['x-github-delivery'];
    const signature = req.headers['x-hub-signature-256'];

    logger.info('[Webhook] 收到事件: ' + event + ' delivery=' + (delivery || 'N/A'));

    // 签名验证
    const secret = agent.config.webhookSecret;
    // express.raw 将 req.body 设为 Buffer
    let rawBody;
    if (Buffer.isBuffer(req.body)) {
      rawBody = req.body;
    } else if (typeof req.body === 'string') {
      rawBody = Buffer.from(req.body, 'utf-8');
    } else {
      rawBody = Buffer.from(JSON.stringify(req.body), 'utf-8');
    }

    if (secret) {
      if (!verifySignature(secret, rawBody, signature)) {
        logger.warn('[Webhook] 签名验证失败, 拒绝请求');
        return res.status(401).json({ error: 'Invalid signature' });
      }
    }

    // 解析payload
    let payload;
    try {
      payload = JSON.parse(rawBody.toString('utf-8'));
    } catch (err) {
      logger.error('[Webhook] payload解析失败: ' + err.message);
      return res.status(400).json({ error: 'Invalid JSON payload' });
    }

    // 先响应200, 后台处理
    res.json({ received: true, event: event, delivery: delivery });

    // 异步处理事件
    try {
      await handleEvent(agent, event, payload);
    } catch (err) {
      logger.error('[Webhook] 事件处理失败: ' + err.message);
    }
  });

  // Webhook状态
  router.get('/status', (req, res) => {
    res.json({
      webhookEnabled: true,
      secretConfigured: !!agent.config.webhookSecret,
      endpoint: '/webhook/github'
    });
  });

  return router;
}

/**
 * 处理GitHub事件
 */
async function handleEvent(agent, event, payload) {
  switch (event) {
    case 'push':
      await handlePushEvent(agent, payload);
      break;

    case 'pull_request':
      await handlePullRequestEvent(agent, payload);
      break;

    case 'ping':
      logger.info('[Webhook] Ping事件: ' + (payload.zen || ''));
      break;

    default:
      logger.info('[Webhook] 忽略事件类型: ' + event);
  }
}

/**
 * 处理push事件 → 自动部署变更的模块
 */
async function handlePushEvent(agent, payload) {
  const ref = payload.ref || '';
  const branch = ref.replace('refs/heads/', '');
  const repo = (payload.repository && payload.repository.full_name) || 'unknown';
  const pusher = (payload.pusher && payload.pusher.name) || 'unknown';

  logger.info('[Webhook] Push事件: ' + repo + ' 分支=' + branch + ' 推送者=' + pusher);

  // 只处理main分支的push (或配置的目标分支)
  const targetBranch = agent.config.targetBranch || 'main';
  if (branch !== targetBranch) {
    logger.info('[Webhook] 忽略非目标分支: ' + branch + ' (目标=' + targetBranch + ')');
    return;
  }

  // 提取变更的模块
  const changedModules = extractChangedModules(payload.commits);
  if (changedModules.length === 0) {
    logger.info('[Webhook] 无模块目录变更, 跳过部署');
    return;
  }

  logger.info('[Webhook] 检测到 ' + changedModules.length + ' 个模块变更: ' + changedModules.join(', '));

  // 逐个触发安装/更新
  for (const moduleName of changedModules) {
    try {
      logger.info('[Webhook] 开始自动部署模块: ' + moduleName);
      const result = await agent.installer.install({
        repoUrl: (payload.repository && payload.repository.clone_url) || '',
        moduleName: moduleName,
        branch: targetBranch,
        autoTriggered: true
      });
      logger.info('[Webhook] 模块部署完成: ' + moduleName + ' -> ' + result.status);
    } catch (err) {
      logger.error('[Webhook] 模块部署失败: ' + moduleName + ' -> ' + err.message);
    }
  }
}

/**
 * 处理pull_request事件 (记录日志, 后续可扩展为自动审查)
 */
async function handlePullRequestEvent(agent, payload) {
  const action = payload.action || 'unknown';
  const prNumber = (payload.pull_request && payload.pull_request.number) || 'N/A';
  const prTitle = (payload.pull_request && payload.pull_request.title) || 'N/A';
  const prUser = (payload.pull_request && payload.pull_request.user && payload.pull_request.user.login) || 'unknown';

  logger.info('[Webhook] PR事件: #' + prNumber + ' ' + action + ' by ' + prUser + ' - ' + prTitle);

  // PR合并到目标分支时也可触发部署 (留作扩展)
  if (action === 'closed' && payload.pull_request && payload.pull_request.merged) {
    logger.info('[Webhook] PR已合并, 等待push事件触发部署');
  }
}

module.exports = createWebhookRouter;
