#!/usr/bin/env node
/**
 * skyeye/hibernation/sleep-scheduler.js
 * ═══════════════════════════════════════════════
 * 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
 * 📜 Copyright: 国作登字-2026-A-00037559
 * ═══════════════════════════════════════════════
 *
 * 🧠 动态休眠决策引擎
 * 根据当日/本周运行数据，动态计算休眠时长
 *
 * 用法:
 *   node sleep-scheduler.js --mode=daily   → 计算日休眠时长
 *   node sleep-scheduler.js --mode=weekly  → 计算周休眠时长
 *
 * 输出: JSON 格式的休眠决策结果到 stdout
 */

'use strict';

const fs   = require('fs');
const path = require('path');

const ROOT         = path.resolve(__dirname, '../..');
const SKYEYE_DIR   = path.resolve(__dirname, '..');
const GUARDS_DIR   = path.join(SKYEYE_DIR, 'guards');
const LOGS_DIR     = path.join(SKYEYE_DIR, 'logs');
const BUFFER_DIR   = path.join(ROOT, 'buffer');
const CP_DIR       = path.join(__dirname, 'checkpoints');

// ━━━ 工具函数 ━━━

function getTimestamp() {
  return new Date().toISOString();
}

function getBeijingTime() {
  return new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
}

function getDateStr() {
  return new Date().toISOString().slice(0, 10).replace(/-/g, '');
}

function loadJSON(filePath) {
  try {
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (e) {
    return null;
  }
}

function countFiles(dir, filter) {
  try {
    if (!fs.existsSync(dir)) return 0;
    const files = fs.readdirSync(dir);
    return filter ? files.filter(filter).length : files.length;
  } catch (e) {
    return 0;
  }
}

// ━━━ 日休眠评估因子 ━━━

const DAILY_EVALUATION_FACTORS = {
  base_minutes: 5,

  factors: [
    {
      name: 'workflow_load',
      description: '今日 workflow 触发总次数',
      weight: {
        low: 0,       // < 50 次
        medium: 2,    // 50-200 次
        high: 5,      // 200-500 次
        extreme: 8    // > 500 次
      }
    },
    {
      name: 'guard_self_heal_count',
      description: '今日 Guard 自愈触发次数',
      weight: {
        zero: 0,
        low: 2,       // 1-3 次
        high: 5,      // 4-10 次
        critical: 10  // > 10 次
      }
    },
    {
      name: 'error_count',
      description: '今日异常/错误计数',
      weight: {
        zero: 0,
        low: 1,       // 1-5 个
        medium: 3,    // 6-15 个
        high: 8       // > 15 个
      }
    },
    {
      name: 'buffer_backlog',
      description: 'buffer 堆积量',
      weight: {
        empty: 0,
        normal: 1,    // < 100 条
        heavy: 3,     // 100-500 条
        overflow: 5   // > 500 条
      }
    },
    {
      name: 'quota_burn_rate',
      description: '今日配额消耗速度 vs 日均值',
      weight: {
        normal: 0,
        elevated: 2,  // 高于均值 30%
        dangerous: 5  // 高于均值 60%
      }
    },
    {
      name: 'checkin_anomaly',
      description: '小兵签到异常数',
      weight: {
        zero: 0,
        few: 1,       // 1-2 个
        many: 3       // > 2 个
      }
    }
  ],

  max_minutes: 30,

  calculate: function (evaluatedFactors) {
    let total = DAILY_EVALUATION_FACTORS.base_minutes;
    for (const factor of evaluatedFactors) {
      total += factor.computed_weight;
    }
    return Math.min(total, DAILY_EVALUATION_FACTORS.max_minutes);
  }
};

// ━━━ 周休眠评估因子 ━━━

const WEEKLY_EVALUATION_FACTORS = {
  base_hours: 2,

  factors: [
    {
      name: 'weekly_health_trend',
      description: '本周 6 个 daily-checkpoint 的健康趋势',
      weight: {
        stable: 0,
        declining: 0.5,
        volatile: 1
      }
    },
    {
      name: 'optimization_requests',
      description: '本周自优化申请数量',
      weight: {
        few: 0,
        moderate: 0.5,
        many: 1
      }
    },
    {
      name: 'self_heal_total',
      description: '本周累计自愈次数',
      weight: {
        low: 0,
        medium: 0.5,
        high: 1
      }
    },
    {
      name: 'structural_changes',
      description: '本周是否有新应用接入/架构变更',
      weight: {
        none: 0,
        minor: 0.5,
        major: 1.5
      }
    },
    {
      name: 'upgrade_distribution_scope',
      description: '需要分发升级的小兵数量',
      weight: {
        few: 0,
        moderate: 0.5,
        many: 1
      }
    },
    {
      name: 'notion_sync_volume',
      description: 'Notion 端需要同步的变更量',
      weight: {
        light: 0,
        moderate: 0.5,
        heavy: 1
      }
    }
  ],

  max_hours: 8,

  calculate: function (evaluatedFactors) {
    let total = WEEKLY_EVALUATION_FACTORS.base_hours;
    for (const factor of evaluatedFactors) {
      total += factor.computed_weight;
    }
    return Math.min(total, WEEKLY_EVALUATION_FACTORS.max_hours);
  }
};

// ━━━ 数据采集 ━━━

function collectDailyData() {
  const data = {
    workflow_triggers: 0,
    guard_self_heals: 0,
    error_count: 0,
    buffer_backlog: 0,
    quota_burn_rate: 'normal',
    checkin_anomalies: 0
  };

  // 1. 读取今日日志目录统计 workflow 触发
  const dailyLogDir = path.join(LOGS_DIR, 'daily');
  if (fs.existsSync(dailyLogDir)) {
    const files = fs.readdirSync(dailyLogDir).filter(f => f.endsWith('.json'));
    data.workflow_triggers = files.length * 10; // 估算：每个日志文件约代表 10 次触发
  }

  // 2. 读取 Guard 配置统计自愈次数
  if (fs.existsSync(GUARDS_DIR)) {
    const guardFiles = fs.readdirSync(GUARDS_DIR)
      .filter(f => f.endsWith('.json') && f !== 'guard-template.json');
    for (const file of guardFiles) {
      const guard = loadJSON(path.join(GUARDS_DIR, file));
      if (guard && guard.health_check) {
        data.guard_self_heals += (guard.health_check.consecutive_failures || 0);
      }
    }
  }

  // 3. 统计 buffer 堆积量
  const inboxDir = path.join(BUFFER_DIR, 'inbox');
  if (fs.existsSync(inboxDir)) {
    try {
      const devDirs = fs.readdirSync(inboxDir).filter(d => {
        return fs.statSync(path.join(inboxDir, d)).isDirectory();
      });
      for (const dev of devDirs) {
        data.buffer_backlog += countFiles(path.join(inboxDir, dev), f => f.endsWith('.json'));
      }
    } catch (e) {
      // ignore
    }
  }

  // 4. 读取配额 ledger 判断消耗速度
  const ledger = loadJSON(path.join(SKYEYE_DIR, 'quota-ledger.json'));
  if (ledger && ledger.services) {
    const actions = ledger.services.github_actions;
    if (actions) {
      const usedPct = actions.monthly_limit > 0
        ? (actions.current_used || 0) / actions.monthly_limit * 100
        : 0;
      if (usedPct > 60) data.quota_burn_rate = 'dangerous';
      else if (usedPct > 30) data.quota_burn_rate = 'elevated';
    }
  }

  return data;
}

function evaluateDailyFactors(data) {
  const results = [];

  // workflow_load
  let wl = 0;
  if (data.workflow_triggers > 500) wl = 8;
  else if (data.workflow_triggers > 200) wl = 5;
  else if (data.workflow_triggers >= 50) wl = 2;
  results.push({ name: 'workflow_load', value: data.workflow_triggers, level: wl > 5 ? 'extreme' : wl > 2 ? 'high' : wl > 0 ? 'medium' : 'low', computed_weight: wl });

  // guard_self_heal_count
  let gh = 0;
  if (data.guard_self_heals > 10) gh = 10;
  else if (data.guard_self_heals >= 4) gh = 5;
  else if (data.guard_self_heals >= 1) gh = 2;
  results.push({ name: 'guard_self_heal_count', value: data.guard_self_heals, level: gh > 5 ? 'critical' : gh > 2 ? 'high' : gh > 0 ? 'low' : 'zero', computed_weight: gh });

  // error_count
  let ec = 0;
  if (data.error_count > 15) ec = 8;
  else if (data.error_count >= 6) ec = 3;
  else if (data.error_count >= 1) ec = 1;
  results.push({ name: 'error_count', value: data.error_count, level: ec > 3 ? 'high' : ec > 1 ? 'medium' : ec > 0 ? 'low' : 'zero', computed_weight: ec });

  // buffer_backlog
  let bb = 0;
  if (data.buffer_backlog > 500) bb = 5;
  else if (data.buffer_backlog >= 100) bb = 3;
  else if (data.buffer_backlog > 0) bb = 1;
  results.push({ name: 'buffer_backlog', value: data.buffer_backlog, level: bb > 3 ? 'overflow' : bb > 1 ? 'heavy' : bb > 0 ? 'normal' : 'empty', computed_weight: bb });

  // quota_burn_rate
  let qb = 0;
  if (data.quota_burn_rate === 'dangerous') qb = 5;
  else if (data.quota_burn_rate === 'elevated') qb = 2;
  results.push({ name: 'quota_burn_rate', value: data.quota_burn_rate, level: data.quota_burn_rate, computed_weight: qb });

  // checkin_anomaly
  let ca = 0;
  if (data.checkin_anomalies > 2) ca = 3;
  else if (data.checkin_anomalies >= 1) ca = 1;
  results.push({ name: 'checkin_anomaly', value: data.checkin_anomalies, level: ca > 1 ? 'many' : ca > 0 ? 'few' : 'zero', computed_weight: ca });

  return results;
}

function collectWeeklyData() {
  const data = {
    health_trend: 'stable',
    optimization_requests: 0,
    self_heal_total: 0,
    structural_changes: 'none',
    upgrade_targets: 0,
    notion_sync_volume: 'light'
  };

  // 读取本周 checkpoint
  if (fs.existsSync(CP_DIR)) {
    const cpFiles = fs.readdirSync(CP_DIR)
      .filter(f => f.startsWith('daily-cp-') && f.endsWith('.json'))
      .sort()
      .slice(-6);

    let totalHeals = 0;
    for (const file of cpFiles) {
      const cp = loadJSON(path.join(CP_DIR, file));
      if (cp && cp.health_summary) {
        const guards = cp.health_summary.guards || {};
        for (const g of Object.values(guards)) {
          totalHeals += (g.self_heals_today || 0);
        }
      }
    }
    data.self_heal_total = totalHeals;
  }

  // 统计子仓库数量（upgrade targets）
  const spokeDir = path.join(ROOT, 'spoke-deployments');
  if (fs.existsSync(spokeDir)) {
    try {
      data.upgrade_targets = fs.readdirSync(spokeDir)
        .filter(d => fs.statSync(path.join(spokeDir, d)).isDirectory()).length;
    } catch (e) {
      // ignore
    }
  }

  // 读取 ASOP 请求（optimization_requests）
  const asopDir = path.join(ROOT, 'data', 'asop-requests');
  if (fs.existsSync(asopDir)) {
    data.optimization_requests = countFiles(asopDir, f => f.endsWith('.json'));
  }

  return data;
}

function evaluateWeeklyFactors(data) {
  const results = [];

  // weekly_health_trend
  let ht = 0;
  if (data.health_trend === 'volatile') ht = 1;
  else if (data.health_trend === 'declining') ht = 0.5;
  results.push({ name: 'weekly_health_trend', value: data.health_trend, computed_weight: ht });

  // optimization_requests
  let or_ = 0;
  if (data.optimization_requests > 10) or_ = 1;
  else if (data.optimization_requests >= 4) or_ = 0.5;
  results.push({ name: 'optimization_requests', value: data.optimization_requests, computed_weight: or_ });

  // self_heal_total
  let sh = 0;
  if (data.self_heal_total > 30) sh = 1;
  else if (data.self_heal_total >= 10) sh = 0.5;
  results.push({ name: 'self_heal_total', value: data.self_heal_total, computed_weight: sh });

  // structural_changes
  let sc = 0;
  if (data.structural_changes === 'major') sc = 1.5;
  else if (data.structural_changes === 'minor') sc = 0.5;
  results.push({ name: 'structural_changes', value: data.structural_changes, computed_weight: sc });

  // upgrade_distribution_scope
  let ud = 0;
  if (data.upgrade_targets > 15) ud = 1;
  else if (data.upgrade_targets >= 5) ud = 0.5;
  results.push({ name: 'upgrade_distribution_scope', value: data.upgrade_targets, computed_weight: ud });

  // notion_sync_volume
  let ns = 0;
  if (data.notion_sync_volume === 'heavy') ns = 1;
  else if (data.notion_sync_volume === 'moderate') ns = 0.5;
  results.push({ name: 'notion_sync_volume', value: data.notion_sync_volume, computed_weight: ns });

  return results;
}

// ━━━ 主流程 ━━━

function run() {
  const args = process.argv.slice(2);
  const modeArg = args.find(a => a.startsWith('--mode='));
  const mode = modeArg ? modeArg.split('=')[1] : 'daily';

  console.log(`[SkyEye Sleep Scheduler] Mode: ${mode}`);
  console.log(`[SkyEye Sleep Scheduler] Timestamp: ${getTimestamp()}`);
  console.log(`[SkyEye Sleep Scheduler] Beijing Time: ${getBeijingTime()}`);

  let result;

  if (mode === 'weekly') {
    const data = collectWeeklyData();
    const factors = evaluateWeeklyFactors(data);
    const totalHours = WEEKLY_EVALUATION_FACTORS.calculate(factors);
    const totalMinutes = Math.round(totalHours * 60);

    result = {
      scheduler_id: `SLEEP-WEEKLY-${getDateStr()}`,
      mode: 'weekly',
      timestamp: getTimestamp(),
      beijing_time: getBeijingTime(),
      raw_data: data,
      evaluated_factors: factors,
      decision: {
        base_hours: WEEKLY_EVALUATION_FACTORS.base_hours,
        additional_hours: totalHours - WEEKLY_EVALUATION_FACTORS.base_hours,
        total_hours: totalHours,
        total_minutes: totalMinutes,
        max_hours: WEEKLY_EVALUATION_FACTORS.max_hours,
        human_readable: `约 ${Math.floor(totalHours)} 小时 ${totalMinutes % 60} 分钟`
      }
    };

    console.log(`[SkyEye Sleep Scheduler] Weekly hibernation decision: ${result.decision.human_readable}`);
  } else {
    const data = collectDailyData();
    const factors = evaluateDailyFactors(data);
    const totalMinutes = DAILY_EVALUATION_FACTORS.calculate(factors);

    result = {
      scheduler_id: `SLEEP-DAILY-${getDateStr()}`,
      mode: 'daily',
      timestamp: getTimestamp(),
      beijing_time: getBeijingTime(),
      raw_data: data,
      evaluated_factors: factors,
      decision: {
        base_minutes: DAILY_EVALUATION_FACTORS.base_minutes,
        additional_minutes: totalMinutes - DAILY_EVALUATION_FACTORS.base_minutes,
        total_minutes: totalMinutes,
        max_minutes: DAILY_EVALUATION_FACTORS.max_minutes,
        human_readable: `约 ${totalMinutes} 分钟`
      }
    };

    console.log(`[SkyEye Sleep Scheduler] Daily hibernation decision: ${result.decision.human_readable}`);
  }

  // 输出结果到 stdout 供下游脚本读取
  const outputPath = path.join(__dirname, `sleep-decision-${mode}-${getDateStr()}.json`);
  fs.writeFileSync(outputPath, JSON.stringify(result, null, 2) + '\n', 'utf8');
  console.log(`[SkyEye Sleep Scheduler] Decision written to: ${outputPath}`);

  // 输出关键结果到 stdout 方便 workflow 日志检索
  console.log(`[RESULT] sleep_minutes=${result.decision.total_minutes || 0}`);
  console.log(`[RESULT] sleep_decision=${JSON.stringify(result.decision)}`);

  return result;
}

// ━━━ 导出供其他模块使用 ━━━
module.exports = {
  DAILY_EVALUATION_FACTORS,
  WEEKLY_EVALUATION_FACTORS,
  collectDailyData,
  evaluateDailyFactors,
  collectWeeklyData,
  evaluateWeeklyFactors,
  run
};

if (require.main === module) {
  run();
}
