#!/usr/bin/env node
/**
 * 灰度阶段晋级策略 — PR-12 Active Memory Rollout Stage Promotion
 *
 * 根据 rollout report、readiness、circuit breaker 数据，判断当前灰度
 * 阶段是否健康，给出下一阶段建议（promote / freeze / rollback / kill switch）。
 *
 * 这是一个纯函数模块，不：
 *   - 读写任何文件
 *   - 读写环境变量
 *   - 读写 SQLite / FTS5
 *   - 修改 MEMORY.md
 *   - 修改生产召回路径
 *   - 有任何副作用
 *
 * 阶段序列：0 → 1 → 5 → 10 → 25 → 50 → 100
 *
 * 动作：
 *   promote           — 可以推进到下一阶段
 *   freeze            — 保持当前阶段，继续观察
 *   rollback          — 退回上一阶段
 *   activate_kill_switch — 紧急关闭 replacement
 *
 * 指标门禁：
 *   promote:
 *     - readiness.status === "pass"
 *     - circuitBreaker.state === "closed"
 *     - rolloutReport.recommendation === "increase_rollout" 或 "keep_current"
 *     - timeoutRate <= 0.01
 *     - mismatchRate <= 0.01
 *     - fallbackRate <= 0.05
 *     - 当前阶段满足最低样本数
 *
 *   freeze:
 *     - promote 条件未满足但未劣化到 rollback
 *     - 样本不足但指标健康
 *     - rolloutReport.recommendation === "keep_current"
 *
 *   rollback:
 *     - rolloutReport.recommendation === "decrease_rollout"
 *     - timeoutRate 或 mismatchRate 超警示阈值但未达临界
 *     - fallbackRate 明显升高
 *
 *   activate_kill_switch:
 *     - rolloutReport.recommendation === "activate_kill_switch"
 *     - circuitBreaker.state === "open"
 *     - readiness.status === "fail"
 *     - timeoutRate >= 0.05 或 mismatchRate >= 0.10
 *
 * @module precompute-rollout-stage-policy
 */

// ─── 阶段定义 ────────────────────────────────────────

/** 灰度阶段序列 */
export const STAGES = [0, 1, 5, 10, 25, 50, 100];

/** 动作常量 */
export const ACTION_PROMOTE = "promote";
export const ACTION_FREEZE = "freeze";
export const ACTION_ROLLBACK = "rollback";
export const ACTION_KILL_SWITCH = "activate_kill_switch";

// ─── 默认门禁阈值 ────────────────────────────────────

const DEFAULT_THRESHOLDS = {
  /** 超时率：触发 rollback */
  timeoutRateWarn: 0.02,
  /** 超时率：触发 kill switch */
  timeoutRateCritical: 0.05,
  /** 不匹配率：触发 rollback */
  mismatchRateWarn: 0.05,
  /** 不匹配率：触发 kill switch */
  mismatchRateCritical: 0.10,
  /** 回退率：触发 rollback */
  fallbackRateWarn: 0.10,
  /** 回退率：触发 kill switch */
  fallbackRateCritical: 0.20,
  /** 选择率下限：低于此值说明 replacement 未生效 */
  minSelectionRate: 0.70,
  /** 各阶段最低样本数 */
  minSamples: {
    0: 0,
    1: 50,
    5: 100,
    10: 200,
    25: 300,
    50: 400,
    100: 500,
  },
};

// ─── 阶段查找工具 ────────────────────────────────────

/**
 * 在阶段序列中查找当前阶段的下标。
 * 如果不在序列中，返回 -1。
 */
function _stageIndex(currentStage) {
  return STAGES.indexOf(currentStage);
}

/**
 * 获取下一阶段。100 之后返回 null。
 */
function _nextStage(currentStage) {
  const idx = _stageIndex(currentStage);
  if (idx < 0 || idx >= STAGES.length - 1) return null;
  return STAGES[idx + 1];
}

/**
 * 获取上一阶段。0 之前返回 null。
 */
function _prevStage(currentStage) {
  const idx = _stageIndex(currentStage);
  if (idx <= 0) return null;
  return STAGES[idx - 1];
}

/**
 * 验证阶段是否合法。
 */
function _isValidStage(stage) {
  return STAGES.includes(stage);
}

// ─── Stage Policy 主函数 ─────────────────────────────

/**
 * 评估当前灰度阶段是否应该推进、冻结、回退或触发熔断。
 *
 * @param {object} params
 * @param {number} params.currentStage - 当前灰度百分比（0/1/5/10/25/50/100）
 * @param {object} params.rolloutReport - PR-11 rollout report 输出
 *   包含：recommendation, timeoutRate, mismatchRate, requests, enrolled,
 *         replacementSelected, fallbackPrimary
 * @param {object} params.readiness - 就绪评估结果
 *   包含：status ("pass"|"warn"|"fail"), safeToReplace
 * @param {object} params.circuitBreaker - 断路器快照
 *   包含：state ("closed"|"open"|"half_open")
 * @param {object} [params.options] - 可选项
 *   thresholds: 自定义门禁阈值（合并默认值）
 *
 * @returns {object}
 *   - action: "promote" | "freeze" | "rollback" | "activate_kill_switch"
 *   - currentStage: number
 *   - nextStage: number | null
 *   - reasons: string[] — 决策原因
 *   - confidence: number 0–100
 *   - gates: object — 各门禁通过情况
 *   - recommendation: string — 人类可读建议
 */
export function evaluateStagePromotion(params) {
  // ── 参数校验 ──────────────────────────────────
  if (!params || typeof params !== "object") {
    return _buildResult(
      ACTION_FREEZE, 0, null, ["没有提供有效参数 — 冻结当前状态"],
      50, {}, "参数无效，无法评估阶段晋级。请提供完整的 rollout / readiness / circuit 数据。"
    );
  }

  const {
    currentStage,
    rolloutReport = {},
    readiness = {},
    circuitBreaker = {},
    options = {},
  } = params;

  const thresholds = { ...DEFAULT_THRESHOLDS, ...(options.thresholds || {}) };
  // 合并阶段最低样本数
  if (options.thresholds && options.thresholds.minSamples) {
    thresholds.minSamples = { ...DEFAULT_THRESHOLDS.minSamples, ...options.thresholds.minSamples };
  }

  const reasons = [];
  const gates = {};

  // ── 门禁 1：阶段合法性 ─────────────────────────
  if (!_isValidStage(currentStage)) {
    reasons.push(`非法灰度阶段 ${currentStage} — 有效阶段: ${STAGES.join(", ")}`);
    return _buildResult(
      ACTION_FREEZE, currentStage || 0, null, reasons,
      30, gates, `当前阶段 ${currentStage} 不在有效阶段列表中。请修正为 0/1/5/10/25/50/100 之一。`
    );
  }

  // 提取指标（安全取值，缺省为 0）
  const timeoutRate = typeof rolloutReport.timeoutRate === "number" ? rolloutReport.timeoutRate : 0;
  const mismatchRate = typeof rolloutReport.mismatchRate === "number" ? rolloutReport.mismatchRate : 0;
  const requests = typeof rolloutReport.requests === "number" ? rolloutReport.requests : 0;
  const enrolled = typeof rolloutReport.enrolled === "number" ? rolloutReport.enrolled : 0;
  const replacementSelected = typeof rolloutReport.replacementSelected === "number" ? rolloutReport.replacementSelected : 0;
  const fallbackPrimary = typeof rolloutReport.fallbackPrimary === "number" ? rolloutReport.fallbackPrimary : 0;

  const fallbackRate = enrolled > 0 ? fallbackPrimary / enrolled : 0;
  const selectionRate = enrolled > 0 ? replacementSelected / enrolled : 0;

  const readinessStatus = readiness.status || "unknown";
  const safeToReplace = readiness.safeToReplace === true;
  const circuitState = circuitBreaker.state || "unknown";
  const rolloutRecommendation = rolloutReport.recommendation || "unknown";

  const minSamples = (thresholds.minSamples && thresholds.minSamples[currentStage]) || 100;

  // ── 门禁 2：readiness 检查 ─────────────────────
  gates.readinessPass = readinessStatus === "pass" && safeToReplace;
  gates.readinessStatus = readinessStatus;
  if (readinessStatus === "fail") {
    reasons.push(`就绪评估 FAIL — replacement 不应激活`);
  } else if (readinessStatus === "warn") {
    reasons.push(`就绪评估 WARN — 质量存疑，建议不推进`);
  } else if (readinessStatus === "unknown") {
    reasons.push(`就绪评估状态未知 — 无法判断`);
  }

  // ── 门禁 3：断路器检查 ─────────────────────────
  gates.circuitPass = circuitState === "closed";
  gates.circuitState = circuitState;
  if (circuitState === "open") {
    reasons.push(`断路器 OPEN — replacement 已被自动阻断`);
  } else if (circuitState === "half_open") {
    reasons.push(`断路器 HALF_OPEN — 正在探测恢复，不宜变更`);
  } else if (circuitState === "unknown") {
    reasons.push(`断路器状态未知`);
  }

  // ── 门禁 4：rollout report 建议 ────────────────
  gates.rolloutRecommendation = rolloutRecommendation;
  // promote 要求 rollout report 明确建议 increase_rollout；
  // keep_current 意味着不建议推进 → 不满足 promote 门禁
  gates.rolloutRecAcceptable = rolloutRecommendation === "increase_rollout";
  if (rolloutRecommendation === "activate_kill_switch") {
    reasons.push(`Rollout report 建议 ACTIVATE_KILL_SWITCH`);
  } else if (rolloutRecommendation === "disable_replacement" || rolloutRecommendation === "decrease_rollout") {
    reasons.push(`Rollout report 建议 ${rolloutRecommendation} — 不应推进`);
  }

  // ── 门禁 5：超时率 ─────────────────────────────
  gates.timeoutRate = timeoutRate;
  if (timeoutRate > thresholds.timeoutRateCritical) {
    gates.timeoutPass = false;
    gates.timeoutSeverity = "critical";
    reasons.push(`超时率 ${(timeoutRate * 100).toFixed(1)}% ≥ ${thresholds.timeoutRateCritical * 100}% 临界值 — 触发 kill switch`);
  } else if (timeoutRate >= thresholds.timeoutRateWarn) {
    gates.timeoutPass = false;
    gates.timeoutSeverity = "warn";
    reasons.push(`超时率 ${(timeoutRate * 100).toFixed(1)}% ≥ ${thresholds.timeoutRateWarn * 100}% 警示值 — 不建议推进`);
  } else {
    gates.timeoutPass = true;
    gates.timeoutSeverity = "ok";
  }

  // ── 门禁 6：不匹配率 ───────────────────────────
  gates.mismatchRate = mismatchRate;
  if (mismatchRate > thresholds.mismatchRateCritical) {
    gates.mismatchPass = false;
    gates.mismatchSeverity = "critical";
    reasons.push(`不匹配率 ${(mismatchRate * 100).toFixed(1)}% ≥ ${thresholds.mismatchRateCritical * 100}% 临界值 — 触发 kill switch`);
  } else if (mismatchRate >= thresholds.mismatchRateWarn) {
    gates.mismatchPass = false;
    gates.mismatchSeverity = "warn";
    reasons.push(`不匹配率 ${(mismatchRate * 100).toFixed(1)}% ≥ ${thresholds.mismatchRateWarn * 100}% 警示值 — 不建议推进`);
  } else {
    gates.mismatchPass = true;
    gates.mismatchSeverity = "ok";
  }

  // ── 门禁 7：回退率 ─────────────────────────────
  gates.fallbackRate = Math.round(fallbackRate * 10000) / 10000;
  if (fallbackRate > thresholds.fallbackRateCritical) {
    gates.fallbackPass = false;
    gates.fallbackSeverity = "critical";
    reasons.push(`回退率 ${(fallbackRate * 100).toFixed(1)}% ≥ ${thresholds.fallbackRateCritical * 100}% 临界值 — 触发 kill switch`);
  } else if (fallbackRate >= thresholds.fallbackRateWarn) {
    gates.fallbackPass = false;
    gates.fallbackSeverity = "warn";
    reasons.push(`回退率 ${(fallbackRate * 100).toFixed(1)}% ≥ ${thresholds.fallbackRateWarn * 100}% 警示值 — 不应推进`);
  } else {
    gates.fallbackPass = true;
    gates.fallbackSeverity = "ok";
  }

  // ── 门禁 8：选择率 ─────────────────────────────
  gates.selectionRate = Math.round(selectionRate * 10000) / 10000;
  if (enrolled > 0 && selectionRate < thresholds.minSelectionRate) {
    gates.selectionPass = false;
    reasons.push(`选择率 ${(selectionRate * 100).toFixed(1)}% < ${thresholds.minSelectionRate * 100}% — replacement 选择率过低`);
  } else {
    gates.selectionPass = true;
  }

  // ── 门禁 9：样本数 ─────────────────────────────
  gates.sampleCount = requests;
  gates.minSamplesRequired = minSamples;
  if (requests < minSamples) {
    gates.samplesPass = false;
    reasons.push(`样本数 ${requests} < ${minSamples}（${currentStage}% 阶段最低要求）— 数据不足`);
  } else {
    gates.samplesPass = true;
  }

  // ── 决策逻辑 ───────────────────────────────────

  // 1. 最高优先级：kill switch 条件
  const criticalConditions = [
    readinessStatus === "fail",
    circuitState === "open",
    rolloutRecommendation === "activate_kill_switch",
    gates.timeoutSeverity === "critical",
    gates.mismatchSeverity === "critical",
    gates.fallbackSeverity === "critical",
  ];

  if (criticalConditions.some(c => c)) {
    return _buildResult(
      ACTION_KILL_SWITCH, currentStage, null, reasons,
      _calcConfidence(reasons, "critical"), gates,
      `关键门禁未通过，建议立即激活 kill switch，将灰度回退到 0%。`
    );
  }

  // 2. rollback 条件（仅指标警示，不包含 readiness warn / half_open — 那些是 freeze）
  const rollbackReasons = [];
  if (rolloutRecommendation === "decrease_rollout" || rolloutRecommendation === "disable_replacement") {
    rollbackReasons.push("rollout report 建议降低或关闭");
  }
  if (gates.timeoutSeverity === "warn") rollbackReasons.push("超时率超过警示值");
  if (gates.mismatchSeverity === "warn") rollbackReasons.push("不匹配率超过警示值");
  if (gates.fallbackSeverity === "warn") rollbackReasons.push("回退率超过警示值");

  if (rollbackReasons.length > 0) {
    const prevStage = _prevStage(currentStage);
    return _buildResult(
      ACTION_ROLLBACK, currentStage, prevStage, reasons,
      _calcConfidence(reasons, "warn"), gates,
      `存在 ${rollbackReasons.length} 项警示：${rollbackReasons.join("；")}。建议退回${prevStage !== null ? `到 ${prevStage}%` : "并保持"}。`
    );
  }

  // 3. promote 条件
  const allPromoteGates = [
    gates.readinessPass,
    gates.circuitPass,
    gates.rolloutRecAcceptable,
    gates.timeoutPass,
    gates.mismatchPass,
    gates.fallbackPass,
    gates.selectionPass,
    gates.samplesPass,
  ];

  // 额外：100% 已经最大，不能 promote
  if (currentStage >= 100) {
    reasons.push("已到达最大灰度阶段 100%");
    return _buildResult(
      ACTION_FREEZE, currentStage, null, reasons,
      95, gates,
      "已处于全量灰度阶段，所有指标通过。继续保持 100% 并监控。"
    );
  }

  if (allPromoteGates.every(g => g === true)) {
    const nextStage = _nextStage(currentStage);
    return _buildResult(
      ACTION_PROMOTE, currentStage, nextStage, reasons,
      85, gates,
      `所有门禁通过，建议将灰度从 ${currentStage}% 推进到 ${nextStage}%。`
    );
  }

  // 4. freeze（默认）
  if (reasons.length === 0) {
    reasons.push("当前指标处于可接受范围，但部分门禁尚未满足推进条件");
  }
  reasons.push("建议保持当前阶段继续观察");

  return _buildResult(
    ACTION_FREEZE, currentStage, null, reasons,
    _calcConfidence(reasons, "ok"), gates,
    `不建议变更灰度阶段。保持 ${currentStage}% 继续收集数据。`
  );
}

// ─── 内部工具 ────────────────────────────────────────

function _buildResult(action, currentStage, nextStage, reasons, confidence, gates, recommendation) {
  return {
    action,
    currentStage,
    nextStage,
    reasons,
    confidence: Math.min(100, Math.max(0, Math.round(confidence))),
    gates,
    recommendation,
  };
}

function _calcConfidence(reasons, severity) {
  const weights = {
    critical: 95,
    warn: 70,
    ok: 50,
  };
  const base = weights[severity] || 50;
  // 原因越多，置信度稍降
  const penalty = Math.min(20, reasons.length * 3);
  return Math.max(0, base - penalty);
}

// ─── 工具导出 ────────────────────────────────────────

export { _stageIndex, _nextStage, _prevStage, _isValidStage };
